Asynchronous?
I have seen the word asynchronous (Asynchronous) in many places, but when I don’t fully understand this concept, I find that I am often regarded as “already clear” (*  ̄? ̄).
If you have a similar situation, it doesn’t matter. Search this word and you can get a rough explanation. Here, I’ll give a little extra explanation of JavaScript’s asynchrony.
Look at this code:
var start = new Date(); setTimeout(function(){ var end = new Date(); console.log("Time elapsed: ", end - start, "ms"); }, 500); while (new Date - start < 1000) {};
After running this code, you will get a result like Time elapsed: 1013ms. The function set to be executed 500ms in the future by setTimeout() actually waits for more than 1000ms before executing.
How to explain? When setTimeout() is called, a delayed event is queued. Then, continue executing the code after this, and the code after that, until there is no more code. After there is no code, the JavaScript thread becomes idle. At this time, the JavaScript execution engine looks through the queue, finds the event that "should be triggered" in the queue, and then calls the handler (function) of this event. After the processor completes execution, it returns to the queue and looks at the next event.
Single-threaded JavaScript works in the form of an event loop through queues. Therefore, in the previous code, while is used to drag the execution engine for up to 1000ms while the code is running, and no event will be triggered until all the code is completed and returned to the queue. This is the asynchronous mechanism of JavaScript.
JavaScript’s Async Difficulties
Asynchronous operations in JavaScript may not always be straightforward.
Ajax is perhaps the asynchronous operation we use most. Taking jQuery as an example, the code to initiate an Ajax request generally looks like this:
// Ajax请求示意代码 $.ajax({ url: url, data: dataObject, success: function(){}, error: function(){} });
Is there anything wrong with this way of writing? Simply put, it's not portable enough. Why do we have to write success and error callbacks where the request is initiated? If my callback has to do many, many things, do I need to run back here and add code when I think of one thing?
For another example, we want to complete such a thing: there are 4 url addresses for Ajax access. We need to access the first one through Ajax first. After the first access is completed, use the returned data obtained as The second parameter is accessed again, and then the third one is accessed after the second access is completed... From this point on, all 4 accesses are completed. According to this way of writing, it seems to become like this:
$.ajax({ url: url1, success: function(data){ $.ajax({ url: url2, data: data, success: function(data){ $.ajax({ //... }); } }); } })
You will definitely think that this code called Pyramid of Doom looks like It looks terrible. If you are used to writing directly attached callbacks, you may feel confused about the asynchronous events that are passed from one to the next. Naming these callback functions separately and storing them separately can reduce nesting in form and make the code clearer, but it still does not solve the problem.
Another common difficulty is to send two Ajax requests at the same time, and then do the next thing after both requests return successfully. Think about it if you just follow the previous method to do the next thing. Does it seem a bit difficult to attach a callback to the calling position?
Promise is suitable for dealing with these asynchronous operations and allows you to write more elegant code.
Promise comes on stage
What is Promise? Let’s continue taking the previous jQuery Ajax request signal code as an example. That code can actually be written like this:
var promise = $.ajax({ url: url, data: dataObject }); promise.done(function(){}); promise.fail(function(){});
This is similar to the previous Ajax request signal. The codes are equivalent. As you can see, the addition of Promise changes the form of the code. Ajax requests are "saved" just like variable assignments. This is encapsulation, and encapsulation will truly make asynchronous events easier.
Encapsulation is useful
The Promise object is like an encapsulated reference to an asynchronous event. Want to do something after this async event completes? Just attach callbacks to it, no matter how many you attach, it doesn’t matter!
jQuery’s Ajax method will return a Promise object (this is a key feature added in jQuery 1.5). If I have two functions do1() and do2() that I want to execute after the asynchronous event is successfully completed, I only need to do this:
promise.done(do1); // Other code here. promise.done(do2);
This way It's much more free. I just need to save this Promise object and attach any number of callbacks to it at any time while writing code, regardless of where the asynchronous event is initiated. This is the advantage of Promise.
Formal introduction
Promise is so useful for asynchronous operations that it has been developed into a specification of CommonJS called Promises/A. Promise represents the return value after an operation is completed. It has three states:
Positive (fulfilled or resolved), indicating that the Promise operation was successful.
Negation (rejected or failed) indicates that the Promise operation failed.
Waiting (pending), no positive or negative result has been obtained yet, in progress.
In addition, there is a nominal state used to indicate that the Promise operation has succeeded or failed, which is a collection of positive and negative states, called settled. Promise also has the following important features:
一个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。
结语
The word Promise is so stubborn that it is not suitable for translation, and the meaning will be unclear at a glance. However, it can indeed provide considerable help when doing more complex asynchronous tasks in JavaScript.
The above is the content of Promise usage in asynchronous JavaScript programming_node.js?1.1.5. For more related content, please pay attention to the PHP Chinese website (www.php.cn)!