Jak mogę utworzyć funkcję asynchroniczną w JavaScript?


143

Sprawdź ten kod :

<a href="#" id="link">Link</a>
<span>Moving</span>

$('#link').click(function () {
    console.log("Enter");
    $('#link').animate({ width: 200 }, 2000, function() {
         console.log("finished");            
    });    
    console.log("Exit");    
});

Jak widać w konsoli, funkcja „animate” jest asynchroniczna i „rozwidla” przepływ kodu blokowego modułu obsługi zdarzeń. W rzeczywistości :

$('#link').click(function () {
    console.log("Enter");
    asyncFunct();
    console.log("Exit");    
});

function asyncFunct() {
    console.log("finished");
}

podążaj za przepływem kodu blokowego!

Jeśli chcę utworzyć plik function asyncFunct() { } z takim zachowaniem, jak mogę to zrobić za pomocą javascript / jquery? Myślę, że jest to strategia bez użycia setTimeout()


spójrz na źródła jQuery :)
yatskevich

Mathod .animate () używa wywołania zwrotnego. Program Animate wywoła wywołanie zwrotne po zakończeniu animacji. Jeśli potrzebujesz tego samego zachowania .animate (), potrzebujesz wywołania zwrotnego (wywoływanego przez funkcję "main" po kilku innych operacjach). Jest inaczej, jeśli potrzebujesz „pełnej” funkcji asynchronicznej (funkcja o nazwie bez blokowania przepływu wykonywania). W tym przypadku możesz użyć setTimeout () z opóźnieniem bliskim 0.
Fabio Buda

@Fabio Buda: dlaczego callback () ma implementować coś w rodzaju asynchronii? W rzeczywistości nie działa jsfiddle.net/5H9XT/9
markzzz

w rzeczywistości po „callback” zacytowałem „pełną” metodę asynchroniczną z setTimeout. Miałem na myśli callback jako pseudo-async w sposobie wywoływania funkcji po innym kodzie :-)
Fabio Buda

Odpowiedzi:


185

Nie można utworzyć prawdziwie niestandardowej funkcji asynchronicznej. W końcu będziesz musiał wykorzystać technologię dostarczaną natywnie, taką jak:

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • Niektóre interfejsy API HTML5, takie jak File API, Web Database API
  • Technologie, które wspierają onload
  • ... wiele innych

W rzeczywistości do animacji jQuery używa setInterval .


1
Wczoraj omawiałem to z przyjacielem, więc ta odpowiedź jest idealna! Rozumiem i potrafię zidentyfikować funkcje asynchroniczne i prawidłowo z nich korzystać w JS. Ale po prostu, dlaczego nie możemy wdrożyć niestandardowych, nie jest dla mnie jasne. To jest jak czarna skrzynka, którą wiemy, jak to działa (powiedzmy, używając setInterval), ale nie możemy jej nawet otworzyć, aby zobaczyć, jak to się robi. Masz więcej informacji na ten temat?
Matheus Felipe

2
@MatheusFelipe te funkcje są natywne dla implementacji silnika javascript i jedyne, na czym możesz polegać, to specyfikacje, np. Timery HTML5 i ufaj naturze czarnej skrzynki, że zachowują się zgodnie ze specyfikacjami.
Spoike

10
@MatheusFelipe youtu.be/8aGhZQkoFbQ najlepszy talk dotychczas dotyczące tego tematu ...
Andreas Niedermair

Niektóre implementacje, w szczególności Node.js, obsługująsetImmediate
Jon Surrell,

co z promises. Czy to daje awaitable?
Nithin Chandran

69

Możesz użyć timera:

setTimeout( yourFn, 0 );

(gdzie yourFnjest odniesienie do twojej funkcji)

lub z Lodash :

_.defer( yourFn );

Odracza wywoływanie do funcmomentu wyczyszczenia bieżącego stosu wywołań. Wszelkie dodatkowe argumenty są podawane funcpodczas wywołania.


3
To nie działa, moja funkcja javascript, która rysuje na płótnie, powoduje, że interfejs użytkownika nie odpowiada.
gab06

2
@ gab06 - Powiedziałbym, że funkcja rysowania na płótnie blokuje się z własnych ważnych powodów. Podziel jego akcję na wiele mniejszych i wywołaj każdą z nich za pomocą timera: zobaczysz, że interfejs w ten sposób reaguje na kliknięcia myszą itp.
Marco Faustinelli

Link jest uszkodzony.
JulianSoto

1
@JulianSoto naprawiono
Šime Vidas

1
Minimalny czas setTimeoutto 4 milisekundy według specyfikacji HTML5. Nadanie mu 0 nadal zajmie minimalną ilość czasu. Ale tak, działa dobrze jako odrocznik funkcji.
hegez

30

tutaj masz proste rozwiązanie (inni o tym piszą) http://www.benlesh.com/2012/05/calling-javascript-function.html

A oto gotowe rozwiązanie:

function async(your_function, callback) {
    setTimeout(function() {
        your_function();
        if (callback) {callback();}
    }, 0);
}

TEST 1 ( może wyświetlać '1 x 2 3' lub '1 2 x 3' lub '1 2 3 x' ):

console.log(1);
async(function() {console.log('x')}, null);
console.log(2);
console.log(3);

TEST 2 ( zawsze wyświetla 'x 1' ):

async(function() {console.log('x');}, function() {console.log(1);});

Ta funkcja jest wykonywana z limitem czasu 0 - będzie symulować zadanie asynchroniczne


6
TEST 1 może właściwie wyprowadzić tylko „1 2 3 x”, a TEST 2 gwarantuje, że za każdym razem wyprowadzi „1 x”. Przyczyną nieoczekiwanych wyników w TEST 2 jest to, że console.log(1)jest wywoływana, a output ( undefined) jest przekazywany jako drugi argument do async(). W przypadku TESTU 1 myślę, że nie w pełni rozumiesz kolejkę wykonywania JavaScript. Ponieważ każde wywołanie, które ma console.log()miejsce na tym samym stosie, xjest rejestrowane jako ostatnie. Głosowałbym w dół na tę odpowiedź za dezinformację, ale nie mam wystarczającej liczby powtórzeń.
Joshua Piccari,

1
@Joshua: Wydaje @fider oznaczało napisać test 2 jak: async(function() {console.log('x')}, function(){console.log(1)});.
nzn

Tak @nzn i @Joshua Miałem na myśli TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});- już to poprawiłem
fider

Wyjście TESTU 2 to 1 x w async (function () {setTimeout (() => {console.log ('x');}, 1000)}, function () {console.log (1);});
Mohsen

10

Oto funkcja, która przyjmuje inną funkcję i wyprowadza wersję, która działa asynchronicznie.

var async = function (func) {
  return function () {
    var args = arguments;
    setTimeout(function () {
      func.apply(this, args);
    }, 0);
  };
};

Jest używany jako prosty sposób na utworzenie funkcji asynchronicznej:

var anyncFunction = async(function (callback) {
    doSomething();
    callback();
});

Różni się to od odpowiedzi @ fider, ponieważ sama funkcja ma swoją własną strukturę (bez dodanego wywołania zwrotnego, jest już w funkcji), a także dlatego, że tworzy nową funkcję, której można użyć.


setTimeout nie może być używany w pętli (wywołaj tę samą funkcję kilka razy z różnymi argumentami) .
user2284570

@ user2284570 Po to są domknięcia. (function(a){ asyncFunction(a); })(a)
Swivel

1
IIRC, możesz to również osiągnąć bez zamknięcia:setTimeout(asyncFunction, 0, a);
Swivel

1
Jeśli przez asynchroniczność rozumiemy: działanie w tle, równolegle do głównego wątku, to nie jest to naprawdę asynchroniczne. Wszystko to spowoduje opóźnienie wykonania do procesu .nextTick. Kod, który masz w funkcji, zostanie wykonany w głównym wątku. Jeśli funkcja została ustawiona na obliczanie PI, aplikacja zawiesi się, z przekroczeniem limitu czasu lub bez!
Mike M

1
Nie rozumiem, dlaczego ta odpowiedź jest przegłosowana. Kiedy kładę to w moim kodzie, program blokuje aż funkcja jest zakończona, co dokładnie to co powinno nie robić.
Martin Argerami

6

Edycja: całkowicie źle zrozumiałem pytanie. W przeglądarce użyłbym setTimeout. Gdyby ważne było, aby działał w innym wątku, użyłbym Web Workers .


1
? To nie tworzy funkcji asynchronicznej: O
markzzz

6

Późno, ale aby pokazać proste rozwiązanie promisespo ich wprowadzeniu w ES6 , o wiele łatwiej obsługuje wywołania asynchroniczne:

Ustawiasz kod asynchroniczny w nowej obietnicy:

var asyncFunct = new Promise(function(resolve, reject) {
    $('#link').animate({ width: 200 }, 2000, function() {
        console.log("finished");                
        resolve();
    });             
});

Uwaga, aby ustawić, resolve()kiedy zakończy się połączenie asynchroniczne.
Następnie dodajesz kod, który chcesz uruchomić po zakończeniu wywołania asynchronicznego wewnątrz .then()obietnicy:

asyncFunct.then((result) => {
    console.log("Exit");    
});

Oto jego fragment:

$('#link').click(function () {
    console.log("Enter");
    var asyncFunct = new Promise(function(resolve, reject) {
        $('#link').animate({ width: 200 }, 2000, function() {
            console.log("finished");            	
            resolve();
        }); 			
    });
    asyncFunct.then((result) => {
        console.log("Exit");    
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<a href="#" id="link">Link</a>
<span>Moving</span>

lub JSFiddle



3

Jeśli chcesz używać parametrów i regulować maksymalną liczbę funkcji asynchronicznych, możesz użyć prostego pracownika asynchronicznego, który zbudowałem:

var BackgroundWorker = function(maxTasks) {
    this.maxTasks = maxTasks || 100;
    this.runningTasks = 0;
    this.taskQueue = [];
};

/* runs an async task */
BackgroundWorker.prototype.runTask = function(task, delay, params) {
    var self = this;
    if(self.runningTasks >= self.maxTasks) {
        self.taskQueue.push({ task: task, delay: delay, params: params});
    } else {
        self.runningTasks += 1;
        var runnable = function(params) {
            try {
                task(params);
            } catch(err) {
                console.log(err);
            }
            self.taskCompleted();
        }
        // this approach uses current standards:
        setTimeout(runnable, delay, params);
    }
}

BackgroundWorker.prototype.taskCompleted = function() {
    this.runningTasks -= 1;

    // are any tasks waiting in queue?
    if(this.taskQueue.length > 0) {
        // it seems so! let's run it x)
        var taskInfo = this.taskQueue.splice(0, 1)[0];
        this.runTask(taskInfo.task, taskInfo.delay, taskInfo.params);
    }
}

Możesz go używać w ten sposób:

var myFunction = function() {
 ...
}
var myFunctionB = function() {
 ...
}
var myParams = { name: "John" };

var bgworker = new BackgroundWorker();
bgworker.runTask(myFunction, 0, myParams);
bgworker.runTask(myFunctionB, 0, null);

2
Function.prototype.applyAsync = function(params, cb){
      var function_context = this;
      setTimeout(function(){
          var val = function_context.apply(undefined, params); 
          if(cb) cb(val);
      }, 0);
}

// usage
var double = function(n){return 2*n;};
var display = function(){console.log(arguments); return undefined;};
double.applyAsync([3], display);

Chociaż nie różni się zasadniczo od innych rozwiązań, myślę, że moje rozwiązanie ma kilka dodatkowych fajnych rzeczy:

  • pozwala na parametry funkcji
  • przekazuje dane wyjściowe funkcji do wywołania zwrotnego
  • dodaje się to do Function.prototypeumożliwienia ładniejszego nazwania tego

Również podobieństwo do funkcji wbudowanej Function.prototype.applywydaje mi się odpowiednie.


1

Oprócz świetnej odpowiedzi @pimvdb i na wypadek, gdybyś się zastanawiał, async.js również nie oferuje prawdziwie asynchronicznych funkcji. Oto (bardzo) uproszczona wersja głównej metody biblioteki:

function asyncify(func) { // signature: func(array)
    return function (array, callback) {
        var result;
        try {
            result = func.apply(this, array);
        } catch (e) {
            return callback(e);
        }
        /* code ommited in case func returns a promise */
        callback(null, result);
    };
}

Tak więc funkcja chroni przed błędami i wdzięcznie przekazuje ją do wywołania zwrotnego do obsługi, ale kod jest tak samo synchroniczny jak każda inna funkcja JS.


1

Niestety JavaScript nie zapewnia funkcji asynchronicznej. Działa tylko w jednym wątku. Ale większość nowoczesnych przeglądarek zapewnia Workers , czyli drugie skrypty, które są wykonywane w tle i mogą zwracać wynik. Tak więc doszedłem do rozwiązania, które uważam za przydatne do asynchronicznego uruchamiania funkcji, która tworzy proces roboczy dla każdego wywołania asynchronicznego.

Poniższy kod zawiera funkcję async do wywołania w tle.

Function.prototype.async = function(callback) {
    let blob = new Blob([ "self.addEventListener('message', function(e) { self.postMessage({ result: (" + this + ").apply(null, e.data) }); }, false);" ], { type: "text/javascript" });
    let worker = new Worker(window.URL.createObjectURL(blob));
    worker.addEventListener("message", function(e) {
        this(e.data.result);
    }.bind(callback), false);
    return function() {
        this.postMessage(Array.from(arguments));
    }.bind(worker);
};

Oto przykład użycia:

(function(x) {
    for (let i = 0; i < 999999999; i++) {}
        return x * 2;
}).async(function(result) {
    alert(result);
})(10);

To wykonuje funkcję, która iteruje a forz ogromną liczbą, aby zająć trochę czasu jako demonstracja asynchroniczności, a następnie pobiera podwojenie podanej liczby. asyncMetoda zapewnia functionktóra wywołuje żądaną funkcję w tle, a to, co jest przewidziane jako parametr asyncwywołania zwrotne ze returnw jego unikalny parametr. Więc w funkcji zwrotnej alertotrzymuję wynik.


0

MDN ma dobry przykład użycia setTimeout zachowującego „this”.

Jak poniżej:

function doSomething() {
    // use 'this' to handle the selected element here
}

$(".someSelector").each(function() {
    setTimeout(doSomething.bind(this), 0);
});
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.