Deepen the understanding of promise, step by step to implement and improve promise and API implementation.

Resolve and Reject are implemented

Resolve and reject are used to determine the state of a Promise object after a new Promise. The sequence of the two functions in the code determines the state of the Promise object. Once the state changes, it is not modified

The difficulty of implementation is that the promise state cannot be modified again after the state changes.

Console. log('start') const p1 =new promise ((resolve,reject)=>{console.log("doing")) resolve(1) reject(2) }) console.log('end',p1) // start // doing // end Promise {<fulfilled>: FULFILLESSTATUS ='pending' static FULFILLESSTATUS =' fulfilled' static REJECTEDSTATUS =' fulfilled 'rejected' constructor(executor){ this.PromiseState = MPromise.PENDINGSTATUS; this.PromiseResult = null; // Resolve and reject execute this as window, Bind this executor(this.resolve. Bind (this),this.reject. Bind (this))} resolve(res){if(this.promisestate === MPromise.PENDINGSTATUS){ this.PromiseResult = res; this.PromiseState = MPromise.FULFILLESSTATUS; } } reject(err){ if(this.PromiseState === MPromise.PENDINGSTATUS){ this.PromiseResult = err; this.PromiseState = MPromise.REJECTEDSTATUS; } } } console.log('start') const p2 =new MPromise((resolve,reject)=>{ console.log("doing") resolve(1) reject(2) }) console.log('end',p2) // start // doing // end MPromise { PromiseState: 'fulfilled', PromiseResult: 1 }Copy the code

The realization of the Promise. Prototype. Then

Basic usage

instructions

  1. Asynchronous execution
  1. The constructor’s executor argument is executed when resolve is called, and ondepressing is executed then
  1. The executor argument to the constructor calls reject, and then calls onRejected
/ / sample
console.log('start')
const p1 = new Promise((resolve, reject) = > {
    resolve(1)
}).then(res= > {
    console.log('p1->then', res)
})
​
const p2 = new Promise((resolve, reject) = > {
    reject(2)
}).then(res= >{},err= > {
    console.log('p2->then', err)
})
​
console.log('end')
// start
// end
// p1->then 1
// p2->then 2
Copy the code

Implementation idea:

  1. The then function is used to register the callback function, onFulfilled, and the onFulfilled callback function is added to the queue
  2. The resolve/reject function executes the callback asynchronously

Take new Promise(resolve=>resolve(1)). Then (res=> RES) as an example to illustrate the implementation process

  1. Pending state: Synchronization code is executed

    • performresolve=>resolve(1)Function, executeresolve(1)function
    • The code to be executed is encapsulated as function fn and added to the micro task. The functions of this function include: modifying the state to be fulfilled, modifying the PromiseResult value, and executing the function in the resolvedQueue
    • performthen(res=>res)Function, add ondepressing function namelyres=>resTo the queue array resolvedQueue
  2. Pending → depressing state: Asynchronous execution of FN in micro-task

    • The state is modified as depressing
    • PromiseResult value change
    • The arrays in the resolvedQueue execute sequentially
class MPromise {
    static PENDINGSTATUS = 'pending'
    static FULFILLESSTATUS = 'fulfilled'
    static REJECTEDSTATUS = 'rejected'
    constructor(executor) {
        this.PromiseState = MPromise.PENDINGSTATUS;
        this.PromiseResult = null;
        // +++ the following code blocks are new
        // Store the ondepressing, onRejected callback function which needs to be performed in then function
        this.resolvedQueue = [];
        this.rejectedQueue = [];
        // +++
        // Resolve and reject execute this with the value window and bind this
        executor(this.resolve.bind(this), this.reject.bind(this));
    }
    resolve(res) {
        if (this.PromiseState === MPromise.PENDINGSTATUS) {
            // Use if to determine the state, so that the state cannot be changed again
            // +++ the following code blocks are new
            // This is the big pity function that can be saved in the resolvedQueue asynchronously
            const fn = () = > {
                this.PromiseResult = res;
                this.PromiseState = MPromise.FULFILLESSTATUS;
                this.resolvedQueue.length && this.resolvedQueue.forEach(cb= > cb(res))
            }
            // Enable the microqueue
            resultHandlerAsync(fn)
            // +++}}reject(err) {
        if (this.PromiseState === MPromise.PENDINGSTATUS) {
            // +++ the following code blocks are new
            // Run the onRejected function in the rejectedQueue asynchronously
            const fn = () = > {
                this.PromiseResult = err;
                this.PromiseState = MPromise.REJECTEDSTATUS;
                this.rejectedQueue.length && this.rejectedQueue.forEach(cb= > cb(err))
            }
            // Enable the microqueue
            resultHandlerAsync(fn)
            // +++}}// +++ the following code blocks are new
    then(onFulfilled, onRejected) {
        // The then function registers the callback function, which is to be performed ondepressing, and the onRejected callback function is added to the queue
        if (this.PromiseState === MPromise.PENDINGSTATUS) {
            this.resolvedQueue.push(onFulfilled)
            this.rejectedQueue.push(onRejected)
        }
    }
    // +++

}

/ / test
console.log('start')
const p3 = new MPromise((resolve, reject) = > {
    resolve(1)
}).then(res= > {
    console.log('p3->then', res)
})

const p4 = new MPromise((resolve, reject) = > {
    reject(2)
}).then(res= >{},err= > {
    console.log('p4->then', err)
})

console.log('end')
// start
// end
// p3->then 1
// p4->then 2
Copy the code

Chain calls to THEN

Functions to be implemented are as follows:

  1. OnFulfilled, onFulfilled. The callback function returns the value or Error as parameter to the next onFulfilled, onFulfilled
  2. Then is still a Promise object after execution
/ / sample
console.log('start')
new Promise(resolve= > {
    resolve(1)
})
.then(res= > res + 1)
.then(res= > { 
    console.log("resolve(1) ~ then:return 1+1 ~ then: res", res) 
})
​
new Promise(resolve= > {
    resolve(2)
})
.then(res= > { throw new Error(res + 1) })
.then(res= > { console.log(res) }, err= > { 
  console.log("resolve(2) ~ then:throw Err 2+1 ~ then: err", err) 
})
​
new Promise((resolve, reject) = > {
    reject(3)
})
.then(() = >{},err= > err + 1)
.then(res= > { 
    console.log("reject(3) ~ then:return 3+1 ~ then: res", res) 
})
​
new Promise((resolve, reject) = > {
    reject(4)
})
.then(() = >{},err= > { throw new Error(err+1) })
.then(res= > { console.log(res) }, err= > { 
    console.log("reject(4) ~ then:throw Err 4+1 ~ then: err", err) 
})
console.log('end')
​
// start
// end
// resolve(1) ~ then:return 1+1 ~ then: res 2
// resolve(2) ~ then:throw Err 2+1 ~ then: err Error: 3
// reject(3) ~ then:return 3+1 ~ then: res 4
// reject(4) ~ then:throw Err 4+1 ~ then: err Error: 5
Copy the code

Implementation idea:

  1. Then creates a promise and returns it
  2. Push to resolvedQueue/rejectedQueue function includes two steps, implement onFulfilled/onRejected and resolve/reject.

Use new Promise(resolve=>resolve(1)).then(res=> RES +1).then(res=>console.log(res)) as an example to illustrate the implementation process

  1. Pending state: Synchronization code is executed

    • new PromisePerform:resolve=>resolve(1)perform
    • resolve(1)Execution: The code to be executed is encapsulated into the function FN and added to the micro task. The functions of FN include: modifying the state to be fulfilled, modifying the PromiseResult value, and executing the function in the resolvedQueue
    • Then perform: This function will perform onFulfilled/onRejected and resolve/Reject (onFulfilled/onRejected returns). This function will perform onFulfilled/onRejected (onFulfilled/onRejected returns).
class MPromise {
    static PENDINGSTATUS = 'pending'
    static FULFILLESSTATUS = 'fulfilled'
    static REJECTEDSTATUS = 'rejected'
    constructor(executor) {
        this.PromiseState = MPromise.PENDINGSTATUS;
        this.PromiseResult = null;
        // Store the ondepressing, onRejected callback function which needs to be performed in then function
        this.resolvedQueue = [];
        this.rejectedQueue = [];
        // Resolve and reject execute this with the value window and bind this
        executor(this.resolve.bind(this), this.reject.bind(this));
    }
    resolve(res) {
        // the following code has been modified
        if (this.PromiseState === MPromise.PENDINGSTATUS) {
            // Use if to determine the state, so that the state cannot be changed again
            // Asynchronously execute the ondepressing function saved in the resolvedQueue
            // Enable the microqueue
            resultHandlerAsync(() = > {
                this.PromiseResult = res;
                this.PromiseState = MPromise.FULFILLESSTATUS;
                if (this.resolvedQueue.length) {
                    const cb = this.resolvedQueue.shift();
                    cb(res)
                }
            })
        }
        // +++
    }
    reject(err) {
        // the following code has been modified
        if (this.PromiseState === MPromise.PENDINGSTATUS) {
            // Run the onRejected function in the rejectedQueue asynchronously
            // Enable the microqueue
            resultHandlerAsync(() = > {
                this.PromiseResult = err;
                this.PromiseState = MPromise.REJECTEDSTATUS;
                if (this.rejectedQueue.length) {
                    const cb = this.rejectedQueue.shift();
                    cb(err)
                }
            })
        }
        // +++
    }
    then(onFulfilled, onRejected) {
        // The then function registers the callback function, which is to be performed ondepressing, and the onRejected callback function is added to the queue
        // the following code has been modified
        return new MPromise((resolve, reject) = > {
            if (this.PromiseState === MPromise.PENDINGSTATUS) {
                // This is a big pity. The return values will be passed to the parameters of ondepressing in the next then
                this.resolvedQueue.push((res) = > {
                    try {
                        const result = onFulfilled(res);
                        resolve(result);
                    } catch (err) {
                        reject(err)
                    }
                });
                this.rejectedQueue.push((err) = > {
                    try {
                        const result = onRejected(err);
                        resolve(result);
                    } catch(err) { reject(err) } }); }})// +++}}function resultHandlerAsync(cb) {
    // console.log('async')
    // This function will start a microtask, cb into the microtask execution
    const observer = new MutationObserver(cb);
    observer.observe(document.body, { attributes: true });
    document.body.className = `The ${Math.random()}`;
}
​
console.log('start')
new MPromise(resolve= > {
    resolve(1)
})
.then(res= > res + 1)
.then(res= > { 
    console.log("resolve(1) ~ then:return 1+1 ~ then: res", res) 
})
​
new MPromise(resolve= > {
    resolve(2)
})
.then(res= > { throw new Error(res + 1) })
.then(res= > { console.log(res) }, err= > { 
  console.log("resolve(2) ~ then:throw Err 2+1 ~ then: err", err) 
})
​
new MPromise((resolve, reject) = > {
    reject(3)
})
.then(() = >{},err= > err + 1)
.then(res= > { 
    console.log("reject(3) ~ then:return 3+1 ~ then: res", res) 
})
​
new MPromise((resolve, reject) = > {
    reject(4)
})
.then(() = >{},err= > { throw new Error(err+1) })
.then(res= > { console.log(res) }, err= > { 
    console.log("reject(4) ~ then:throw Err 4+1 ~ then: err", err) 
})
console.log('end')
// start
// end
// resolve(1) ~ then:return 1+1 ~ then: res 2
// resolve(2) ~ then:throw Err 2+1 ~ then: err Error: 3
// reject(3) ~ then:return 3+1 ~ then: res 4
// reject(4) ~ then:throw Err 4+1 ~ then: err Error: 5
Copy the code

Arguments to then are non-functions

/ / sample
new Promise((resolve, reject) = > {
    resolve(1)
}).then().then(res= > { console.log("resolve(1) ~ undefined ~ then: res", res) })

new Promise((resolve, reject) = > {
    reject(2)
}).then().then(undefined.err= > { console.log("reject(2) ~ undefined ~ then: err", err) })

// resolve(1) ~ undefined ~ then: res 1
// reject(2) ~ undefined ~ then: err 2
Copy the code

Implementation idea:

Check whether the passed argument is a function, if so, do nothing; If not, create a function that returns the value of resolve or throws an error

then(onFulfilled, onRejected) {
    // +++ The following code is new
    onFulfilled = (typeof onFulfilled === 'function')? onFulfilled :res= > res;
    onRejected = (typeof onRejected === 'function')? onRejected :err= > { throw err };
    // +++
    return new MPromise(...)
}
                        
new MPromise((resolve, reject) = > {
    resolve(1)
}).then().then(res= > { console.log("resolve(1) ~ undefined ~ then: res", res) })
​
new MPromise((resolve, reject) = > {
    reject(2)
}).then().then(undefined.err= > { console.log("reject(2) ~ undefined ~ then: err", err) })
​
// resolve(1) ~ undefined ~ then: res 1
// reject(2) ~ undefined ~ then: err 2
Copy the code

Promise. Prototype. Catch and Promise. The prototype. The realization of the finally

The catch method returns a Promise and handles the rejection.

The finally method returns a Promise. At the end of the promise, the specified callback function will be executed, whether the result is fulfilled or Rejected

/ / sample
new Promise((resolve, reject) = > {
    reject(1)
})
.catch(err= > { 
    console.log("reject(1) ~ then: err", err) 
})
​
new Promise((resolve, reject) = > {
    reject(2)
})
.catch()
.then(() = >{},err= > { 
    console.log("reject(2) ~ catch:undefined ~ then: err", err) 
})
​
new Promise((resolve, reject) = > {
    reject(3)
})
.catch(err= > err + 1)
.finally((value) = > { 
    console.log("reject(3) ~ catch: err=>err+1 ~ finally:return 22", value); 
    return 22;
})
.then(res= > { 
    console.log("reject(3) ~ catch: err=>err+1 ~ finally:return 22 ~ then: err", res)
})
// reject(1) ~ then: err 1
// reject(2) ~ catch:undefined ~ then: err 2
// reject(3) ~ catch: err=>err+1 ~ finally:return 22 undefined
// reject(3) ~ catch: err=>err+1 ~ finally:return 22 ~ then:err 4
Copy the code

Implementation:

Catch method = then(undefined,onRejected)

Finally method: Asynchronously executes passed callback arguments while passing the result of the previous THEN to the next

catch(onRejected) {
    return this.then(undefined,onRejected)
}
finally(cb){
    return new MPromise((resolve,reject) = >{
        if (this.PromiseState === MPromise.PENDINGSTATUS) {
            this.resolvedQueue.push((res) = > {
                try {
                    cb()
                    resolve(res);
                } catch (err) {
                    reject(err)
                }
            });
            this.rejectedQueue.push((err) = > {
                try {
                    cb();
                    resolve(err);
                } catch(err) { reject(err) } }); }})}new MPromise((resolve, reject) = > {
    reject(1)
})
.catch(err= > { 
    console.log("reject(1) ~ then: err", err) 
})
​
new MPromise((resolve, reject) = > {
    reject(2)
})
.catch()
.then(() = >{},err= > { 
    console.log("reject(2) ~ catch:undefined ~ then: err", err) 
})
​
new MPromise((resolve, reject) = > {
    reject(3)
})
.catch(err= > err + 1)
.finally((value) = > { 
    console.log("reject(3) ~ catch: err=>err+1 ~ finally:return 22", value); 
    return 22;
})
.then(res= > { 
    console.log("reject(3) ~ catch: err=>err+1 ~ finally:return 22 ~ then: err", res)
})
// reject(1) ~ then: err 1
// reject(2) ~ catch:undefined ~ then: err 2
// reject(3) ~ catch: err=>err+1 ~ finally:return 22 undefined
// reject(3) ~ catch: err=>err+1 ~ finally:return 22 ~ then:err 4
Copy the code

Promise. Resolve and Promise. Reject are implemented

The promise. resolve method returns a Promise object resolved with the given value

The promise. reject method returns a Promise object with a reason for the rejection

implementation

static resolve(res) {
    return new MPromise(resolve= > resolve(res))
}
static reject(res) {
    return new MPromise((resolve, reject) = > reject(res))
}
Copy the code

Promise.all and promise. race are implemented

The promise.all () method takes an iterable type of a Promise (Array, Map, Set) and returns only one Promise object. The return value is the result of all resolve, and if any reject callback executes, an error is immediately thrown.

The promise.race (iterable) method returns a Promise that is resolved or rejected once a Promise in the iterator is resolved or rejected.

/ / sample
const p1 = new Promise((resolve, reject) = > {
    setTimeout(resolve, 500.'one');
});
​
const p2 = new Promise((resolve, reject) = > {
    setTimeout(resolve, 100.'two');
});
​
const p3 = new Promise((resolve, reject) = > {
    setTimeout(reject, 300.'three');
});
​
Promise.race([p1, p2]).then((value) = > {
    console.log('Promise.race', value);
});
​
Promise.all([p1, p2, p3]).then((value) = > {
    console.log('Promise.all ~ then:res', value);
}).catch(err= >{console.log('Promise.all ~ catch:err', err)});
​
Promise.all([p1, p2]).then((value) = > {
    console.log('Promise.all', value);
});
// Promise.race two
// Promise.all ~ catch:err three
// Promise.all ['one', 'two']
Copy the code
static race(promiseList) {
    return new MPromise((resolve, reject) = > {
        promiseList.forEach(p= > p.then(res= > resolve(res), err= > reject(err)))
    })
}
static all(promiseList) {
    return new MPromise((resolve, reject) = > {
        let promiseArr = [];
        promiseList.forEach(p= > {
            p.then(res= > {
                promiseArr.push(res)
            }, err= > {
                throw new Error(err); reject(err); }) }) resolve(promiseArr); })}Copy the code

The complete code

class MPromise {
    static PENDINGSTATUS = 'pending'
    static FULFILLESSTATUS = 'fulfilled'
    static REJECTEDSTATUS = 'rejected'
    constructor(executor) {
        this.PromiseState = MPromise.PENDINGSTATUS;
        this.PromiseResult = null;
        // Store the ondepressing, onRejected callback function which needs to be performed in then function
        this.resolvedQueue = [];
        this.rejectedQueue = [];
        // Resolve and reject execute this with the value window and bind this
        executor(this.resolve.bind(this), this.reject.bind(this));
    }
    resolve(res) {
        // console.log('resolve')
        if (this.PromiseState === MPromise.PENDINGSTATUS) {
            // Use if to determine the state, so that the state cannot be changed again
            // Asynchronously execute the ondepressing function saved in the resolvedQueue
            // Enable the microqueue
            resultHandlerAsync(() = > {
                this.PromiseResult = res;
                this.PromiseState = MPromise.FULFILLESSTATUS;
                if (this.resolvedQueue.length) {
                    const cb = this.resolvedQueue.shift();
                    cb(res)
                }
            })
        }
    }
    reject(err) {
        if (this.PromiseState === MPromise.PENDINGSTATUS) {
            // Run the onRejected function in the rejectedQueue asynchronously
            // Enable the microqueue
            resultHandlerAsync(() = > {
                this.PromiseResult = err;
                this.PromiseState = MPromise.REJECTEDSTATUS;
                if (this.rejectedQueue.length) {
                    const cb = this.rejectedQueue.shift();
                    cb(err)
                }
            })
        }
    }
    then(onFulfilled, onRejected) {
        // The then function registers the callback function, which is to be performed ondepressing, and the onRejected callback function is added to the queue
        onFulfilled = (typeof onFulfilled === 'function')? onFulfilled :res= > res;
        onRejected = (typeof onRejected === 'function')? onRejected :err= > { throw err };
        return new MPromise((resolve, reject) = > {
            if (this.PromiseState === MPromise.PENDINGSTATUS) {
                // This is a big pity. The return values will be passed to the parameters of ondepressing in the next then
                this.resolvedQueue.push((res) = > {
                    try {
                        const result = onFulfilled(res);
                        resolve(result);
                    } catch (err) {
                        reject(err)
                    }

                });
                this.rejectedQueue.push((err) = > {
                    try {
                        const result = onRejected(err);
                        resolve(result);
                    } catch(err) { reject(err) } }); }})}catch(onRejected) {
        return this.then(undefined, onRejected)
    }
    finally(cb) {
        return new MPromise((resolve, reject) = > {
            if (this.PromiseState === MPromise.PENDINGSTATUS) {
                // This is a big pity. The return values will be passed to the parameters of ondepressing in the next then
                this.resolvedQueue.push((res) = > {
                    try {
                        cb()
                        resolve(res);
                    } catch (err) {
                        reject(err)
                    }
                });
                this.rejectedQueue.push((err) = > {
                    try {
                        cb();
                        resolve(err);
                    } catch(err) { reject(err) } }); }})}static resolve(res) {
        return new MPromise(resolve= > resolve(res))
    }
    static reject(res) {
        return new MPromise((resolve, reject) = > reject(res))
    }
    static race(promiseList) {
        return new MPromise((resolve, reject) = > {
            promiseList.forEach(p= > p.then(res= > resolve(res), err= > reject(err)))
        })
    }
    static all(promiseList) {
        return new MPromise((resolve, reject) = > {
            let promiseArr = [];
            promiseList.forEach(p= > {
                p.then(res= > {
                    promiseArr.push(res)
                }, err= > {
                    throw new Error(err); reject(err); }) }) resolve(promiseArr); }}})function resultHandlerAsync(cb) {
    // This function will start a microtask, cb into the microtask execution
    const observer = new MutationObserver(cb);
    observer.observe(document.body, { attributes: true });
    document.body.className = `The ${Math.random()}`;
}
Copy the code