Asynchrone ?
J'ai vu le mot asynchrone (Asynchrone) à de nombreux endroits, mais quand je ne comprends pas bien ce concept, je trouve que je suis souvent considéré comme « déjà clair » (*  ̄? ̄ ).
Si vous vivez une situation similaire, cela n'a pas d'importance. Recherchez ce mot et vous pourrez obtenir une explication approximative. Ici, je vais donner une petite explication supplémentaire sur l’asynchronie de JavaScript.
Regardez ce code :
var start = new Date(); setTimeout(function(){ var end = new Date(); console.log("Time elapsed: ", end - start, "ms"); }, 500); while (new Date - start < 1000) {};
Après avoir exécuté ce code, vous obtiendrez un résultat comme Temps écoulé : 1013 ms. La fonction définie pour être exécutée 500 ms dans le futur par setTimeout() attend en fait plus de 1 000 ms avant de s'exécuter.
Comment expliquer ? Lorsque setTimeout() est appelé, un événement retardé est mis en file d'attente. Ensuite, continuez à exécuter le code après cela, et le code après cela, jusqu'à ce qu'il n'y ait plus de code. Lorsqu'il n'y a plus de code, le thread JavaScript devient inactif. À ce moment, le moteur d'exécution JavaScript parcourt la file d'attente, trouve l'événement qui « devrait être déclenché » dans la file d'attente, puis appelle le gestionnaire (fonction) de cet événement. Une fois l’exécution terminée, le processeur retourne dans la file d’attente et examine l’événement suivant.
JavaScript monothread fonctionne sous la forme d'une boucle d'événements à travers des files d'attente. Par conséquent, dans le code précédent, while est utilisé pour faire glisser le moteur d'exécution jusqu'à 1 000 ms pendant l'exécution du code, et aucun événement ne sera déclenché tant que tout le code n'est pas terminé et renvoyé dans la file d'attente. Il s'agit du mécanisme asynchrone de JavaScript.
Difficultés asynchrones de JavaScript
Les opérations asynchrones en JavaScript ne sont pas toujours simples.
Ajax est peut-être l'opération asynchrone que nous utilisons le plus. En prenant jQuery comme exemple, le code pour lancer une requête Ajax ressemble généralement à ceci :
// Ajax请求示意代码 $.ajax({ url: url, data: dataObject, success: function(){}, error: function(){} });
Y a-t-il quelque chose qui ne va pas avec cette façon d'écrire ? En termes simples, ce n'est pas assez portable. Pourquoi devons-nous écrire des rappels de réussite et d'erreur à l'endroit où la requête est initiée ? Si mon rappel doit faire beaucoup de choses, dois-je revenir ici et ajouter du code lorsque je pense à une chose ?
Pour un autre exemple, nous voulons compléter une telle chose : il y a 4 adresses URL pour l'accès Ajax. Nous devons d'abord accéder à la première via Ajax. Une fois le premier accès terminé, utilisez celle renvoyée. données obtenues en tant que Le deuxième paramètre est à nouveau accédé, puis le troisième est accédé une fois le deuxième accès terminé... À partir de ce moment, les 4 accès sont terminés. Selon cette façon d'écrire, cela semble devenir comme ceci :
$.ajax({ url: url1, success: function(data){ $.ajax({ url: url2, data: data, success: function(data){ $.ajax({ //... }); } }); } })
Vous penserez certainement que ce code appelé Pyramid of Doom ressemble à ça. terrible. Si vous avez l'habitude d'écrire des rappels directement attachés, vous pourriez vous sentir confus quant aux événements asynchrones transmis de l'un à l'autre. Nommer ces fonctions de rappel séparément et les stocker séparément peut réduire l'imbrication dans la forme et rendre le code plus clair, mais cela ne résout toujours pas le problème.
Une autre difficulté courante consiste à envoyer deux requêtes Ajax en même temps, puis à effectuer l'opération suivante une fois les deux requêtes renvoyées avec succès. Pensez-y si vous suivez simplement la méthode précédente pour effectuer l'opération suivante. cela semble un peu difficile d'attacher un rappel à la position appelante ?
Promise est adapté pour gérer ces opérations asynchrones et vous permet d'écrire du code plus élégant.
La promesse entre en scène
Qu'est-ce que la promesse ? Continuons à prendre le code de signal de requête jQuery Ajax précédent comme exemple. Ce code peut en fait être écrit comme ceci :
var promise = $.ajax({ url: url, data: dataObject }); promise.done(function(){}); promise.fail(function(){});
Ceci est similaire au précédent. Signal de requête Ajax Les codes sont équivalents. Comme vous pouvez le constater, l'ajout de Promise modifie la forme du code. Les requêtes Ajax sont "enregistrées" tout comme les affectations de variables. Il s'agit de l'encapsulation, et l'encapsulation facilitera véritablement les événements asynchrones.
L'encapsulation est utile
L'objet Promise est comme une référence encapsulée à un événement asynchrone. Voulez-vous faire quelque chose une fois cet événement asynchrone terminé ? Attachez-y simplement des rappels, peu importe le nombre que vous y attachez, cela n'a pas d'importance !
La méthode Ajax de jQuery renverra un objet Promise (il s'agit d'une fonctionnalité clé ajoutée dans jQuery 1.5). Si j'ai deux fonctions do1() et do2() que je souhaite exécuter une fois l'événement asynchrone terminé avec succès, il me suffit de faire ceci :
promise.done(do1); // Other code here. promise.done(do2);
De cette façon, c'est beaucoup plus gratuit. Il me suffit de sauvegarder cet objet Promise et d'y attacher un nombre quelconque de rappels à tout moment lors de l'écriture du code, quel que soit l'endroit où l'événement asynchrone est initié. C'est l'avantage de Promise.
Introduction formelle
Promise est si utile pour les opérations asynchrones qu'elle a été développée dans une spécification de CommonJS appelée Promises/A. Promise représente la valeur de retour une fois une opération terminée. Elle a trois états :
Positif (réalisé ou résolu), indiquant que l'opération Promise a réussi.
La négation (rejetée ou échouée) indique que l'opération Promise a échoué.
En attente (en attente), aucun résultat positif ou négatif n'a encore été obtenu, en cours.
De plus, il existe un état nominal utilisé pour indiquer que l'opération Promise a réussi ou échoué, qui est un ensemble d'états positifs et négatifs, appelés réglés. Promise possède également les fonctionnalités importantes suivantes :
一个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。
结语
Le mot Promesse est si têtu qu'il ne convient pas à la traduction, et le sens ne sera pas clair en un coup d'œil. Cependant, il peut en effet apporter une aide considérable lors de l’exécution de tâches asynchrones plus complexes en JavaScript.
Ce qui précède est le contenu de l'utilisation de Promise dans la programmation JavaScript asynchrone_node.js?1.1.5. Pour plus de contenu connexe, veuillez faire attention au site Web PHP chinois (www.php.cn) !