Preface
The first time I came into contact with Promise was when Microsoft released the Windows 8 operating system in 2012 and studied using html5 to write Metro applications with a curious attitude. At that time, the asynchronous interfaces in the WinJS library provided with html5 were all in Promise form, which was simply a book of heaven for me who had just graduated from javascript at that time. What I was thinking at the time was that Microsoft was tinkering with it again.
Unexpectedly, by 2015, Promise was actually written into the ES6 standard. Moreover, a survey shows that js programmers use this thing quite high.
Ironically, as Microsoft, which widely used Promise in Metro application development interface as early as 2012, its own browser IE still did not support Promise until it died in 2015. It seems that Microsoft does not have this technology, but has really given up treatment for IE. . .
Looking back now, the most troublesome thing about seeing Promise at that time was that beginners seem incredible and is also the most widely praised feature by js programmers: then function call chain.
Then function call chain, essentially, is to call multiple asynchronous processes in sequence. This article starts from this point and studies and learns the feature of Promise.
Promise solved
Consider the following scenario, after the function delays by 2 seconds, print a line of logs, then delays by 3 seconds, and then delays by 4 seconds, print a line of logs. This is a very simple thing in other programming languages, but it is more difficult to get into JS, and the code will probably be written as follows:
var myfunc = function() { setTimeout(function() { console.log("log1"); setTimeout(function() { console.log("log2"); setTimeout(function() { console.log("log3"); }, 4000); }, 3000); }, 2000);}Due to the nested multi-layer callback structure, a typical pyramid structure is formed here. If the business logic is more complicated, it will become a terrifying callback hell.
If you have better awareness and know how to extract simple functions, then the code will look like this:
var func1 = function() { setTimeout(func2, 2000);};var func2 = function() { console.log("log1"); setTimeout(func3, 3000);};var func3 = function() { console.log("log2"); setTimeout(func4, 4000);};var func4 = function() { console.log("log3");};This looks a little better, but it always feels a little weird. . . Well, in fact, my js level is limited, so I can’t say why I can’t write this well. If you know why this is not good and so you invented Promise, please let me know.
Now let's get back to the point and talk about the Promise thing.
Description of Promise
Please allow me to quote MDN's description of Promise here:
The Promise object is used for deferred calculations and asynchronous calculations. A Promise object represents an operation that has not been completed but is expected to be completed in the future.
The Promise object is a proxy for the return value, which may not be known when the promise object is created. It allows you to specify a handling method for success or failure of an asynchronous operation. This allows an asynchronous method to return a value like a synchronous method: the asynchronous method returns a promise object containing the original return value instead of the original return value.
The Promise object has the following states:
•pending: Initial state, non-fulfilled or rejected.
•fulfilled: Successful operation.
•rejected: Failed operation.
The promise object with a pending state can be converted to a fulfilled state with a success value or a rejected state with a failure message. When the state transitions, the method bound to promise.then (function handle) will be called. (When binding a method, if the promise object is already in the fulfilled or rejected state, the corresponding method will be called immediately, so there is no race condition between the completion of the asynchronous operation and its binding method.)
For more descriptions and examples of Promise, please refer to the Promise entry of MDN or the Promise entry of MSDN.
Try to solve our problem with Promise
Based on the above understanding of Promise, we know that we can use it to solve the problem that the code behind nested multi-layer callbacks is stupid and difficult to maintain. The two links given above are already very clear about the syntax and parameters of Promise. I will not repeat them here, just upload the code.
Let's first try a relatively simple case, which only executes delays and callbacks once:
new Promise(function(res, rej) { console.log(Date.now() + " start setTimeout"); setTimeout(res, 2000);}).then(function() { console.log(Date.now() + " timeout call back");});It seems that there is no difference from the examples in MSDN, and the execution result is as follows:
$ node promisTest.js1450194136374 start setTimeout1450194138391 timeout call back
So if we want to do another delay, then I can write this:
new Promise(function(res, rej) { console.log(Date.now() + " start setTimeout 1"); setTimeout(res, 2000);}).then(function() { console.log(Date.now() + " timeout 1 call back"); new Promise(function(res, rej) { console.log(Date.now() + " start setTimeout 2"); setTimeout(res, 3000); }).then(function() { console.log(Date.now() + " timeout 2 call back"); })});It seems to work correctly as well:
$ node promisTest.js1450194338710 start setTimeout 11450194340720 timeout 1 call back1450194340720 start setTimeout 21450194343722 timeout 2 call back
But the code looks stupid and cute, right? It’s vaguely building a pyramid again. This goes against the purpose of introducing Promise.
So what's the problem? What is the correct posture?
The answer is hidden in the return value of the then function and the then function's onFulfilled (or onCompleted) callback function.
First of all, the then function will return a new Promise variable, and you can call the then function of this new Promise variable again, like this:
new Promise(...).then(...) .then(...).then(...).then(...).then(...).
What kind of Promies is returned by the then function depends on the return value of the onFulfilled callback.
In fact, onFulfilled can return a normal variable or another Promise variable.
If onFulfilled returns a normal value, then the function will return a default Promise variable. Executing the then function of this Promise will make the Promise satisfy immediately, and the onFulfilled function is executed, and the onFulfilled entry parameter is the return value of the previous onFulfilled.
If onFulfilled returns a Promise variable, that Promise variable will be used as the return value of the then function.
The documents on MDN and MSDN do not have a clear positive description of this series of settings for the then function and the onFulfilled function. As for the official ES6 document ECMAScript 2015 (6th Edition, ECMA-262). . . I really can't understand my level. If any expert can explain the description of the two return values in the official document, please leave a message for advice! ! !
So the above is my free play, and the language organization is a bit difficult to describe. You will understand after reading the code.
First, the case of returning normal variables:
new Promise(function(res, rej) { console.log(Date.now() + " start setTimeout 1"); setTimeout(res, 2000);}).then(function() { console.log(Date.now() + " timeout 1 call back"); return 1024;}).then(function(arg) { console.log(Date.now() + " last onFulfilled return " + arg); });The above code execution result is:
$ node promisTest.js1450277122125 start setTimeout 11450277124129 timeout 1 call back1450277124129 last onFulfilled return 1024
It's a little interesting, right, but that's not the key. The key is that the onFulfilled function returns a Promise variable, which makes it convenient for us to call multiple asynchronous processes in succession. For example, we can try to do two delay operations in succession:
new Promise(function(res, rej) { console.log(Date.now() + " start setTimeout 1"); setTimeout(res, 2000);}).then(function() { console.log(Date.now() + " timeout 1 call back"); return new Promise(function(res, rej) { console.log(Date.now() + " start setTimeout 2"); setTimeout(res, 3000); });}).then(function() { console.log(Date.now() + " timeout 2 call back");});The execution results are as follows:
$ node promisTest.js1450277510275 start setTimeout 11450277512276 timeout 1 call back1450277512276 start setTimeout 21450277515327 timeout 2 call back
If you think this is nothing great, then it's not a problem to do it a few more times:
new Promise(function(res, rej) { console.log(Date.now() + " start setTimeout 1"); setTimeout(res, 2000);}).then(function() { console.log(Date.now() + " timeout 1 call back"); return new Promise(function(res, rej) { console.log(Date.now() + " start setTimeout 2"); setTimeout(res, 3000); });}).then(function() { console.log(Date.now() + " timeout 2 call back"); return new Promise(function(res, rej) { console.log(Date.now() + " start setTimeout 3"); setTimeout(res, 4000); });});}).then(function() { console.log(Date.now() + " timeout 3 call back"); return new Promise(function(res, rej) { console.log(Date.now() + " start setTimeout 4"); setTimeout(res, 5000); });});}).then(function() { console.log(Date.now() + " timeout 4 call back");});$ node promisTest.js1450277902714 start setTimeout 11450277904722 timeout 1 call back1450277904724 start setTimeout 21450277907725 timeout 2 call back1450277907725 start setTimeout 31450277911730 timeout 3 call back1450277911730 start setTimeout 41450277916744 timeout 4 call back
It can be seen that multiple delayed callback functions are arranged in an orderly manner, and there is no popular pyramid-like structure. Although the code calls asynchronous processes, it looks like they are all composed of synchronous processes. This is the benefit Promise brings us.
If you have the good habit of distilling verbose code into separate functions, it will be even more beautiful:
function timeout1() { return new Promise(function(res, rej) { console.log(Date.now() + " start timeout1"); setTimeout(res, 2000); });}function timeout2() { return new Promise(function(res, rej) { console.log(Date.now() + " start timeout2"); setTimeout(res, 3000); });}function timeout3() { return new Promise(function(res, rej) { console.log(Date.now() + " start timeout2"); setTimeout(res, 3000); });}function timeout3() { return new Promise(function(res, rej) { console.log(Date.now() + " start timeout3"); setTimeout(res, 4000); });}function timeout4() { return new Promise(function(res, rej) { console.log(Date.now() + " start timeout4"); setTimeout(res, 5000); });}timeout1() .then(timeout2) .then(timeout3) .then(timeout4) .then(function() { console.log(Date.now() + " timout4 callback"); });$ node promisTest.js1450278983342 start timeout11450278985343 start timeout2145027898351 start timeout31450278992356 start timeout41450278997370 timout4 callback
Next, we can continue to study the problem of passing in the incoming parameters of the onFulfilled function.
We already know that if the previous onFulfilled function returns a normal value, then this value is the entry parameter of the onFulfilled function; then if the previous onFulfilled returns a Promise variable, where does the entry parameter of the onFulfilled come from?
The answer is that the entry parameter of this onFulfilled function is the value passed in when the resolve function was called in the previous Promise.
I couldn't accept the jump for a while, right? Let's do it well.
First of all, what is the function Promise.resolve? Using the statement from Zou Zou above MDN
Solve a Promise object with the success value value. If the value is continuable (thenable, i.e. with the then method), the returned Promise object will "follow" the value
In short, this is the callback when the asynchronous call is successful.
Let's take a look at what the callback looks like in a normal asynchronous interface. Take fs.readFile(file[, options], callback) on nodejs for example. Its typical call example is as follows
fs.readFile('/etc/passwd', function (err, data) { if (err) throw err; console.log(data);});Because for the fs.readFile function, whether successful or failed, it will call the callback function callback, so this callback accepts two parameters, namely the exception description on failure err and the return result data on success.
So if we use Promise to reconstruct this example of reading file, how should we write it?
First, encapsulate the fs.readFile function:
function readFile(fileName) { return new Promise(function(resolve, reject) { fs.readFile(fileName, function (err, data) { if (err) { reject(err); } else { resolve(data); } }); });});}The second is the call:
readFile('theFile.txt').then( function(data) { console.log(data); }, function(err) { throw err; } );Imagine where the content of the file is usually placed in the synchronous call interface of reading files in other languages? Is the function return value correct? The answer is out, what is the entry ginseng of this resolve? It is the return value when the asynchronous call is successful.
With this concept, it is not difficult to understand the "input parameter of the onFulfilled function is the value passed in when calling the resolve function in the previous Promise". Because the onFulfilled task is to process the result after the previous asynchronous call is successful.
Alas finally straightened out. . .
Summarize
Please allow me to use a piece of code to summarize the key points explained in this article:
function callp1() { console.log(Date.now() + " start callp1"); return new Promise(function(res, rej) { setTimeout(res, 2000); });}function callp2() { console.log(Date.now() + " start callp2"); return new Promise(function(res, rej) { setTimeout(function() { res({arg1: 4, arg2: "arg2 value"}); }, 3000); });}function callp3(arg) { console.log(Date.now()) + " start callp3 with arg = " + arg); return new Promise(function(res, rej) { setTimeout(function() { res("callp3"); }, arg * 1000); });}callp1().then(function() { console.log(Date.now() + " callp1 return"); return callp2();}).then(function(ret) { console.log(Date.now() + " callp2 return with ret value = " + JSON.stringify(ret)); return callp3(ret.arg1);}).then(function(ret) { console.log(Date.now() + " callp3 return with ret value = " + ret);}) $ node promisTest.js1450191479575 start callp11450191481597 callp1 return1450191481599 start callp21450191484605 callp2 return with ret value = {"arg1":4,"arg2":"arg2 value"}1450191484605 start callp3 with arg = 41450191488610 callp3 return with ret value = callp3The above simple learning experience of using Promise to solve multi-layer asynchronous calls is all the content I share with you. I hope you can give you a reference and I hope you can support Wulin.com more.