We often write javascript without asynchronous operations. In the past, we used to nested callback functions to solve the problem that the latter asynchronous operation depended on the former asynchronous operation. Then, to solve the pain point of callback region, there are some solutions such as event subscribe/publish, event listening. Then came asynchronous solutions like Promise, Generator, async/await, and so on. The CO module uses the Promise automatic execution Generator. Async /await, the latest solution supported by default from Node7.6, also relies on promises, so understanding promises is essential and understanding the implementation principle behind them makes it easier to use them.
Implement a simple asynchronous scheme
We know that Promise to realize the multiple interdependent asynchronous operation is performed through. Then, we will not send question, the operation is how to know the front behind the asynchronous operation completed, we may have an idea, behind have a function in front has been listening to the asynchronous operation completed, it is as you say, the publish/subscribe pattern? The Promise implementation personally feels a bit like publish/subscribe, but it seems a bit complicated because of the chain calls to.then and the lack of an obvious subscribe/publish thing like on/emit
First we have an array of events to collect events, then subscribe to put events into the array via ON, and emit triggers the array of events. HMM, this isn’t too complicated. Once we understand this, we can start to really talk about the implementation.
Promise has an internal defers queue that holds the.then event. When the program starts executing, the.then event is put into the next event. Then, when the asynchronous operation completes, the resolve event is triggered. Then, and it’s actually pretty quick to come up with a solution where each asynchronous operation completes by triggering an event through resolve and removing the event from the event queue, and persisting the event through resolve of the event queue, We can implement this logic in a dozen lines of code, implementing a simple asynchronous programming scheme
function P(fn) { var value = null; var events = []; this.then = function(f) { events.push(f); return this; } function resolve(newValue) { var f = events.shift(); f(newValue, resolve); } fn(resolve); } function a() { return new P(function(resolve) { console.log("get..." ); setTimeout(function() { console.log("get 1"); resolve(1); }, 1000)}); } a().then(function(value, resolve) { console.log("get..." ); setTimeout(function() { console.log("get 2"); resolve(2); }, 1000) }).then(function(value, resolve) { console.log(value) })Copy the code
This results in the console as follows
get...
get 1
get...
get 2
2
Copy the code
We have no reject, no error handling. This is not complete. If readers want to expand, they can implement it by themselves
Briefly understand the implementation behind the Promise/A+ specification
Promise/A+ Spec: promisesaplus.com/
I learned from this article, Anatomy of the Promise Foundation, and the code used in the rest of this article is also from this article, so you can read it first to understand it better.
Let’s say we have a scenario where we need to asynchronously get the user ID, asynchronously get the user name from the user ID, get the output of the name, and quickly write out the Promise (MyPromise because it’s not a full implementation of Promise).
function getID() { return new MyPromise(function(resolve, reject) { console.log("get id..." ); setTimeout(function() { resolve("666"); }, 1000); }) } function getNameByID(id) { return new MyPromise(function(resolve, reject) { console.log(id); console.log("get name..." ); setTimeout(function() { resolve("hjm"); }, 1000); }) } getID().then(getNameByID).then(function(name) { console.log(name); }, function(err) { console.log(err); });Copy the code
The desired result is printed correctly, and the back FN takes the value of resolve
get id...
666
get name...
hjmCopy the code
The big question is how two promises are connected by dot then, a picture is worth a thousand words.
Orange: is what was generated when the initialization started (a bunch of.then)
Purple: a series of processes after asynchronous execution begins
It looks complicated at first glance, but let’s take it one step at a time
Forget about the purple one
Each Promise instance contains state, event queue defers, value, resolve, reject
There is also a handle that queues Defered{} (including onFulfilled, onRejected, resolve, and Reject) when the state is pending. OnFulfilled /onRejected and resolve will be returned when the state is fulfilled or Rejected
And then in order to implement serial promises, .then actually creates a new instance of a Promise as an intermediate Promise, This process will combine the then functions with its own instance resolve and reject to form a Defered{} (which includes onFulfilled, onRejected, resolve, and reject). It puts its own instance’s resolve, Reject, which will be the key to the serial Promise bridge (implemented through closures), using the Handle function to put this object into the event queue of the previous Promise instance
Asynchronous start!
Purple: a series of processes after asynchronous execution begins
Follow the label to see ~ if the thing in front of the words understand, you can see bottom of ~ ha ha
- GetID setTimeout 1000s Call resolve(” 666 “)
- The state of the current Promise instance changes (wait => complete) and the instance value(=>666)
- The current Handle function will be called, and since the state is depressing, the current value 666 will be passed into the corresponding function in the event queue (which will also return a Promise), and the getNameByID will start executing
- The resolve call determines whether a Promise is returned, and if so, calls the currently returned.then
- Then pass resolve and reject of the previous instance as onFulfilled and onRejected
- If you look closely at this line in the diagram, it’s a wonderful way to concatenate the promise returned from this event queue with the next. Then promise, which all refer to the same resolve, reject
- When the second asynchronous operation getNameByID setTimeout 1000s completes again, invoke instance resolve(” HJM “)
- The state of the current Promise instance changes (wait => complete), and the instance value(=> HJM)
- Call the current handle function, which will be fulfilled, and pass the current value HJM into the corresponding function in the event queue, which is actually the resolve(” HJM “) of the next intermediate Promise.
- The state of the current intermediate Promise instance changes (wait => complete), and the instance value(=> HJM)
- Call the current Handle function, and because the state is depressing, pass the current value HJM into the corresponding function in the event queue, print console.log(” HJM “), and successfully get the name
- Call resolve to find that the event queue is empty, and the program ends
The code address of this article is on github: github.com/BUPT-HJM/st…
Students who want to run their own can try, clear the whole process will be a lot clearer to Promise
Promise little test
These two questions are taken from the Are you Hungry Node-interview
Determine the output and the corresponding time
let doSth = new Promise((resolve, reject) => { console.log('hello'); resolve(); }); setTimeout(() => { doSth.then(() => { console.log('over'); })}, 10000);Copy the code
Determine the output order
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function executor(resolve) {
console.log(2);
for( var i=0 ; i<10000 ; i++ ) {
i == 9999 && resolve();
}
console.log(3);
}).then(function() {
console.log(4);
});
console.log(5);Copy the code
answer
In fact, these two problems can be solved with three tips
- The Promise function call executes
- In the Promise/A+ specification, then is placed at the end of the current event loop
- SetTimeout (fn,0) will appear in the next event loop
Here is a deep analysis, involving event loop, macro-task, micro-task and other things. I don’t know much about it personally, so I don’t want to make an in-depth analysis
Those who are interested can read: github.com/creeperyang…
Back to the problem, the first problem is tip1, so console.log(hello) immediately, and then over 10 seconds later
The second problem uses three tips, Promise to execute output 2, call resolve, then print 3, then call THEN and put output 4 at the end of the loop, then print 5, then reach the end, print 4, then the next event loop prints the first 1, so the order is 23541
The last
Thanks for reading ~ welcome to follow me haha github.com/BUPT-HJM welcome to continue sightseeing my blog ~
Welcome to attention
It can be reproduced and quoted freely, but the author must be signed and indicate the source of the article.