preface
What is Promise?
For those of you working on the front end who have used Promises, ES6 offers a powerful solution to the problems of asynchronous programming (hellbacks, etc.) : Promises. Promise is a constructor that transforms an asynchronous task into a synchronous one, changing the state of the task through resolve, reject, and the essential then methods used to accept the Promise value are all basic uses of promises. How do promises handle states, resove, Reject, and chain calls? If you don’t know, this article will help you. Let’s take a look at how promises are implemented. Everyone can write their own Promise method.
Here introduced making A in line with the Promise of A + standard source https://github.com/then/promiseCopy the code
Function object Promise
Let’s start with the SRC /index.js file
Some necessary definitions
// Define an empty function
function noop() {}
// Used to store error information
var IS_ERROR = {}
// Get the instance's then
function getThen(obj) {
try {
return obj.then;
} catch (ex) {
LAST_ERROR = ex;
returnIS_ERROR; }}// Execute the then method callback
function tryCallOne(fn, a) {
try {
return fn(a);
} catch (ex) {
LAST_ERROR = ex;
returnIS_ERROR; }}// Execute the Promise constructor
function tryCallTwo(fn, a, b) {
try {
fn(a, b);
} catch (ex) {
LAST_ERROR = ex;
returnIS_ERROR; }}Copy the code
Promise constructor
function Promise(fn) {
// Verify the instance
if (typeof this! = ='object') {
throw new TypeError('Promises must be constructed via new');
}
// Validates the Promise constructor
if (typeoffn ! = ='function') {
throw new TypeError('Promise constructor\'s argument is not a function');
}
// The stored instance status 0 indicates that the instance is not stored yet. 1 indicates that one instance is stored. 2 indicates that two instances are stored
this._deferredState = 0;
// The state 0 represents padding, which is a big pity, which is Fulfilled. The state 0 represents padding, which is Fulfilled. The state 0 represents Rejected, which is Fulfilled
this._state = 0;
/ / the value of the Fulfilled
this._value = null;
// Store the instance after calling THEN
this._deferreds = null;
if (fn === noop) return;
// Handle the Promise arguments
doResolve(fn, this);
}
// No explanation
newPromise._onHandle = null;
newPromise._onReject = null;
newPromise._noop = noop;
Copy the code
When you instantiate a Promise with the new operator, you must pass in the Promise constructor fn, otherwise an error will be thrown, the state and value of the instance will be initialized, and the doResolve method will be called
DoResolve method
function doResolve(fn, promise) {
// done to prevent repeated triggering
var done = false;
TryCallTwo is used to process and mount reolve, reject
// Pass three arguments, the Promise constructor itself, the resolve callback, and the reject callback
var res = tryCallTwo(fn, function (value) {
if (done) return;
done = true;
// Resolve method
resolve(promise, value);
}, function (reason) {
if (done) return;
done = true;
// Reject method
reject(promise, reason);
});
// Reject the error
if(! done && res === IS_ERROR) { done =true; reject(promise, LAST_ERROR); }}Copy the code
The doResolve method takes two arguments (the Promise constructor, the Promise instance being this). TryCallTwo here plays an important role by executing the fn constructor, passing in three arguments (the fn constructor, the resolve callback, Reject callback) let’s go back to the tryCallTwo function
// Execute the Promise constructor
function tryCallTwo(fn, a, b) {
/ /...
fn(a, b);
/ /...
}
Copy the code
So a and B are the Promise’s resolve and reject call fn and pass in a and B and then resolve and Reject call resolve and reject call resolve when the instance calls resolve, Let’s look at the resove method
Resolve method
function resolve(self, newValue) {
// Prevent the value of resolve from being passed into the instance itself
if (newValue === self) {
return reject(
self,
new TypeError('A promise cannot be resolved with itself.')); }// If is used to resolve a Promise
if (
newValue &&
(typeof newValue === 'object' || typeof newValue === 'function')) {// Get the Promise's then method
var then = getThen(newValue);
if (then === IS_ERROR) {
return reject(self, LAST_ERROR);
}
// If an instance of Promise is passed in
if (
then === self.then &&
newValue instanceof newPromise
) {
self._state = 3;
// Mount the incoming Promise instance directly to value
self._value = newValue;
finale(self);
return;
} else if (typeof then === 'function') { // If passed with the then method
// Treat then as a constructor and refer this to the object of that THEN
doResolve(then.bind(newValue), self);
return;
}
}
self._state = 1;
self._value = newValue;
finale(self);
}
Copy the code
The resolve method starts by determining the value passed in to resolve. If resolve is passed in as a Promise instance, Changing the instance state to 3 changes the instance value to the incoming Promise instance. Call the finale method. If resolve is passed as a Promise instance and contains the THEN method, doResolve is called to execute the constructor for that instance
If resolve is passed a normal value instead of a Promise instance, call the finale method by changing the instance’s state to 1 and changing its value to the value passed in resolve
Reject method
function reject(self, newValue) {
// reject changes to 2
self._state = 2;
// Save the error message to _value
self._value = newValue;
if (newPromise._onReject) {
newPromise._onReject(self, newValue);
}
finale(self);
}
Copy the code
Reject status changes to 2 when a Promise executes Reject save the reject error message to the _value call to the finale method
Finale method
function finale(self) {
// Call then only once
if (self._deferredState === 1) {
handle(self, self._deferreds);
self._deferreds = null;
}
// Call multiple times then
if (self._deferredState === 2) {
// console.log(self._deferreds);
for (var i = 0; i < self._deferreds.length; i++) {
handle(self, self._deferreds[i]);
}
self._deferreds = null; }}Copy the code
The finaale method acts as a sort of staging post for the Handle method, calling the Handle depending on the situation and as mentioned above _deferredState is used to record the state of the stored instance under the same Promise object, The value of _deferredState is determined by the number of calls to THEN. Why is it only triggered by the same Promise object? Here’s a quick example
const promise2 = new Promise((resolve,reject) = > {
setTimeout(() = > {
resolve('Hahaha')},500);
})
.then(res= > {}) // First time then
.then(res= > {}) // The second time then
Copy the code
If (self._deferredState === 2){}, _deferredState will always be 1, If (self._deferredState === 1){} ‘, then the kids will say, “No, I’m under the same Promise, I only instantiated it once.” It’s true that we only instantiate a Promise once, but each call to then returns a Promise that is not the same reference as the instance’s Promise. In other words, self is not the instantiated object. We’ll talk more about how then returns a Promise
promise2.then(res= > {
console.log(res);
})
promise2.then(res= > {
console.log(res);
})
Copy the code
If (self._deferredState === 2){} ‘is executed only when then is called
Promise.prototype.then
Promise.prototype.then = function(onFulfilled, onRejected) {
if (this.constructor ! = =Promise) {
return safeThen(this, onFulfilled, onRejected);
}
// Create a new Promise instance
var res = new Promise(noop);
// // new Handler builds an object containing the new Promise instance and the resolve, Reject methods
handle(this.new Handler(onFulfilled, onRejected, res));
// A new PROMISE instance is returned after each then processing
return res;
};
Copy the code
The resolve and reject functions start by deciding whether the instance’s constructor is Promise(to prevent external changes to prototype.constructor). The safeThen function is used to instantiate the external instance’s constructor and return it Create an empty Promise instance for res. What does this code do
handle(this, new Handler(onFulfilled, onRejected, res));
Copy the code
So let’s break it down and see what happens when we instantiate new Handler
The resolve and reject constructor builds a new object each time then
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
So you can get an object that looks something like this by instantiating the Handler function
So handle takes two arguments, one to this and one to an object that looks like this. So, what does Handle do
function handle(self, deferred) {
// Resolve is passed in an instance of the promise, and this (context) is passed in as an instance of the promise
while (self._state === 3) {
self = self._value;
}
if (newPromise._onHandle) {
newPromise._onHandle(self);
}
// In the padding state (resolve and reject are not called)
// Store the new Promise instance
if (self._state === 0) {
if (self._deferredState === 0) {
self._deferredState = 1;
self._deferreds = deferred;
return;
}
// Then has been called once
if (self._deferredState === 1) {
self._deferredState = 2;
self._deferreds = [self._deferreds, deferred];
return;
}
/ / then many times
self._deferreds.push(deferred);
return;
}
handleResolved(self, deferred);
}
Copy the code
The handle function is used to change the value of _deferredState and save a new instance of it each time it is generated
Finally, return RES returns a new Promise instance each time it calls the THEN method
Chain calls
If you are careful, you may have noticed that at the bottom of the previous Handle method a function called handleResolved is called
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);
/ /}
// });
After resolve, get the callback for then
var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
// If there is no callback for then, call the callback manually
if (cb === null) {
if (self._state === 1) {
resolve(deferred.promise, self._value);
} else {
reject(deferred.promise, self._value);
}
return;
}
// Get the return value of then
var ret = tryCallOne(cb, self._value);
if (ret === IS_ERROR) {
reject(deferred.promise, LAST_ERROR);
} else{ resolve(deferred.promise, ret); }}Copy the code
In fact, source code also introduced asap this library, I first put the ASAP function annotation out, the reason is too lazy to use NPM, this article is HTML + JS +live-server but we must not ignore ASAP this function!! So far, you don’t seem to have noticed any asynchrony in the source code, you know that promises are implemented asynchronously, SetImmediate mediate is the core method of implementing things asynchronously through the ASAP function. If you’re interested, look at the SOURCE code of ASAP and see how it works. It’s just for better parsing of the source code. Without ASAP, the Promise is meaningless
This makes sense by calling tryCallOne to get the return value of THEN and then calling resolve again, reject if an error is reported or reject is called manually, completing the chained call to Promise
extension
src/es6-extensions.js
define
var TRUE = valuePromise(true);
var FALSE = valuePromise(false);
var NULL = valuePromise(null);
var UNDEFINED = valuePromise(undefined);
var ZERO = valuePromise(0);
var EMPTYSTRING = valuePromise(' ');
function valuePromise(value) {
var p = new newPromise(newPromise._noop);
p._state = 1;
p._value = value;
return p;
}
Copy the code
Promise.resolve
Promise.resolve = function (value) {
// Check if value is an instance of Promise
if (value instanceof Promise) return value;
// Because 0,'',null, etc., are implicitly converted to false, we make a precise judgment here
if (value === null) return NULL;
if (value === undefined) return UNDEFINED;
if (value === true) return TRUE;
if (value === false) return FALSE;
if (value === 0) return ZERO;
if (value === ' ') return EMPTYSTRING;
// The same determination as in the resolve method is to determine if the incoming Promise is a Promise
if (typeof value === 'object' || typeof value === 'function') {
try {
var then = value.then;
if (typeof then === 'function') {
return new Promise(then.bind(value)); }}catch (ex) {
return new Promise(function (resolve, reject) { reject(ex); }); }}// Return a new Promise according to the valuePromise method
return valuePromise(value);
};
Copy the code
A try/catch is used to catch promise. resolve whether val passed contains a then method
Promise.all
Promise.all = function (arr) {
var args = iterableToArray(arr);
return new Promise(function (resolve, reject) {
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {
if (val && (typeof val === 'object' || typeof val === 'function')) {
// If val is an instance of Promise
if (val instanceof Promise && val.then === Promise.prototype.then) {
// _state equals 3 to prove that val is also a Promise instance. Replace val with a new Promise instance
while (val._state === 3) {
val = val._value;
}
Resolved successfully called, recursively handling resolved values
if (val._state === 1) return res(i, val._value);
if (val._state === 2) reject(val._value);
// Call the then method while in the padding state and manually process the value
val.then(function (val) {
res(i, val);
}, reject);
return;
} else {
// If it is not an instance of Promise and contains the then method
var then = val.then;
if (typeof then === 'function') {
var p = new Promise(then.bind(val));
p.then(function (val) {
res(i, val);
}, reject);
return;
}
}
}
args[i] = val;
// This is a big pity
if (--remaining === 0) { resolve(args); }}for (var i = 0; i < args.length; i++) { res(i, args[i]); }}); };Copy the code
The first line uses the iterableToArray function, whose main purpose is to convert an array of classes into an array that can be traversed
const p1 = new Promise(() = > {})
const p2 = new Promise(() = > {})
console.log(Array.isArray(Promise.all[p1,p2])) //false
Copy the code
Array.form is compatible with es6 syntax
var iterableToArray = function (可迭代) {
if (typeof Array.from === 'function') {
// ES2015+, iterables exist
iterableToArray = Array.from;
return Array.from(iterable);
}
// ES5, only arrays and array-likes exist
iterableToArray = function (x) { return Array.prototype.slice.call(x); };
return Array.prototype.slice.call(iterable);
}
Copy the code
Use Array. Prototype. Slice. Call (x) to the Array. The from made compatible
Promise.all = function (arr) {
var args = iterableToArray(arr);
return new Promise(function (resolve, reject) {
if (args.length === 0) return resolve([]);
var remaining = args.length;
function res(i, val) {... }for (var i = 0; i < args.length; i++) { res(i, args[i]); }}); };Copy the code
So instead of looking at the RES method, let’s look at what we’re going to do with the parameters that are passed in and we’re going to loop through the RES method and we’re going to process each item that’s passed in, so the first parameter is the index, the second parameter is each item in the array and let’s look at the RES method, okay
function res(i, val) {
if (val && (typeof val === 'object' || typeof val === 'function')) {
// If val is an instance of Promise
if (val instanceof Promise && val.then === Promise.prototype.then) {
// _state equals 3 to prove that val is also a Promise instance. Replace val with a new Promise instance
while (val._state === 3) {
val = val._value;
}
Resolved successfully called, recursively handling resolved values
if (val._state === 1) return res(i, val._value);
if (val._state === 2) reject(val._value);
// Call the then method while in the padding state and manually process the value
val.then(function (val) {
res(i, val);
}, reject);
return;
} else {
// If it is not an instance of Promise and contains the then method
var then = val.then;
if (typeof then === 'function') {
var p = new Promise(then.bind(val));
p.then(function (val) {
res(i, val);
}, reject);
return;
}
}
}
args[i] = val;
// This is a big pity
if (--remaining === 0) { resolve(args); }}Copy the code
If val (each item of args) is passed in that is not an object or function, replace it with args[I]
args[i] = val;
Copy the code
If a Promise is passed in
if (val instanceof Promise && val.then === Promise.prototype.then) {
...
}
Copy the code
If the val passed in is not a Promise and contains the then method
else {
// If it is not an instance of Promise and contains the then method
var then = val.then;
if (typeof then === 'function') {
var p = new newPromise(then.bind(val));
p.then(function (val) {
res(i, val);
}, reject);
return; }}Copy the code
The emphasis is on this paragraph
// _state equals 3 to prove that val is also a Promise instance. Replace val with a new Promise instance
while (val._state === 3) {
val = val._value;
}
Resolved successfully called, recursively handling resolved values
if (val._state === 1) return res(i, val._value);
if (val._state === 2) reject(val._value);
// Call the then method while in the padding state and manually process the value
val.then(function (val) {
res(i, val);
}, reject);
return;
Copy the code
This is a big pity. Only when the state of Promise is Fulfilled, the value of the instance will be correctly processed, otherwise the return will be implemented. Therefore, as long as one Promise fails to fulfill, the resolve(args) will not be implemented.
// The condition cannot be met
if (--remaining === 0) {
resolve(args);
}
Copy the code
When all the result values are returned (args[I] = val) == args[I] = val.value, call the resolve method and pass in the result array args
Look at an example
const promise2 = new Promise((resolve,reject) = > {
setTimeout(() = > {
resolve('Hahaha')},700);
})
const promise3 = new Promise((resolve,reject) = > {
setTimeout(() = > {
resolve('Hahaha 2')},600);
})
newPromise.all([promise2,promise3])
.then(res= > {
console.log(res); //[' hahaha ',' hahaha 2']
})
Copy the code
Res (I, args[I]) is used to output the array res(I, args[I]) in the same order as promise.all ([]).
Promise.race
Promise.race = function (values) {
return new Promise(function (resolve, reject) {
iterableToArray(values).forEach(function(value){
Promise.resolve(value).then(resolve, reject);
});
});
};
Copy the code
Promise.race passes in an array, Resolve (value).then(resolve, The state will not change after the Promise executes resolve once. Therefore, race will pass in multiple promises. The Promise whose state will become a big pity first will be returned
const promise2 = new Promise((resolve,reject) = > {
setTimeout(() = > {
resolve('Hahaha')},700);
})
const promise3 = new Promise((resolve,reject) = > {
setTimeout(() = > {
resolve('Hahaha 2')},600);
})
Promise.race([promise2,promise3])
.then(res= > {
console.log(res); // Hahaha 2
})
Copy the code