At present, many blogs are teaching how to use, not involving his implementation and principle part, the length is short, knowledge is fragmentary, come up with A more complete Promise tutorial, help new students quickly start, of course, the specific process can also go to see the Promise A+ specification, But Promise A+ specification document logic is too complex, A half will not string up, even if understand part, soon may see another specification to break their string of good logic, and have to string again, so, to learn to learn all specifications together, long pain is better than short pain, XDM, blunt duck!

AbouveAll

If you are already familiar with the observer pattern, higher-order functions, various static methods of Promise, etc., or the following code print results and reasons, this article can be skipped directly, but please read through the 2740 word article, continue to improve obscure points.

let temp = new Promise((reslove, reject) = > {
  console.log('1')
  reslove('value1')})let p1 = new Promise((reslove, reject) = > {
  console.log('2')
  reslove(temp)  
}).then((res) = > {
  console.log('a1')
}).then((res) = > {
  console.log('a2')
}).then((res) = > {
  console.log('a3')})let p2 = new Promise((reslove, reject) = > {
  console.log('3')
  reslove('valu2e')
}).then((res) = > {
  console.log('b1')
}).then((res) = > {
  console.log('b2')
}).then((res) = > {
  console.log('b3')})Copy the code

process

For quick understanding, the Promise can be divided into three steps, namely, the parsing process, the THEN process and the observation process

Analytical process

A Promise is declared. When a callback value is received, either reslove or Reject is used to inject the value into the Promise and make the state transition.

let p =  new (function(reslove, reject){
	reslove(value)
})
Copy the code
  • Reject (value) changes the state reject (value),
  • When reslove (value) is called, value will be the result and the Promise will be changed to fulfilled,
    • If value is an object and it has a then method, called thenable, then it might be a Promise, trying to continue parsing, calling the then method of that value until it gets a non-Thenable value, How to continue parsing thenabel is described in detail in then.
    • If value is a non-Thenable value, it is directly injected

Source status map

The red and green parts in the figure will undergo state changes, which is gradually changed from the pending state to the failed and successful state respectively. Before the state change, there will be a series of judgments.

Inject non-Thenable values

let p = new Promise(function (resolve, reject) {  
    resolve('success')})// This will be a big pity.

Copy the code

Because ‘success’ is not thenable, I will directly inject ‘success’ into the promise with a depressing state. The final promise P is as follows

Injection thenable value

let p2 = new MyPromise(function (resolve, reject) {   
    resolve('haha')})let p = new MyPromise(function (resolve, reject) { 
    resolve(p2)
})
Copy the code

The value of thenable is injected. For example, if a Promise P2 is injected, p2 itself has then method, so it cannot be injected as the final value. You need to call THE THEN method of P2 to get the final value, and then inject the final value into this Promise. This requires understanding of the THEN process and observer process ** (difficulty ①) **.

Then the process

The then method of a Promise used to get the end value of a Promise invokes different methods depending on the state of the Promise, as shown in the code below

let p = new Promise(function(reslove, reject){
	reslove('hah')	
}) 

p.then((res) = > {
	console.log(res) // 'hah'
},(err) = > {
	console.log(err)
})
Copy the code
  • When the JS engine executes the P.teng method, it adds the two parameters of the then method to the microtask queue, and must return a new Promise before the actual execution
    • The depressing state: The final value will be passed as the parameter of the first callback function of the THEN method
      • The state and final value of the new Promise are determined by the type of value returned by the then method with a callback function
        • If thenable is returned, the thenable** continues to be parsed(Difficult point ②)支那
        • Returns non-thenable, which can be injected directly with this value in the DEPRESSING state
    • The Rejected state: passes the result through the second parameter
      • The chain is rejected, and there will be no follow-up results.
    • Pending state: The parameters of the THEN method (two callback functions) are added as observers to the original Promise queue. After the principle Promise state is determined, all observers are notified to execute, inject values, and change the state.

Source flow chart

A non-pending state calls the then method

A non-pending state is one in which the state has changed. Calling then directly retrieves the final value, as shown in the following code

let p = new MyPromise(function (reslove, reject) {
  console.log('1')  
  reslove('hah')})let temp = p; // save p to temp

p = p.then((res) = > {   
  console.log(res) 
  return 'bbb'
}, (err) = > {
  console.log(err)
})  
// a new promise is returned


console.log('2')
console.log(temp === p) // false
Copy the code

The Promise resolution process starts, injecting non-Thenable values directly, and then calling the then method

In addition to the parse flow, both the THEN flow and the observer flow are microtasks, and the code above should print in the order of ‘1’ ‘2’ ‘HAH’.

Call the then method when in a Pending state

Being Pending, that is, calling the THEN method while the result is still Pending, is also common, and is a basic scenario for asynchronous operations,

let p  = new Promise(function(reslove, reject){
	setTimeout(() = >{
		reslove('Display in two seconds')},2000)
})

p.then((res) = >{
	console.log(res) // Print in 2 seconds
},(err) = >{
	console.log(err)
})
Copy the code

When p. Chen is executed, the final value of P has not been injected, but in order to keep the chain falling, it must continue. Therefore, the Promise internally wraps the callback function of then method, namely the successful and failed callback function, as an observer, and pushes it into the observer queue of P. Waiting for p’s state to change notifying the observer, making a choice based on p’s state, which is similar to calling P. then in a non-pending state, very cleverly uses the observer to figure out how to link promises that do not inject final values.

Observer flow

When the Js engine executes the p,then process, and the final value of P is not determined, it will wrap the successful and failed callback function as an observer and store it in the observer queue such as P. As mentioned above, when the state of P changes, it will notify the observer queue. Let each observer decide which callback function to execute based on its value and state.

In fact, the execution of THEN process is suspended. The internal process and THEN process are basically the same. Notifying the observer after the state change of P is equivalent to calling THEN method in the non-peding state of THEN process.

What are the rules for adding observers

To understand the rules for adding observers, try writing a print of the front code.

let p1  = new Promise(function(reslove, reject){
	setTimeout(() = >{
		reslove('Display in 1 second')},1000)
})
p1.then((res) = >{
	console.log('p1 then:',res)
})

let p2 = new Promise(function(reslove, reject){
	console.log(2)
	reslove(p1)
})
p2.then((res) = >{
	console.log('p2 then:',res)
})

let p4 = new Promise(function(reslove, reject){
	reslove(p1)
})
p4.then((res) = >{
	console.log('p4 then:',res)
})

let p3 = new Promise(function(reslove, reject){
	reslove(p2)
})
p3.then((res) = >{
	console.log(res)
},(err) = >{
	console.log(err)
})
console.log(1)
Copy the code

First, P1 simulates asynchronous request data. Now each upper layer indexes the upper layer. When the upper layer data changes, it notifies the lower layer to change the data. Wait for notification of a change in the upper Promise state.

According to the above logic, the order of the notification thick callback method would be pushed into the microtask queue like this, as shown in the microtask queue and the print result.

But how did P2 Promise injection P1 get added?

Both promises are executed simultaneously

let p1 = new Promise((reslove, reject) = > {
  reslove('suc')
}).then((res) = > {
  console.log(res, 'a1')
}).then((res) = > {
  console.log(res, 'a2')
}).then((res) = > {
  console.log(res, 'a3')
}).then((res) = > {
  console.log(res, 'a4')})let p2 = new Promise((reslove, reject) = > {
  reslove('suc')
}).then((res) = > {
  console.log(res, 'b1')
}).then((res) = > {
  console.log(res, 'b2')
}).then((res) = > {
  console.log(res, 'b3')
}).then((res) = > {
  console.log(res, 'b4')})Copy the code

One thing to keep in mind:

  1. The then process returns after the first macro task has been executed, and can be understood as linking all the promises
  2. The observer is notified when the state changes, and the microtask is added to the microtask queue

According to the above two points can understand the above code execution process

Because the first time you perform a macro task, all the promises have already returned and been linked together, this part is not drawn (you can try to debug it in the browser), and you can directly look at the microtask queue. Although the macro task is linked, the first macro task only changes the state of two promises. Therefore, there are only two microtasks in the microtask queue at present. After the execution of the first microtask changes the Promise state of the other, the observed microtasks are added to the microtask queue. Each time there are two microtasks in the microtask queue at the same time, so the continuous addition forms cross-printing.

The result of cross-printing code above is the high-priority feature of adding all tasks directly to the macro task queue at parsing time, and the microtask queue adding microtasks to the microtask queue as it executes.

How do I add observers when INJECTING ThEnable

Take a look at the print order of the code below:

let temp = new Promise((reslove, reject) = > {
  console.log('1')
  reslove('value1')})let p1 = new Promise((reslove, reject) = > {
  console.log('2')
  reslove(temp)  
}).then((res) = > {
  console.log('a1')
}).then((res) = > {
  console.log('a2')
}).then((res) = > {
  console.log('a3')})let p2 = new Promise((reslove, reject) = > {
  console.log('3')
  reslove('valu2e')
}).then((res) = > {
  console.log('b1')
}).then((res) = > {
  console.log('b2')
}).then((res) = > {
  console.log('b3')})Copy the code

As in line 7, temp is injected as a Promise, which is equivalent to thenable. Internally, thenable’s then method needs to be called to fetch the value that accepts the Promise. Therefore, line 7 is equivalent to one more step temp. Then (ondepressing, onRejected), adding an observer to it, which is the same as the THEN process.

The source code to achieve

Function way to achieve the source code

var
    PENDING = 'PENDING',
    FULFILLED = 'FULFILLED',
    REJDECTED = 'REJDECTED'

function MyPromise(fn) {
    var state = PENDING,
        value = null,
        handlers = [];

    // Parse the process
    doResolve(fn, resolve, reject)

    function doResolve(fn, onFulfilled, onRejected) {
        let done = false;
        try {
            fn(function (value) {
                if (done) return
                done = true;
                onFulfilled(value)
            }, function (err) {
                if (done) return
                done = true;
                onRejected(err)
            })
        } catch (err) {
            if (done) return
            done = true
            onRejected(err)
        }
    }
    // Try to inject value
    function resolve(value) {  
        try {
            var then = getThen(value)
            if (then) {
                doResolve(then.bind(value), resolve, reject)
                return
            }
            fulfill(value)
        } catch (error) {
            reject(error)
        }
    }

    // thenable
    function getThen(value) {
        var t = typeof value;
        if (value && (t === 'object' || t === 'function')) {
            var then = value.then
            if (typeof then === 'function') {
                return then
            }
        }
        return null;
    }

    // The state changes gradually
    function fulfill(result) {
        state = FULFILLED;
        value = result
        handlers.forEach(handle); // Tell the observer to process
        handlers = null
    }
    // State change REJDECTED
    function reject(err) {
        state = REJDECTED;
        value = err
        handlers.forEach(handle); // Tell the observer to process
        handlers = null
    }

    // Add/notify the observer
    function handle(handler) {
        if (state === PENDING) {
            handlers.push(handler)
        } else {
            if (state === FULFILLED &&
                typeof handler.onFulfilled === 'function') {
                handler.onFulfilled(value)
            } else if (state === REJDECTED &&
                typeof handler.onRejected === 'function') {
                handler.onRejected(value)
            }
        }
    }

    // Simulate asynchrony
    this.done = function (onFulfilled, onRejected) {
        setTimeout(function () {
            handle({
                onFulfilled: onFulfilled,
                onRejected: onRejected
            });
        }, 0);
    }

    / / then process
    this.then = function (onFulfilled, onRejected) {
        var self = this;
        return new MyPromise(function (resolve, reject) {
            return self.done(function (result) {
                if (typeof onFulfilled === 'function') {
                    try {
                        return resolve(onFulfilled(result));
                    } catch (ex) {
                        returnreject(ex); }}else {
                    returnresolve(result); }},function (err) {
                if (typeof onRejected === 'function') {
                    try {
                        return resolve(onRejected(err))
                    } catch (ex) {
                        return reject(ex)
                    }
                } else {
                    returnreject(err) } }); }}})Copy the code

In later cases, the simulated microtask implemented by setTimeOut may be different from the native microtask. If the macro task queue is used, there is no concept of high priority of microtask, leading to such situations as Promise cannot realize double Promise cross-printing (the two promises above are executed at the same time).

Implement authentication

Promises – Aplus-Tests A+

Implemented in a functional manner, 868/872 pass rate.

In principle

We also need to understand the principle of the tentacle energy level

Functional programming

Functional programming is specifically explained in one article. In Promise, only higher-order functions are used, and there are only two kinds. Functions are passed as parameters and functions as return values, as shown in the following code:Copy the code

Function as argument:

function fn1(params){ console.log(params) }

function fn2(fn){}
fn2(fn1){
	fn(1) / / print 1
}
Copy the code

Function as return value: A function returns another function, which looks like this, eventually passing the return value to the function at the bottom of the stack in the inner execution stack, and the later function is pushed to the top of the stack, waiting to be ejected.

function fn1(){
  // Of course you can return another function here.
}
function fn2(){
	return fn1()
}
Copy the code

Higher-order functions used within the Promise implementation

doResolve(fn, resolve, reject) 
// fn, resolve, reject are all functions
// fn passes two more methods and finally suspends execution

function doResolve(fn, onFulfilled, onRejected) {
  let done = false;
  try {
    fn(function (value) {
      // ...
    }, function (err) {
      // ...})}catch (err) {
		// ...}}Copy the code

There are a lot of places like the done method used to return new Promises, etc., you can look at the source code implementation above in detail.

Observer model

The observer model is one of the behavioral models, which will be explained in detail in an article. Simply speaking, the observer model mainly has two roles

  • Subject: The object being observed
  • Watcher: An informed character

First add the observer to the object to be observed, and go back to inform the observer when the state of the topic changes. The following is a simple code example:

class subject {
	construct(){
  	this.watchers = Object.create(null)
  }
  addWatcher (w) {
  	this.watchers.push(w) // Add the observer to the observer queue
  }
  notifyAll(){
  	this.watchers.forEach((item) = > {
    	item.update()
    })
  }
}

class Watcher {
	construct(){}update() {
  	// The behavior of the observer after notification}}Copy the code

In the implementation source code, when the macro task is executed to the promise. then method, if the Promise state is changed, the micro-task will be pushed to the micro-task queue. If the Promise is still Pending at this point, Then is placed in the observer queue of the Promise for the time being, and the microtask is pushed into the microtask queue for the next notification, waiting for the microtask to execute and change the other Promise states, so that the loop can form a chain call. That said, the observer pattern plays a very important role in Promise implementation.

Promise static method implementation

Promise.all

Pass in the first argument

Promise.myAll = function () {
    let args = arguments;
    return new Promise((reslove, reject) = > {
        // Check whether the first argument is iterable
        if (!Object.getPrototypeOf(args[0[])Symbol.iterator]) {
            throw new Error(`${args[0]} is not iterable`)}const iterable = args[0];
        let resArr = [],
            idx = 0,
            falg = 1;

        for (let p of iterable) {
            // Put an array of results according to type P
            if (typeof p.then === 'function') {
                p.then((res) = > {
                    resArr[idx++] = res
                    if (++falg === iterable.length) reslove(resArr)
                }, (err) = > {
                    reject(esrr)
                })
            } else{ resArr[idx++] = p; falg++; }}})}Copy the code

Promise.race

Promise.myRace = function () {
    let args = arguments;
    return new Promise((reslove, reject) = > {
        // Check whether the first argument is iterable
        if (!Object.getPrototypeOf(args[0[])Symbol.iterator]) {
            throw new Error(`${args[0]} is not iterable`)}const iterable = args[0];
        let resArr = [];
        for (let p of iterable) {
            // Immediately return the first decision Promise
            if (typeof p.then === 'function') {
                p.then((res) = > {
                    reslove(res)
                }, (err) = > {
                    reject(err)
                })
            }
        }
    })
}
Copy the code

Promise.any

Promise.myAll = function () {
    let args = arguments;
    return new Promise((reslove, reject) = > {
        // Check whether the first argument is iterable
        if (!Object.getPrototypeOf(args[0[])Symbol.iterator]) {
            throw new Error(`${args[0]} is not iterable`)}const iterable = args[0];
        let resArr = [],
            idx = 0,
            falg = 1;

        for (let p of iterable) {
            // Put an array of results according to type P
            if (typeof p.then === 'function') {
                p.then((res) = > {
                    reslove(res)
                }, (err) = > {
                    resArr[idx++] = res
                    if (++falg === iterable.length) {
                      reslove('AggregateError: No Promise in Promise.any was resolved')}}}else{ resArr[idx++] = p; falg++; }}})}Copy the code

Promise.allSettled

Promise.myAllSettled = function () {
    let args = arguments;
    return new Promise((reslove, reject) = > {

        // Check whether the first argument is iterable
        if (!Object.getPrototypeOf(args[0[])Symbol.iterator]) {
            throw new Error(`${args[0]} is not iterable`)}const iterable = args[0];
        let resArr = [],
            idx    = 0,
            flag   = 1;

        for (let p of iterable) {
            // Every time a state changes, check to see if all states have changed
            if (typeof p.then === 'function') {
                p.then((res) = > {
                    flag ++;
                    resArr[idx++] = {
                        'status': 'fulfiled'.'value': res
                    } 
                    if(flag === iterable.length) return reslove(resArr)
                }, (err) = > {
                    flag ++;
                    resArr[idx++] = {
                        'status': 'rejected'.'reason': err
                    } 
                    if(flag === iterable.length) return reslove(resArr)
                })
            } else {
                flag ++;
                resArr[idx++] = {
                    'status': 'fulfiled'.'value': p
                } 
                if(flag === iterable.length) return reslove(resArr)
            }
        }

    })
}
Copy the code

Materials & References

Information: source code implementation address: gitee.com/zhangboyu30… State diagram address: www.processon.com/view/link/6… Observer mode:

Reference: the observer pattern: c.biancheng.net/view/1390.h… MDN Promise:developer.mozilla.org/zh-CN/docs/…