Wie kann ich eine asynchrone Funktion in Javascript erstellen?

143

Überprüfen Sie diesen Code :

<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");    
});

Wie Sie in der Konsole sehen können, ist die Funktion "animieren" asynchron und "gabelt" den Fluss des Ereignishandler-Blockcodes. Eigentlich :

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

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

Folgen Sie dem Ablauf des Blockcodes!

Wenn ich meine function asyncFunct() { }mit diesem Verhalten erstellen möchte , wie kann ich es mit Javascript / jquery tun? Ich denke , es ist eine Strategie ohne die Verwendung von setTimeout()

markzzz
quelle
Werfen
Die .animate () -Mathematik verwendet einen Rückruf. Animate ruft den Rückruf auf, wenn die Animation abgeschlossen ist. Wenn Sie das gleiche Verhalten von .animate () benötigen, benötigen Sie einen Rückruf (der von der "main" -Funktion nach einigen anderen Vorgängen aufgerufen wird). Es ist anders, wenn Sie eine "vollständige" asynchrone Funktion benötigen (eine Funktion, die aufgerufen wird, ohne den Ausführungsfluss zu blockieren). In diesem Fall können Sie setTimeout () mit einer Verzögerung von nahezu 0 verwenden.
Fabio Buda
@Fabio Buda: Warum sollte callback () eine Art Async implementieren? In der Tat tut es nicht jsfiddle.net/5H9XT/9
markzzz
Tatsächlich habe ich nach "Rückruf" eine "vollständige" asynchrone Methode mit setTimeout zitiert. Ich meinte Rückruf als pseudo-asynchron in der Art, wie die Funktion nach anderem Code aufgerufen wird :-)
Fabio Buda

Antworten:

185

Sie können keine wirklich benutzerdefinierte asynchrone Funktion erstellen. Sie müssen schließlich auf eine Technologie zurückgreifen, die von Haus aus bereitgestellt wird, z.

  • setInterval
  • setTimeout
  • requestAnimationFrame
  • XMLHttpRequest
  • WebSocket
  • Worker
  • Einige HTML5-APIs wie die Datei-API und die Web-Datenbank-API
  • Technologien, die unterstützen onload
  • ... viele andere

Tatsächlich verwendet jQuery für die Animation setInterval.

pimvdb
quelle
1
Ich habe das gestern mit einem Freund besprochen, also ist diese Antwort perfekt! Ich verstehe und kann die asynchronen Funktionen identifizieren und sie in JS richtig verwenden. Aber warum wir keine benutzerdefinierten implementieren können, ist mir nicht klar. Es ist wie eine Black Box, von der wir wissen, wie sie funktioniert (z. B. mit setInterval), aber wir können sie nicht einmal öffnen, um zu sehen, wie es gemacht wird. Haben Sie zufällig weitere Informationen zu diesem Thema?
Matheus Felipe
2
@MatheusFelipe Diese Funktionen sind in der Implementierung der Javascript-Engine enthalten. Sie können sich nur auf die Spezifikationen verlassen, z. B. HTML5-Timer, und vertrauen auf die Black-Box-Natur, die sie gemäß den Spezifikationen verhalten.
Spoike
10
@ MatheusFelipe youtu.be/8aGhZQkoFbQ bisher beste Rede zu diesem Thema ...
Andreas Niedermair
Einige Implementierungen, insbesondere Node.js, unterstützensetImmediate
Jon Surrell
was ist mit promises. Gibt es eine awaitable?
Nithin Chandran
69

Sie können einen Timer verwenden:

setTimeout( yourFn, 0 );

(Wo yourFnist ein Verweis auf Ihre Funktion)

oder mit Lodash :

_.defer( yourFn );

Verschiebt das Aufrufen von, funcbis der aktuelle Aufrufstapel gelöscht wurde. Alle zusätzlichen Argumente werden funcbeim Aufrufen bereitgestellt .

Šime Vidas
quelle
3
Dies funktioniert nicht. Meine Javascript-Funktion, die eine Zeichenfläche zeichnet, sorgt dafür, dass die Benutzeroberfläche nicht reagiert.
gab06
2
@ gab06 - Ich würde sagen, dass Ihre Zeichenfunktion aus guten Gründen blockiert. Teilen Sie die Aktion in viele kleinere auf und rufen Sie jede mit einem Timer auf: Sie werden sehen, dass die Benutzeroberfläche auf diese Weise auf Ihre Mausklicks usw. reagiert.
Marco Faustinelli
Link ist unterbrochen.
JulianSoto
1
@ JulianSoto behoben
Šime Vidas
1
Die Mindestzeit für setTimeoutbeträgt 4 Millisekunden gemäß HTML5-Spezifikation. Wenn Sie 0 geben, dauert dies immer noch mindestens so lange. Aber ja, es funktioniert gut als Funktionsaufschub.
Hegez
30

Hier haben Sie eine einfache Lösung (andere schreiben darüber) http://www.benlesh.com/2012/05/calling-javascript-function.html

Und hier haben Sie oben bereit Lösung:

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

TEST 1 ( kann '1 x 2 3' oder '1 2 x 3' oder '1 2 3 x' ausgeben ):

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

TEST 2 ( gibt immer 'x 1' aus ):

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

Diese Funktion wird mit Timeout 0 ausgeführt - sie simuliert eine asynchrone Aufgabe

fider
quelle
6
TEST 1 kann tatsächlich immer nur '1 2 3 x' ausgeben, und TEST 2 gibt garantiert jedes Mal '1 x' aus. Der Grund für die unerwarteten Ergebnisse in TEST 2 ist, dass console.log(1)aufgerufen wird und output ( undefined) als zweites Argument an übergeben wird async(). Im Fall von TEST 1 verstehen Sie die Ausführungswarteschlange von JavaScript meines Erachtens nicht vollständig. Da jeder console.log()Aufruf im selben Stapel ausgeführt wird, xwird garantiert zuletzt protokolliert. Ich würde diese Antwort für Fehlinformationen ablehnen, aber nicht genug Repräsentanten haben.
Joshua Piccari
1
@Joshua: Es scheint, dass @fider TEST 2 schreiben wollte als : async(function() {console.log('x')}, function(){console.log(1)});.
nzn
Ja @nzn und @Joshua Ich meinte TEST 2 as: async(function() {console.log('x')}, function(){console.log(1)});- ich habe es bereits korrigiert
fider
Die Ausgabe von TEST 2 ist 1 x in async (function () {setTimeout (() => {console.log ('x');}, 1000)}, function () {console.log (1);});
Mohsen
10

Hier ist eine Funktion, die eine andere Funktion übernimmt und eine Version ausgibt, die asynchron ausgeführt wird.

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

Es wird als einfache Möglichkeit verwendet, eine asynchrone Funktion zu erstellen:

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

Dies unterscheidet sich von der Antwort von @ fider, da die Funktion selbst eine eigene Struktur hat (kein Rückruf hinzugefügt, sie ist bereits in der Funktion enthalten) und auch eine neue Funktion erstellt, die verwendet werden kann.

Ethan McTague
quelle
setTimeout kann nicht in einer Schleife verwendet werden (rufen Sie dieselbe Funktion mehrmals mit unterschiedlichen Argumenten auf) .
user2284570
@ user2284570 Dafür sind Verschlüsse da. (function(a){ asyncFunction(a); })(a)
Schwenken Sie
1
IIRC, Sie könnten dies auch ohne Schließung erreichen:setTimeout(asyncFunction, 0, a);
Schwenken
1
Wenn mit asynchron gemeint ist: im Hintergrund parallel zum Hauptthread laufen, dann ist dies nicht wirklich asynchron. Dies verzögert lediglich die Ausführung von process.nextTick. Der Code, den Sie in der Funktion haben, wird im Hauptthread ausgeführt. Wenn die Funktion zur Berechnung des PI eingestellt wurde, friert die App mit oder ohne Zeitüberschreitung ein!
Mike M
1
Ich verstehe nicht, warum diese Antwort positiv bewertet wird. Wenn ich dies in meinen Code einfüge, blockiert das Programm, bis die Funktion beendet ist, was genau das ist, was es nicht tun sollte.
Martin Argerami
6

Edit: Ich habe die Frage total missverstanden. Im Browser würde ich verwenden setTimeout. Wenn es wichtig wäre, dass es in einem anderen Thread ausgeführt wird, würde ich Web Worker verwenden .

Linus Thiel
quelle
1
? Dies macht keine asynchrone Funktion: O
markzzz
6

Spät, aber um eine einfache Lösung zu zeigen, die promisesnach ihrer Einführung in ES6 verwendet wird , werden asynchrone Anrufe viel einfacher verarbeitet:

Sie legen den asynchronen Code in einem neuen Versprechen fest:

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

Hinweis zum resolve()Festlegen , wenn der asynchrone Aufruf beendet ist.
Anschließend fügen Sie den Code hinzu, den Sie ausführen möchten, nachdem der asynchrone Aufruf innerhalb .then()des Versprechens beendet wurde:

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

Hier ist ein Ausschnitt davon:

$('#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>

oder JSFiddle

hd84335
quelle
6

Diese Seite führt Sie durch die Grundlagen der Erstellung einer asynchronen Javascript-Funktion.

Seit ES2017 sind asynchrone Javacript-Funktionen viel einfacher zu schreiben. Sie sollten auch mehr über Versprechen lesen .

Shanabus
quelle
Der Link ist tot.
Martin Argerami
3

Wenn Sie Parameter verwenden und die maximale Anzahl von Async-Funktionen regulieren möchten, können Sie einen einfachen Async-Worker verwenden, den ich erstellt habe:

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);
    }
}

Sie können es so verwenden:

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

var bgworker = new BackgroundWorker();
bgworker.runTask(myFunction, 0, myParams);
bgworker.runTask(myFunctionB, 0, null);
João Alves
quelle
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);

Obwohl es sich nicht grundlegend von den anderen Lösungen unterscheidet, denke ich, dass meine Lösung ein paar zusätzliche nette Dinge tut:

  • Es ermöglicht Parameter für die Funktionen
  • es übergibt die Ausgabe der Funktion an den Rückruf
  • Es wird hinzugefügt, Function.prototypeum eine schönere Art zu ermöglichen, es zu nennen

Auch die Ähnlichkeit mit der eingebauten Funktion Function.prototype.applyerscheint mir angemessen.

Benjamin Kuykendall
quelle
1

Neben der großartigen Antwort von @pimvdb und für den Fall, dass Sie sich fragen, bietet async.js auch keine wirklich asynchronen Funktionen. Hier ist eine (sehr) abgespeckte Version der Hauptmethode der Bibliothek:

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);
    };
}

Die Funktion schützt also vor Fehlern und übergibt sie ordnungsgemäß an den Rückruf, um sie zu verarbeiten. Der Code ist jedoch genauso synchron wie jede andere JS-Funktion.

Mike M.
quelle
1

Leider bietet JavaScript keine asynchrone Funktionalität. Es funktioniert nur in einem einzigen Thread. Die meisten modernen Browser bieten jedoch Workers , dh zweite Skripte, die im Hintergrund ausgeführt werden und ein Ergebnis zurückgeben können. Daher habe ich eine Lösung gefunden, die meiner Meinung nach nützlich ist, um eine Funktion asynchron auszuführen, die für jeden asynchronen Aufruf einen Worker erstellt.

Der folgende Code enthält die Funktion async zum Aufrufen im Hintergrund.

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);
};

Dies ist ein Beispiel für die Verwendung:

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

Dies führt eine Funktion aus, die a formit einer großen Zahl iteriert , um sich Zeit als Demonstration der Asynchronität zu nehmen, und dann das Doppelte der übergebenen Zahl erhält. Die asyncMethode stellt eine functionFunktion bereit, die die gewünschte Funktion im Hintergrund aufruft, und eine Funktion, die als Parameter für asyncRückrufe returnin ihrem eindeutigen Parameter bereitgestellt wird. Also in der Rückruffunktion habe ich alertdas Ergebnis.

Davide Cannizzo
quelle
0

MDN hat ein gutes Beispiel für die Verwendung von setTimeout, bei dem "this" beibehalten wird.

Wie folgt:

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

$(".someSelector").each(function() {
    setTimeout(doSomething.bind(this), 0);
});
xtian
quelle