Promise source code guide

Promise source code guide

Preface

What is Promise?

I believe that many front-end friends have used Promises. In order to solve the drawbacks of asynchronous programming (hell callbacks and other issues), ES6 provides a powerful thing, that is, Promise. Promise is a constructor that converts asynchronous tasks into synchronous tasks. The state of the task is changed through resolve and reject. The indispensable then method is used to collect the value of Promise. These are the basic uses of Promise. So how does Promise deal with state, how to implement the resove and reject methods, and how to implement chained calls. If you don t know, then this article can help you. Let s analyze Promise together. How is it achieved? I believe that after reading this article, everyone can write their own Promise method.

Here introduce a source code on github that conforms to the Promise A+ specification https://github.com/then/promise

Function Object Promise

Let's take a look first

src/index.js
This file

Some necessary definitions

//define empty function function noop () {} //Used to store error information var IS_ERROR = {} //Get the then function getThen ( obj ) { try { return obj.then; } catch (ex) { LAST_ERROR = ex; return IS_ERROR; } } //Execute the then method callback function function tryCallOne ( fn, a ) { try { return fn(a); } catch (ex) { LAST_ERROR = ex; return IS_ERROR; } } //Execute the Promise constructor function tryCallTwo ( fn, a, b ) { try { fn(a, b); } catch (ex) { LAST_ERROR = ex; return IS_ERROR; } } Copy code

Promise constructor

function Promise ( fn ) { //check instance if ( typeof this !== 'object' ) { throw new TypeError ( 'Promises must be constructed via new' ); } //Check Promise constructor if ( typeof fn !== 'function' ) { throw new TypeError ( 'Promise constructor\'s argument is not a function' ); } //The state of the stored instance 0 means it has not been stored, 1 means 1 is stored, 2 means 2 is stored this. _deferredState = 0 ; //The state of the Promise 0 means padding 1 means Fulfilled 2 means Rejected 3 means resolve is passed in Promise instance this ._state = 0 ; //Fulfilled value this ._value = null ; //Used to store the instance after calling then this ._deferreds = null ; if (fn === noop) return ; //Process Promise Parameters doResolve(fn, this ); } //The following will not explain newPromise._onHandle = null ; newPromise._onReject = null ; newPromise._noop = noop; Copy code

When using

new
When the operator instantiates a Promise, it must pass in the Promise constructor fn, otherwise an error will be thrown,
then initialize the state and value of the instance and finally call
doResolve
Method
Let's take a look
doResolve
this way

doResolve method

function doResolve ( fn, promise ) { //done prevents repeated triggers var done = false ; //tryCallTwo is used to process and mount the reject method //3 parameters are passed in, the Promise constructor itself, resolve callback, reject callback var res = tryCallTwo(fn, function ( value ) { if (done) return ; done = true ; //handle the resolve method resolve(promise, value); }, function ( reason ) { if (done) return ; done = true ; //handle reject method reject(promise, reason); }); //If an error is reported, reject directly if (!done && res === IS_ERROR) { done = true ; reject(promise, LAST_ERROR); } } Copy code

doResolve
The method receives two parameters (Promise constructor, Promise instance is this)
here
tryCallTwo
Plays an important role, it executes the constructor
fn
, Passing in three parameters (fn constructor, resolve callback function, reject callback function)
let's look back
tryCallTwo
This function

//Execute the Promise constructor function tryCallTwo ( fn, a, b ) { //... fn(a, b); //... } Copy code

Here a and b are Promise

resolve
with
reject

carried out
fn
And pass in
a
with
b

Then resolve and reject are executed separately
resolve
Methods and
reject
Method
The resolve method will be executed when the instance calls resolve

resolve method

function resolve ( self, newValue ) { //Prevent the resolved value from being passed into the instance itself if (newValue === self) { return reject( self, new TypeError ( 'A promise cannot be resolved with itself.' ) ); } //The if here is mainly used to resolve a Promise if ( newValue && ( typeof newValue === 'object' || typeof newValue === 'function' ) ) { //Get the then method of Promise var then = getThen(newValue); if (then === IS_ERROR) { return reject(self, LAST_ERROR); } //If the passed in is an instance of Promise if ( then === self.then && newValue instanceof newPromise ) { self._state = 3 ; //directly mount the passed Promise instance to value self._value = newValue; finale(self); return ; } else if ( typeof then === 'function' ) { //If you pass in the then method //use then as the constructor and point this to the then object doResolve(then.bind(newValue), self); return ; } } self._state = 1 ; self._value = newValue; finale(self); } Copy code

resolve
At the beginning of the method, it will first determine the value passed by resolve. If resolve passes in a Promise instance , the following processing
will be performed. Change the state of the instance to 3 and
change the value of the instance to the incoming Promise instance.
Call
finale
Method
If resolve passes in a Promise instance and contains the then method , call
doResolve
Execute the constructor of this instance

If resolve passes in a normal value instead of a Promise instance, do the following:
Change the state of the instance to 1 and
change the value of the instance to the value passed in by resolve
Call

finale
method

reject method

function reject ( self, newValue ) { //When reject, the state becomes 2 self._state = 2 ; //Save error information to _value self._value = newValue; if (newPromise._onReject) { newPromise._onReject(self, newValue); } finale(self); } Copy code

When Promise is executed

reject
When the state becomes
2

save
reject
Error message to
_value
transfer
finale
method

finale method

function finale ( self ) { //Only call then once if (self._deferredState === 1 ) { handle(self, self._deferreds); self._deferreds = null ; } //call then multiple times 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 code

finaale
The role of the method here is equivalent to
handle
Method transfer station, call according to different situations
handle
Method
mentioned above
_deferredState
It is used to record the state of the stored instance
under the same Promise object ,
_deferredState
The value of is determined with the number of then calls. Why is it only triggered under the same Promise object? Let s look at the following small example

const promise2 = new Promise ( ( resolve,reject ) => { setTimeout ( () => { resolve( 'hahaha' ) }, 500 ); }) .then( res => {}) //the first time then .then( res => {}) //the second time then copy the code

Up to this point, many small partners will think that the code will be executed
in the judgment of'if(self._deferredState === 2){}'. In fact, when calling then like this,

_deferredState
The value of will always be only=1, and will only be executed in the judgment of'if(self._deferredState === 1){}'.
Here again, my friends will say, no, I am not in the same Promise Under the object, didn t I just instantiate it once?
When using Promise here, it is indeed only instantiated once, but the Promise returned by each call to the then method is not the same reference to the instance's Promise, that is to say, self here is not an instanced object, which will be detailed later Introduce how then returns the Promise object

promise2.then( res => { console .log(res); }) promise2.then( res => { console .log(res); }) Copy code

Only by calling then will the judgment of'if(self._deferredState === 2){}' be executed

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 constructs an object handle containing a new promise instance and resolve and reject methods ( this , new Handler(onFulfilled, onRejected, res)) ; //return a new promise instance after each then process return res; }; Copy code

Receive two parameters

resolve
with
reject
Function
First determine whether the constructor of the instance is Promise (to prevent external modification of prototype.constructor)
.
safeThen
The function is not explained too much, it is mainly used to instantiate the constructor of the external instance and return the instance.
Create an empty Promise instance to
res

Let's take a look at what the following code mainly does

handle(this, new Handler(onFulfilled, onRejected, res)); Copy code

Let s take it apart, let s take a look

new Handler
What did the instantiation get

//The function of this function is to construct a new object based on resolve and reject through this constructor every 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 code

It can be seen that through instantiation

Handler
The function can get an object that looks like this

and so

handle
The function passes in 2 parameters, one is this, the other is an object like this, then you can see
handle
What did the function do

function handle ( self, deferred ) { //The resolve passed in is the promise instance, this (context) is changed to the passed promise instance while (self._state === 3 ) { self = self._value; } if (newPromise._onHandle) { newPromise._onHandle(self); } //When the padding state (no resolve or reject is called) //store a new promise instance if (self._state === 0 ) { if (self._deferredState === 0 ) { self._deferredState = 1 ; self._deferreds = deferred; return ; } //has been called once then if (self._deferredState === 1 ) { self._deferredState = 2 ; self._deferreds = [self._deferreds, deferred]; return ; } //multiple then self._deferreds.push(deferred); return ; } handleResolved(self, deferred); } Copy code

Everyone should know the code comments above,

handle
The function is mainly used to modify
_deferredState
And save each new instance generated by then

At last

return res
Each call to the then method returns a new Promise instance

Chain call

Careful, you may have found that the bottom of the previous handle method calls a name called

handleResolved
Function, don t say much, go directly to the code

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 of then var cb = self._state === 1 ? deferred.onFulfilled: deferred.onRejected; //If there is no callback in 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 code

In fact, the source code also introduces the asap library. I commented out the asap function first. The reason is that I am too lazy to use npm. The whole article is html+js+live-server,
but don t ignore it.

asap
This function!!!
Throughout the full text up to now, you don t seem to find that the source code has a little asynchronous information. Everyone knows that Promises are executed asynchronously.
asap
Function, pass
setImmediate
This core method is to execute things in asap asynchronously. If you are interested, you can look through the source code of asap to see how it is implemented.
This is just to better analyze the source code. Without asap, Promise is meaningless. Ok, let s return to the text.

It's easy to understand here.
Pass

tryCallOne
The function gets the return value of
then and then calls again
resolve
, If you report an error or call reject manually, call reject, which completes the chain call of Promise

Expand

src/es6-extensions.js

definition

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 code

Promise.resolve

Promise .resolve = function ( value ) { //Determine whether value is an instance of Promise if (value instanceof Promise ) return value; //Because 0,'', null, etc. will be implicitly converted to false, so an accurate judgment is made 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 judgment here is the same as the previous resolve method to judge whether the incoming 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 code

try/catch is used to capture whether the val passed in by Promise.resolve contains

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 is equal to 3 to prove that the value of the val instance is also a Promise instance, replace val with a new Promise instance while (val._state === 3 ) { val = val._value; } //The resolved call is successful, and the resolved value is processed recursively if (val._state === 1 ) return res(i, val._value); if (val._state === 2 ) reject(val._value); //When in the padding state, call the then method 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; //After promise.all is in fulFilled state, if (--remaining === 0 ) { resolve(args); } } for ( var i = 0 ; i <args.length; i++) { res(i, args[i]); } }); }; Copy code

The first line is used

iterableToArray
Function, the main function of this function is to convert an array-like array into an array that can be traversed

const P1 = new new Promise ( () => {}) const P2 = new new Promise ( () => {}) Console .log ( the Array .isArray ( Promise .all [P1, P2])) //to false copy the code
//Compatible with the es6 syntax of Array.form var iterableToArray = function ( iterable ) { 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 code

Use Array.prototype.slice.call(x) to make Array.from 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 code

Don't look at it yet

res
Method, take a look at how to deal with the parameters passed in
the cycle call
res
Process each item passed in, the first parameter is the subscript, and the second parameter is each item in the array
.
res
method

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 is equal to 3 to prove that the value of the val instance is also a Promise instance, replace val with a new Promise instance while (val._state === 3 ) { val = val._value; } //The resolved call is successful, and the resolved value is processed recursively if (val._state === 1 ) return res(i, val._value); if (val._state === 2 ) reject(val._value); //When in the padding state, call the then method 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; //After promise.all is in fulFilled state, if (--remaining === 0 ) { resolve(args); } } Copy code

If it comes in

val
(Each item of args) is not an object or function, then it is directly regarded as the result value and args[i] is replaced

args[i] = val; copy the code

If what comes in is a

Promise
,then

if (val instanceof Promise && val.then === Promise .prototype.then) { ... } Copy code

If it comes in

val
Is not
Promise
And include the then method, then

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 code

Focus on this paragraph

//_state is equal to 3 to prove that the value of the val instance is also a Promise instance, replace val with a new Promise instance while (val._state === 3 ) { val = val._value; } //The resolved call is successful, and the resolved value is processed recursively if (val._state === 1 ) return res(i, val._value); if (val._state === 2 ) reject(val._value); //When in the padding state, call the then method and manually process the value val.then( function ( val ) { res(i, val); }, reject); return ; copy code

Only when the state of Promise is

Fulfilled
When the instance of
value
Will be processed correctly, otherwise it will be executed
return
, So as long as there is one
Promise
Unsuccessful
Fulfilled
Will not execute
resolve(args)

//The conditions cannot be met if (--remaining === 0 ) { resolve(args); } Copy code

When all the result values are obtained

(args[i] = val) == args[i] = val.value
,transfer
resolve
Method and pass in the result array
args

See 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 code

Anytime here

resolve
, The final result array res is in accordance with
Promise.all([])
The order in the array is output, which also confirms why the subscript is passed to the source code
res()
s reason
(res(i, args[i]))

Promise.race

Promise .race = function ( values ) { return new Promise ( function ( resolve, reject ) { iterableToArray(values).forEach( function ( value ) { Promise .resolve(value).then(resolve, reject); }); }); }; Copy code

Promise.race
Pass in an array, use
Promise.resolve
Process each item and call the then method and
finally return one
Promise
Examples
here
Promise.resolve(value).then(resolve, reject)
When incoming
Promise
Whose status becomes first
Fulfilled
, Whoever calls then first
because
Promise
carried out
resolve
The state will not change after one time, so the race is passed in multiple
Promise
, Whose status becomes
Fulfilled
, Which will be returned by race

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 code