Familiar with the Promise of
Say Promise presumably everyone is not unfamiliar with it, prop up the banner of JS asynchronous call. A Promise is a result that holds the result of an event (called asynchronously) that will happen in the future
Take a look at the basic use of Promise
const p1 = new Promise((resolve, reject) = > {
console.log('Code that just enters the Promise and executes immediately')
if(true) {
resolve('nice !! ')}else {
reject('some Error')
}
})
p1.then(val= > {
console.log(val)
}, err= > {
console.log(err)
})
Copy the code
A Promise takes a function as an argument, This function has two parameters: resolve, reject. Resolve is to change the state of the Promise from pedding to fulfilled. Reject is to change the state of the Promise from pedding to fulfilled Let’s look at an example
const p2 = new Promise((resolve, reject) = > {
console.log('enter the Promise')
reject('error 1')
resolve('resolve 1')
resolve('resolve 2')
}).then(res= > {
console.log(res, 'then1')
}).then(res= > {
console.log(res, 'then2')
}).catch(err= > {
console.log(err)
})
Copy the code
The output of this example from the console is as follows
The reason is that the state of a Promise is irreversible once it is resolved or rejected
So let’s go back to the code abovereject('error 1')
Take it out and see what happensIt’s not surprising that then1 is undefined because then2 is undefined because the argument to the then method requires the result of the return in the previous THEN method, for example
How did the Promise come true
So let’s look at how promises are implemented internally (the following source code uses Promise A+). When A Promise is used, it is A new Promise. Obviously, A Promise is A constructor. Let’s write a simple Promise example to plug into the source code and see what happens
const p3 = new Promise((resolve, reject) = > {
console.log('Just entered the Promise')
resolve('resolve 1')
resolve('resolve 2')
reject('reject error')
}).then(res= > {
console.log(res, 'then1')
return 'then1 result'
}, err= > {
console.log(err, 'then1 error')
}).then(res= > {
console.log(res, 'then2')})Copy the code
The first thing I see is the Promise constructor, an input, fn, which is the body of the function that we pass the New Promise to, and I skip two judgments and you see that in the Promise constructor, the state of the Promise is initialized, and I’ve marked the comment in the code
function Promise(fn) {
if (typeof this! = ='object') {
throw new TypeError('Promises must be constructed via new');
}
if (typeoffn ! = ='function') {
throw new TypeError('Promise constructor\'s argument is not a function');
}
this._deferredState = 0; // The state of the next call to the Promise tag is used in subsequent calls to the then method chain
this._state = 0; // Promise state 0 pedding; 1 fulfilled; 2 rejected;
this._value = null; The value of / / Promise
this._deferreds = null; // For the next call, the Promise instance is used in subsequent calls to the then method chain
if (fn === noop) return; // The noop function is an empty function that determines if the Promise is a new Promise in the then method. If the Promise is a new Promise in the THEN method, no subsequent direct return is executed
doResolve(fn, this);
}
Copy the code
doResolve
There’s not a lot of things that we do in constructors, some judgments and some initialization, so let’s move on, let’s talk about parameters,
- The first argument, fn, is the body of the function we pass in to the New Promise,
- The second argument, this, is an instance of the current Promise.
function doResolve(fn, promise) {
var done = false; // Initialize a marked variable
var res = tryCallTwo(fn, function (value) {
if (done) return;
done = true;
resolve(promise, value);
}, function (reason) {
if (done) return;
done = true;
reject(promise, reason);
});
if(! done && res === IS_ERROR) { done =true; reject(promise, LAST_ERROR); }}Copy the code
DoResolve () calls tryCallTwo with three arguments
- The first is the body of the function passed the New Promise
- The second argument is a function that executes the resolve method
- The third argument is a function that executes the reject method
tryCallTwo
Let’s look at tryCallTwo
function tryCallTwo(fn, a, b) {
try {
fn(a, b);
} catch (ex) {
LAST_ERROR = ex;
returnIS_ERROR; }}Copy the code
He just executes fn with the two arguments a and b as the two arguments of fn, so a and B are the same as resolve and reject in the body of our new Promise function, and when we execute resolve or reject in the body of our new Promise function, So let’s just do this function a and b and let’s plug in our example
const p3 = new Promise((resolve, reject) = > {
console.log('Just entered the Promise')
resolve('resolve 1')
resolve('resolve 2')
reject('reject error')})function doResolve(fn, promise) {
var done = false; // Initialize a marked variable
var res = tryCallTwo(fn, function (value) { // value => 'resolve 1'
if (done) return; // On the first call done is false
done = true; // If done is set to true after the first call, a direct return of resolve or reject does not take effect
resolve(promise, value);
}, function (reason) {
if (done) return;
done = true; // For the same reason as above
reject(promise, reason);
});
if(! done && res === IS_ERROR) {// Reject an exception
done = true; reject(promise, LAST_ERROR); }}Copy the code
That’s why the Promise’s state is irreversible and what happens next
resolve
- The first parameter is an instance of the current Promise
- The second parameter is the value of the current Promise
function resolve(self, newValue) { // newValue => 'resolve 1'
if (newValue === self) {
return reject(
self,
new TypeError('A promise cannot be resolved with itself.')); }if (
newValue &&
(typeof newValue === 'object' || typeof newValue === 'function')) {var then = getThen(newValue);
if (then === IS_ERROR) {
return reject(self, LAST_ERROR);
}
if (
then === self.then &&
newValue instanceof Promise
) {
self._state = 3;
self._value = newValue;
finale(self);
return;
} else if (typeof then === 'function') {
doResolve(then.bind(newValue), self);
return;
}
}
self._state = 1;
self._value = newValue;
finale(self);
}
Copy the code
It is clear that our newValue is currently equal to the string ‘resolve 1’, and that neither of the two judgments is consistent with assigning either _state or _value directly to the current Promise instance. Next, perform finale
finale
The instance of the current Promise has a _state of 1, _value of ‘resolve 1’, _deferreds of NULL, and _deferredState of 0, so both judgments are skipped
function finale(self) {
if (self._deferredState === 1) {
handle(self, self._deferreds);
self._deferreds = null;
}
if (self._deferredState === 2) {
for (var i = 0; i < self._deferreds.length; i++) {
handle(self, self._deferreds[i]);
}
self._deferreds = null; }}Copy the code
then
At this point, the Promise has been executed, but isn’t that a Promise a microtask? So far we haven’t seen anything to do with microtasks, so what about the execution of the then method
const p3 = new Promise((resolve, reject) = > {
console.log('Just entered the Promise')
resolve('resolve 1')
}).then(res= > {
console.log(res, 'then1')
return 'then1 result'
}, err= > {
console.log(err, 'then1 error')})Copy the code
function noop() {} // An empty function declared at the top of the source code
Promise.prototype.then = function(onFulfilled, onRejected) {
if (this.constructor ! = =Promise) {
return safeThen(this, onFulfilled, onRejected);
}
var res = new Promise(noop);
handle(this.new Handler(onFulfilled, onRejected, res));
return res;
};
Copy the code
If this is not a Promise, it must be a Promise and an instance of the Promise is passed to noop
if (fn === noop) return;
doResolve(fn, this);
Copy the code
So doResolve will not continue after a return and the Promise’s state will not change
handle
So let’s go ahead and execute the handle method, so we have a new Handler here, so what’s going on inside that Handler
function Handler(onFulfilled, onRejected, promise){
this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
Copy the code
That is, we assign the argument passed to the THEN and the new Promise instance to the constructor
- The first parameter is an instance of the current Promise
- The second argument is an instance of the new Handler
function handle(self, deferred) { //self => { _state : 1, _value: 'resolve 1', _deferredState: 0, _deferreds: null }
while (self._state === 3) {
self = self._value;
}
if (Promise._onHandle) { // The initialization is null
Promise._onHandle(self);
}
if (self._state === 0) {
if (self._deferredState === 0) {
self._deferredState = 1;
self._deferreds = deferred;
return;
}
if (self._deferredState === 1) {
self._deferredState = 2;
self._deferreds = [self._deferreds, deferred];
return;
}
self._deferreds.push(deferred);
return;
}
handleResolved(self, deferred);
}
Copy the code
Since the current Promise has a _state value of 1 (set in the resolve method), both the judgment and the loop skip directly executing the handleResolve
handleResolve
The first argument is an instance of the current Promise instance and the second argument is a new Handler
function handleResolved(self, deferred) {
asap(function() {
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
if (self._state === 1) {
resolve(deferred.promise, self._value);
} else {
reject(deferred.promise, self._value);
}
return;
}
var ret = tryCallOne(cb, self._value);
if (ret === IS_ERROR) {
reject(deferred.promise, LAST_ERROR);
} else{ resolve(deferred.promise, ret); }}); }Copy the code
All right, here… Finally, you see something about asynchronous calls. It was here for a long time. The ASAP method is referenced to an external package
asap
var asap = require('asap/raw');
Copy the code
The main thing that this package does is to create a microtask, and then push things from the THEN method to the microtask queue for the queue to execute. Let’s look at the code implementation of the push to the microtask queue at the core of this method
var BrowserMutationObserver = scope.MutationObserver || scope.WebKitMutationObserver;
var requestFlush = makeRequestCallFromMutationObserver(flush);
module.exports = rawAsap;
function rawAsap(task) {
if(! queue.length) { requestFlush(); flushing =true;
}
// Equivalent to push, but avoids a function call.
queue[queue.length] = task;
}
function makeRequestCallFromMutationObserver(callback) {
var toggle = 1;
var observer = new BrowserMutationObserver(callback);
var node = document.createTextNode("");
observer.observe(node, {characterData: true});
return function requestCall() {
toggle = -toggle;
node.data = toggle;
};
}
Copy the code
The e browser environment Promise uses the MutationObserver API to push microtasks. All the way here, a promised regular link is gone. Tasks pushed into the microtask queue wait for the event loop to execute, and the asap method callback function is executed when the execution is complete
function handleResolved(self, deferred) {
asap(function() {
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; _state = 1; cb is not null
if (cb === null) {
if (self._state === 1) {
resolve(deferred.promise, self._value);
} else {
reject(deferred.promise, self._value);
}
return;
}
var ret = tryCallOne(cb, self._value); // Here is the body of the function that executes the THEN
if (ret === IS_ERROR) {
reject(deferred.promise, LAST_ERROR);
} else{ resolve(deferred.promise, ret); }}); }Copy the code
After two judgments are skipped, the tryCallOne method is executed
tryCallOne
- The callback in the first argument then
- The second parameter is the Value of the current Promise
function tryCallOne(fn, a) {
try {
return fn(a);
} catch (ex) {
LAST_ERROR = ex;
returnIS_ERROR; }}Copy the code
You can see here that we execute the current Promise as an input to the then method callback and return the result of that function and then execute the resolve method, Now, the incoming arguments here notice that the first one is passed to the next Promise instance of the THEN method and the second one is the result of the current then method execution up here, again, skip the judgment, assign _state, _value and then execute the finale method, However, code within _deferredState 0 finale still does not execute. At this point our simple case has been executed.
summary
A little summary
- The code in the body of the Promise function is executed immediately, and async code is executed only when it hits resolve or reject
- The state of a Promise is irreversible. After the first resolve or reject state is changed, the later resolve and reject states do not take effect
- A Promise is a then method that pushes events to the microtask queue
- The browser side of the Promise is a microtask queue pushed through the MutationObserver API
- Callbacks in then methods take the value of the value passed in by resolve or reject, and callbacks in chained THEN methods take the result of the return in the previous THEN
The Promise of chained calls
We know that Promise can be called in a chain, from the then method in the source code, we can see that a new Promise instance returns, so we can be continuous. Take a look at the following Promise
const p1 = new Promise((resolve, reject) = > {
resolve('p1 resolve')
}).then(() = > {
console.log('p1 then1')
}).then(() = > {
console.log('p1 then2')
}).then(() = > {
console.log('p1 then3')})const p2 = new Promise((resolve, reject) = > {
resolve('p2 resolve')
}).then(() = > {
console.log('p2 then1')
}).then(() = > {
console.log('p2 then2')
}).then(() = > {
console.log('p2 then3')})Copy the code
I don’t know if the execution results are the same as you think, but let’s analyze the source code to explain why the execution results are cross.
We here above the analysis of the code will not stick to the source code, direct method name with, unfamiliar students can look up.
- Initialization within the Promise constructor
this._deferredState = 0
this._state = 0;
this._value = null;
this._deferreds = null;
Copy the code
- perform
doResolve
.tryCallTwo
(Done = true to make resolve or reject impossible) - perform
resolve
Sets the status and assigns a value to the current Promise
this._state = 1;
this._value = newValue // 'p1 resolve'
Copy the code
- perform
finale
- perform
then
- perform
new Handler
,handle
- perform
handleResolved
This is where asap is used to push the body of the function in THEN into the microtask queue. Currently, the microtask queue is [Promise(fulfilled)], a microtask - Next execution is behind then method, rather than the method in the callback function asap, because the browser event loop mechanism, this I will another article, summarizing down is the browser after the execution of the master station synchronization code execution of the current main task in the queue the task under the stack, queue execution is first in first out.
- So it executes the next one. Then I’ll post the code here
Promise.prototype.then = function(onFulfilled, onRejected) {
if (this.constructor ! = =Promise) {
return safeThen(this, onFulfilled, onRejected);
}
var res = new Promise(noop);
handle(this.new Handler(onFulfilled, onRejected, res));// This refers to the new Promise instance that returns in the first THEN method
return res;
};
Copy the code
- perform
then
- perform
handle
- The Promise instance returned in the (first) THEN before the first argument
- The second argument is an instance of the new Handler
function handle(self, deferred) { // _state: 0, _value: null, _deferredState: 0, _deferreds: null
while (self._state === 3) {
self = self._value;
}
if (Promise._onHandle) {
Promise._onHandle(self);
}
if (self._state === 0) { // This is where the judgment comes in
if (self._deferredState === 0) { // By judgment
self._deferredState = 1;
self._deferreds = deferred;
return;
}
if (self._deferredState === 1) {
self._deferredState = 2;
self._deferreds = [self._deferreds, deferred];
return;
}
self._deferreds.push(deferred);
return;
}
handleResolved(self, deferred);
}
Copy the code
HandleResolved will not be executed, since self is the previous Promise instance returned in then, and _state is initialized to 0 so it enters the judgment. Anything that enters the judgment will return, so handleResolved is not executed
We know that the asap method called in the handleResolved method pushes the body of the function in the THEN to the microtask queue, so since it is not executed, the body of the subsequent then method is not pushed to the microtask queue, it is just marked (_deferredState).
In the same way, the next then method does not push the task to the microtask queue, it is just marked, so the Promise const P1 will only push one microtask to the microtask queue So after the two Promises are executed there are only two microtasks in the microtask queue and then the tasks in the microtask queue (asap callback) are executed with the first Promise instance (the first Promise instance) and the second parameter new Handler instance (where the promise property is stored as the first promise instance used by the THEN)
function handleResolved(self, deferred) {
asap(function() {
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
if (cb === null) {
if (self._state === 1) {
resolve(deferred.promise, self._value);
} else {
reject(deferred.promise, self._value);
}
return;
}
var ret = tryCallOne(cb, self._value);
if (ret === IS_ERROR) {
reject(deferred.promise, LAST_ERROR);
} else{ resolve(deferred.promise, ret); }}); }Copy the code
In this case, when we execute resolve, we pass in the first Promise instance that was used in then, and we’ve already marked the _deferredState attribute 1 with the _state:0 in handle So the assignment after the resolve method executes and the finale method executes after the state modification begins to execute
function finale(self) {
if (self._deferredState === 1) {
handle(self, self._deferreds);
self._deferreds = null;
}
if (self._deferredState === 2) {
for (var i = 0; i < self._deferreds.length; i++) {
handle(self, self._deferreds[i]);
}
self._deferreds = null; }}Copy the code
The handleResolved task will be pushed into the microtask queue. The first task will be kicked out of the queue. The first task will be kicked out of the queue. [‘p2 resolve’, ‘p1 then1’] [‘p2 resolve’, ‘p1 resolve’] [‘p2 resolve’, ‘p1 then1’] [‘p1 then1’, ‘p2 then1’] [‘p1 then1’, ‘p2 then1’] [‘ P1 then1’, ‘P1 then2’]…
summary
A Promise is a microtask that is pushed in a THEN. Instead of pushing all tasks to the microtask queue, a chained call will only push the first THEN, and the body of the subsequent THEN method will be marked in the previous Promise property. [‘p1 resolve’, ‘p2 resolve’] [‘p2 resolve’, ‘p1 then1’] [‘p1 then1’] [‘p1 then1’] [‘p1 then1’] ‘p2 then1’] [‘p2 then1’, ‘p1 then2’] [‘p1 then2’, ‘p2 then2’] [‘p2 then2’, ‘p1 then3’] [‘p1 then3’, ‘p2 then3’] [‘p2 then3’] []