Asynchron?
Ich habe das Wort asynchron (asynchron) an vielen Stellen gesehen, aber wenn ich dieses Konzept nicht vollständig verstehe, stelle ich fest, dass ich oft als „bereits klar“ angesehen werde (*  ̄? ̄ ).
Wenn Sie eine ähnliche Situation haben, spielt es keine Rolle. Suchen Sie nach diesem Wort und Sie erhalten eine grobe Erklärung. Hier werde ich die Asynchronität von JavaScript etwas näher erläutern.
Sehen Sie sich diesen Code an:
var start = new Date(); setTimeout(function(){ var end = new Date(); console.log("Time elapsed: ", end - start, "ms"); }, 500); while (new Date - start < 1000) {};
Nachdem Sie diesen Code ausgeführt haben, erhalten Sie ein Ergebnis wie „Zeit verstrichen: 1013 ms“. Die von setTimeout() festgelegte Funktion, die 500 ms in der Zukunft ausgeführt werden soll, wartet tatsächlich mehr als 1000 ms, bevor sie ausgeführt wird.
Wie soll ich es erklären? Wenn setTimeout() aufgerufen wird, wird ein verzögertes Ereignis in die Warteschlange gestellt. Fahren Sie dann mit der Ausführung des Codes danach und des Codes danach fort, bis kein Code mehr vorhanden ist. Wenn kein Code vorhanden ist, wird der JavaScript-Thread inaktiv. Zu diesem Zeitpunkt durchsucht die JavaScript-Ausführungs-Engine die Warteschlange, findet das Ereignis, das in der Warteschlange „ausgelöst werden sollte“ und ruft dann den Handler (die Funktion) dieses Ereignisses auf. Nachdem der Prozessor die Ausführung abgeschlossen hat, kehrt er zur Warteschlange zurück und prüft das nächste Ereignis.
Single-Threaded-JavaScript funktioniert in Form einer Ereignisschleife durch Warteschlangen. Daher wird im vorherigen Code while verwendet, um die Ausführungs-Engine bis zu 1000 ms lang zu ziehen, während der Code ausgeführt wird, und es wird kein Ereignis ausgelöst, bis der gesamte Code abgeschlossen und in die Warteschlange zurückgekehrt ist. Dies ist der asynchrone Mechanismus von JavaScript.
Die Asynchronitätsschwierigkeiten von JavaScript
Asynchrone Vorgänge in JavaScript sind möglicherweise nicht immer einfach.
Ajax ist vielleicht die asynchrone Operation, die wir am häufigsten verwenden. Am Beispiel von jQuery sieht der Code zum Initiieren einer Ajax-Anfrage im Allgemeinen so aus:
// Ajax请求示意代码 $.ajax({ url: url, data: dataObject, success: function(){}, error: function(){} });
Ist an dieser Schreibweise etwas falsch? Einfach ausgedrückt ist es nicht tragbar genug. Warum müssen wir Erfolgs- und Fehlerrückrufe dort schreiben, wo die Anfrage initiiert wird? Wenn mein Rückruf viele, viele Dinge tun muss, muss ich dann hierher zurücklaufen und Code hinzufügen, wenn mir eine Sache einfällt?
Für ein weiteres Beispiel möchten wir so etwas vervollständigen: Es gibt 4 URL-Adressen für den Ajax-Zugriff. Nachdem der erste Zugriff abgeschlossen ist, müssen wir die erste verwenden Daten erhalten als Der zweite Parameter wird erneut zugegriffen, und dann wird auf den dritten zugegriffen, nachdem der zweite Zugriff abgeschlossen ist ... Von diesem Punkt an sind alle 4 Zugriffe abgeschlossen. Nach dieser Schreibweise scheint es so zu werden:
$.ajax({ url: url1, success: function(data){ $.ajax({ url: url2, data: data, success: function(data){ $.ajax({ //... }); } }); } })
Sie werden definitiv denken, dass dieser Code namens Pyramid of Doom so aussieht schrecklich. Wenn Sie es gewohnt sind, direkt angehängte Rückrufe zu schreiben, sind Sie möglicherweise verwirrt über die asynchronen Ereignisse, die von einem zum nächsten weitergegeben werden. Das separate Benennen und Speichern dieser Rückruffunktionen kann die Verschachtelung im Formular reduzieren und den Code klarer machen, löst das Problem jedoch immer noch nicht.
Eine weitere häufige Schwierigkeit besteht darin, zwei Ajax-Anfragen gleichzeitig zu senden und dann nach erfolgreicher Rückkehr beider Anfragen die nächste Aktion auszuführen Scheint es etwas schwierig zu sein, der anrufenden Position einen Rückruf zuzuordnen?
Promise eignet sich für den Umgang mit diesen asynchronen Vorgängen und ermöglicht Ihnen das Schreiben eleganterer Codes.
Versprechen kommt auf die Bühne
Was ist Versprechen? Nehmen wir weiterhin den vorherigen jQuery-Ajax-Anforderungssignalcode als Beispiel. Dieser Code kann tatsächlich so geschrieben werden:
var promise = $.ajax({ url: url, data: dataObject }); promise.done(function(){}); promise.fail(function(){});
Dies ähnelt dem vorherigen Ajax-Anfragesignal. Die Codes sind gleichwertig. Wie Sie sehen, ändert das Hinzufügen von Promise die Form des Codes. Ajax-Anfragen werden genau wie Variablenzuweisungen „gespeichert“. Das ist Kapselung, und die Kapselung wird asynchrone Ereignisse wirklich einfacher machen.
Kapselung ist nützlich
Das Promise-Objekt ist wie eine gekapselte Referenz auf ein asynchrones Ereignis. Möchten Sie nach Abschluss dieses asynchronen Ereignisses etwas unternehmen? Hängen Sie einfach Rückrufe an, egal wie viele Sie anhängen, es spielt keine Rolle!
Die Ajax-Methode von jQuery gibt ein Promise-Objekt zurück (dies ist eine wichtige Funktion, die in jQuery 1.5 hinzugefügt wurde). Wenn ich zwei Funktionen do1() und do2() habe, die ich ausführen möchte, nachdem das asynchrone Ereignis erfolgreich abgeschlossen wurde, muss ich nur Folgendes tun:
promise.done(do1); // Other code here. promise.done(do2);
Auf diese Weise ist es viel freier. Ich muss lediglich dieses Promise-Objekt speichern und jederzeit eine beliebige Anzahl von Rückrufen daran anhängen, während ich Code schreibe, unabhängig davon, wo das asynchrone Ereignis initiiert wird. Dies ist der Vorteil von Promise.
Formale Einführung
Promise ist für asynchrone Operationen so nützlich, dass es zu einer Spezifikation von CommonJS namens Promises/A entwickelt wurde. Promise stellt den Rückgabewert nach Abschluss einer Operation dar. Es hat drei Zustände:
Positiv (erfüllt oder gelöst), was angibt, dass die Promise-Operation erfolgreich war.
Eine Verneinung (abgelehnt oder fehlgeschlagen) zeigt an, dass der Promise-Vorgang fehlgeschlagen ist.
Warten (ausstehend), es wurde noch kein positives oder negatives Ergebnis erzielt, in Bearbeitung.
Darüber hinaus gibt es einen nominalen Zustand, der angibt, ob die Promise-Operation erfolgreich war oder fehlgeschlagen ist. Dabei handelt es sich um eine Sammlung positiver und negativer Zustände, die als „abgewickelt“ bezeichnet werden. Promise verfügt außerdem über die folgenden wichtigen Funktionen:
一个Promise只能从等待状态转变为肯定或否定状态一次,一旦转变为肯定或否定状态,就再也不会改变状态。
如果在一个Promise结束(成功或失败,同前面的说明)后,添加针对成功或失败的回调,则回调函数会立即执行。
想想Ajax操作,发起一个请求后,等待着,然后成功收到返回或出现错误(失败)。这是否和Promise相当一致?
进一步解释Promise的特性还有一个很好的例子:jQuery的$(document).ready(onReady)。其中onReady回调函数会在DOM就绪后执行,但有趣的是,如果在执行到这句代码之前,DOM就已经就绪了,那么onReady会立即执行,没有任何延迟(也就是说,是同步的)。
Promise示例
生成Promise
Promises/A里列出了一系列实现了Promise的JavaScript库,jQuery也在其中。下面是用jQuery生成Promise的代码:
var deferred = $.Deferred(); deferred.done(function(message){console.log("Done: " + message)}); deferred.resolve("morin"); // Done: morin
jQuery自己特意定义了名为Deferred的类,它实际上就是Promise。$.Deferred()方法会返回一个新生成的Promise实例。一方面,使用deferred.done()、deferred.fail()等为它附加回调,另一方面,调用deferred.resolve()或deferred.reject()来肯定或否定这个Promise,且可以向回调传递任意数据。
合并Promise
还记得我前文说的同时发送2个Ajax请求的难题吗?继续以jQuery为例,Promise将可以这样解决它:
var promise1 = $.ajax(url1), promise2 = $.ajax(url2), promiseCombined = $.when(promise1, promise2); promiseCombined.done(onDone);
$.when()方法可以合并多个Promise得到一个新的Promise,相当于在原多个Promise之间建立了AND(逻辑与)的关系,如果所有组成Promise都已成功,则令合并后的Promise也成功,如果有任意一个组成Promise失败,则立即令合并后的Promise失败。
级联Promise
再继续我前文的依次执行一系列异步任务的问题。它将用到Promise最为重要的.then()方法(在Promises/A规范中,也是用“有then()方法的对象”来定义Promise的)。代码如下:
var promise = $.ajax(url1); promise = promise.then(function(data){ return $.ajax(url2, data); }); promise = promise.then(function(data){ return $.ajax(url3, data); }); // ...
Promise的.then()方法的完整形式是.then(onDone, onFail, onProgress),这样看上去,它像是一个一次性就可以把各种回调都附加上去的简便方法(.done()、.fail()可以不用了)。没错,你的确可以这样使用,这是等效的。
但.then()方法还有它更为有用的功能。如同then这个单词本身的意义那样,它用来清晰地指明异步事件的前后关系:“先这个,然后(then)再那个”。这称为Promise的级联。
要级联Promise,需要注意的是,在传递给then()的回调函数中,一定要返回你想要的代表下一步任务的Promise(如上面代码的$.ajax(url2, data))。这样,前面被赋值的那个变量才会变成新的Promise。而如果then()的回调函数返回的不是Promise,则then()方法会返回最初的那个Promise。
应该会觉得有些难理解?从代码执行的角度上说,上面这段带有多个then()的代码其实还是被JavaScript引擎运行一遍就结束。但它就像是写好的舞台剧的剧本一样,读过一遍后,JavaScript引擎就会在未来的时刻,依次安排演员按照剧本来演出,而演出都是异步的。then()方法就是让你能写出异步剧本的笔。
将Promise用在基于回调函数的API
前文反复用到的$.ajax()方法会返回一个Promise对象,这其实只是jQuery特意提供的福利。实际情况是,大多数JavaScript API,包括Node.js中的原生函数,都基于回调函数,而不是基于Promise。这种情况下使用Promise会需要自行做一些加工。
这个加工其实比较简单和直接,下面是例子:
var deferred = $.Deferred(); setTimeout(deferred.resolve, 1000); deferred.done(onDone);
这样,将Promise的肯定或否定的触发器,作为API的回调传入,就变成了Promise的处理模式了。
Promise是怎么实现出来的?
本文写Promise写到这里,你发现了全都是基于已有的实现了Promise的库。那么,如果要自行构筑一个Promise的话呢?
位列于Promises/A的库列表第一位的Q可以算是最符合Promises/A规范且相当直观的实现。如果你想了解如何做出一个Promise,可以参考Q提供的设计模式解析。
限于篇幅,本文只介绍Promise的应用。我会在以后单独开一篇文章来详述Promise的实现细节。
作为JavaScript后续版本的ECMAScript 6将原生提供Promise,如果你想知道它的用法,推荐阅读JavaScript Promises: There and back again。
结语
Das Wort „Versprechen“ ist so hartnäckig, dass es nicht zur Übersetzung geeignet ist und die Bedeutung auf den ersten Blick unklar ist. Es kann jedoch tatsächlich eine erhebliche Hilfe bei der Ausführung komplexerer asynchroner Aufgaben in JavaScript sein.
Das Obige ist der Inhalt der Promise-Nutzung in der asynchronen JavaScript-Programmierung_node.js?1.1.5. Weitere verwandte Inhalte finden Sie auf der chinesischen PHP-Website (www.php.cn)!