This article mainly popularizes the usage of promises.
For a long time, JavaScript has always handled asynchronously in the form of callback, and the callback mechanism in the field of front-end development is almost deeply rooted in the hearts of the people. When designing APIs, whether it is a browser manufacturer, SDK developer, or author of various library, they basically follow the callback routine.
In recent years, with the gradual maturity of the JavaScript development model, the CommonJS specification has been born, including the proposal of the Promise specification. Promise has completely changed the writing of js asynchronous programming, making asynchronous programming very easy to understand.
In the callback model, we assume that an asynchronous queue needs to be executed, and the code may look like this:
loadImg('a.jpg', function() { loadImg('b.jpg', function() { loadImg('c.jpg', function() { console.log('all done!'); }); }); });This is what we often callback pyramid. When there are many asynchronous tasks, maintaining a large number of callbacks will be a disaster. Nowadays, Node.js is very popular. It seems that many teams want to use it to make something to get along with the "fashion". Once chatting with an operation and maintenance classmate, they also planned to use Node.js to do something, but when they think of the layers of callbacks of js, they are discouraged.
OK, the nonsense is over, let’s get to the topic.
Promise may be familiar with everyone, because the Promise specification has been out for a long time, and Promise has been included in ES6, and the higher versions of Chrome and Firefox browsers have implemented Promise natively, but there are fewer APIs than the popular Promise class library nowadays.
The so-called Promise can be literally understood as "promise", which means that A calls B, B returns a "promise" to A, and then A can write this when writing the plan: When B returns the result to me, A executes plan S1. On the contrary, if B does not give A the desired result for some reason, then A executes emergency plan S2, so that all potential risks are within A's controllable range.
The above sentence is translated into code similar to:
var resB = B();var runA = function() { resB.then(execS1, execS2);};runA();Just looking at the above line of code, it seems that there is nothing special. But the reality may be much more complicated than this. To accomplish one thing, A may rely on the response of more than one person B. It may require asking multiple people at the same time and then implementing the next plan after receiving all the answers. The final translation into code might look like this:
var resB = B();var resC = C();...var runA = function() { reqB .then(resC, execS2) .then(resD, execS3) .then(resE, execS4) ... .then(execS1);};runA();Here, different processing mechanisms are used when each inquired responds that are not in line with expectations. In fact, the Promise specification does not require this, and you can even do nothing (i.e., not pass in the second parameter of then) or handle it uniformly.
OK, let’s get to know the Promise/A+ specifications:
then method (it can be said that then is the core of promise), and then must return a promise. Then of the same promise can be called multiple times, and the execution order of the callbacks is consistent with the order when they are definedAs you can see, there is not much content in the Promise specification, so you can try to implement the following Promise yourself.
The following is a simple implementation of a Promise that I have referenced many Promise libraries. Please move to promiseA in the code.
A brief analysis of the ideas:
The constructor Promise accepts a function resolver , which can be understood as passing an asynchronous task. The resolver accepts two parameters, one is a callback when successful and the other is a callback when failure. These two parameters are equal to the parameters passed through then.
The second is the implementation of then. Since the Promise requires that then must return a promise, a new promise will be generated when then called, which will be hung on _next of the current promise. Multiple calls of the same promise will only return the previously generated _next .
Since the two parameters accepted by the then method are optional and there is no restriction on the type, it can be a function, a specific value, or another promise. Here is the specific implementation of then:
Promise.prototype.then = function(resolve, reject) { var next = this._next || (this._next = Promise()); var status = this.status; var x; if('pending' === status) { isFn(resolve) && this._resolves.push(resolve); isFn(reject) && this._rejects.push(reject); return next; } if('resolved' === status) { if(!isFn(resolve)) { next.resolve(resolve); } else { try { x = resolve(this.value); resolveX(next, x); } catch(e) { this.reject(e); } } return next; } if('rejected' === status) { if(!isFn(reject)) { next.reject(reject); } else { try { x = reject(this.reason); resolveX(next, x); } catch(e) { this.reject(e); } } return next; }};Here, then has simplified the implementation of other promise class libraries, and the implementation is much more complex than this, and it also has more functions. For example, there is a third parameter - notify, which indicates the current progress of promise, which is very useful when uploading design files, etc. The processing of various parameters of then is the most complicated part. Interested students can refer to the implementation of other types of Promise libraries.
On the basis of then, at least two methods should be needed, namely, to complete the conversion of the state of the promise from pending to resolved or rejected, and to execute the corresponding callback queue, namely resolve() and reject() methods.
At this point, a simple promise has been designed. The following are simple implementations of the following two promised functions:
function sleep(ms) { return function(v) { var p = Promise(); setTimeout(function() { p.resolve(v); }, ms); return p; };}; function getImg(url) { var p = Promise(); var img = new Image(); img.onload = function() { p.resolve(this); }; img.onerror = function(err) { p.reject(err); }; img.url = url; return p;}; Since the Promise constructor accepts an asynchronous task as a parameter, getImg can also be called like this:
function getImg(url) { return Promise(function(resolve, reject) { var img = new Image(); img.onload = function() { resolve(this); }; img.onerror = function(err) { reject(err); }; img.url = url; });};Next (the moment of witnessing the miracle), suppose there is a BT requirement to implement this: get a json configuration asynchronously, parse the json data and get the pictures inside, and then load the pictures in sequence, and give a loading effect when no picture is loaded.
function addImg(img) { $('#list').find('> li:last-child').html('').append(img);};function prepend() { $('<li>') .html('loading...') .appendTo($('#list'));};function run() { $('#done').hide(); getData('map.json') .then(function(data) { $('h4').html(data.name); return data.list.reduce(function(promise, item) { return promise .then(prepend) .then(sleep(1000)) .then(function() { return getImg(item.url); }) .then(addImg); }, Promise.resolve()); }) .then(sleep(300)) .then(function() { $('#done').show(); });};$('#run').on('click', run);The sleep here is just added to see the effect, you can click to view the demo! Of course, the Node.js example can be viewed here.
Here, the static method of Promise.resolve(v) simply returns a promise with v as a positive result. V can not be passed in, or it can be a function or an object or function containing then method (i.e. thenable).
Similar static methods include Promise.cast(promise) , which generates a promise with promise as a positive result;
Promise.reject(reason) generates a promise with reason as the negative result.
Our actual usage scenarios may be very complex, and often require multiple asynchronous tasks to be executed interspersedly, parallel or serially. At this time, you can make various extensions to Promise, such as implementing Promise.all() , accepting the promises queue and waiting for them to complete before continuing, and for example, Promise.any() , when any of the promises queues is in the completion state, the next operation will be triggered.
You can refer to this article in html5rocks JavaScript Promises. Currently, advanced browsers such as chrome and firefox have built-in Promise objects, providing more operation interfaces, such as Promise.all() , which supports passing in a promise array, and then executes when all promises are completed. There is also a more friendly and powerful exception capture, which should be enough to deal with daily asynchronous programming.
The most popular js libraries today have implemented Promise to varying degrees, such as dojo, jQuery, Zepto, when.js, Q, etc., but most of the exposed objects Deferred . Taking jQuery (Zepto similar) as an example, implement the above getImg() :
function getImg(url) { var def = $.Deferred(); var img = new Image(); img.onload = function() { def.resolve(this); }; img.onerror = function(err) { def.reject(err); }; img.src = url; return def.promise();}; Of course, in jQuery, many operations return Deferred or promise, such as animate and ajax :
// ajax$.ajax(opacity': 0}, 1000) .promise() .then(function() { console.log('done'); });// ajax$.ajax(options).then(success, fail);$.ajax(options).done(success).fail(fail);// ajax queue$.when($.ajax(options1), $.ajax(options2)) .then(function() { console.log('all done.'); }, function() { console.error('There something wrong.'); }); jQuery also implements done() and fail() methods, which are actually shortcuts of the then method.
To handle promises queues, jQuery implements $.when() method, and its usage is similar to Promise.all() .
For other class libraries, it is worth mentioning here that when.js has little code, it fully implements Promise, supports browser and Node.js, and provides richer APIs, which is a good choice. Due to space limitations, we will not expand it anymore.
We see that no matter how complex the implementation of Promise is, its usage is very simple and the organizational code is very clear. From now on, there is no need to be tortured by callback.
Finally, the Promise is so elegant! But Promise only solves the problem of deep nesting of callbacks. It is the Generator that really simplifies JavaScript asynchronous programming. On the Node.js side, it is recommended to consider Generator.
Next article, study Generator.
github original text: https://github.com/chemdemo/chemdemo.github.io/issues/6