Preface:

Promise is one of the new features of ES6, which has been widely used in practical development. It is also frequently asked questions in interviews, such as: using Promise to investigate the event loop mechanism, several states of Promise, how to use Promise to deal with asynchrony and so on. The best way to solve the many questions raised by promises is not case by case, but to learn more about promises and follow through. Understand Promsie thoroughly, and these problems will be solved.

Implement Promise by hand

Take a look at a simple use example of promise:

    let p=new Promise((resolve,reject) = >{
        console.log('initialize');
        resolve('success');
    })
    .then((res,rej) = >{
        console.log('then execute',res)}) - initializes - then executes successCopy the code

From easy to difficult, we started with simple implementation, and then added little by little improvements. First we implement resolve and reject. From above 🌰 we need to note a few things:

  1. Promise has three states, namely PENDING, depressing, and REJECTED.
  2. When a promise is initialized, it immediately executes the function passed in, which changes the state of the promise –> prints’ initialize ‘and changes the state of the promise;
  3. From PENDING to FULFILLED state or REJECTED state, but once the state is changed, it cannot be changed again.
  4. The promise performs the resolve function, which is FULFILLED, and passes the value to the RES parameter in the THEN.

(1) Based on the above four pieces of information, we can actually write the first version of the code:

class HD{
    // Define three states
    static PENDING="pending";
    static FULFILLED="fulfiled";
    static REJECTED="rejected";
    // The class executes functions automatically when instantiated
    constructor (executor) {// define the initial value as undefined, which is used to save the arguments passed in resolve,reject
        this.value=undefined;
        // Define the initial state as pending
        this.status=HD.PENDING;
        / / add try... Log (a). If a does not return an error, execute the Rejected function
        try {
            executor(this.resolve,this.rejected);
        } catch (error) {
            this.rejected(error)
        }
    }

    // Define resolve
    resolve=(value) = >{
        // Only the state is pending can be changed to another state
        if(this.status == HD.PENDING){
            // Change promsie status
            this.status=HD.FULFILLED;
            //this.value records the value passed to resolve for use by the then function
            this.value=value; }}/ / same as above
    rejected=(reason) = >{
        if(this.status == HD.PENDING){
            this.status=HD.REJECTED;
            this.value=reason;  
        } 
    }
    then=(onFulfilled,onRejected) = >{
        // Execute different functions in THEN according to different states
        if(this.status==HD.FULFILLED){
            let result=onFulfilled(this.value);
        };
        if(this.status==HD.REJECTED){
            let result=onRejected(this.value); }; }}let p=new HD((res,rej) = >{
        console.log('initialize');
        res('success');
    })
    .then((value) = >{
        console.log(value)}) Output: - initialization - successCopy the code

(2) Asynchronously change state in excutor

The code above hides a problem such as:

    let p=new HD((res,rej) = >{
        setTimeout(() = >{
            console.log('initialize');
            res('success');
        })
    })
    .then((value) = >{
        console.log(value)
    })
Copy the code

Only ‘initialization’ is printed, not SUCCESS. Since setTimeout is an asynchronous task, when executing THEN, the state of the promise is still pending, so we need to continue to modify the above code (the new code is marked with * before and after, the same below) :

class HD{
    // Define three states
    static PENDING="pending";
    static FULFILLED="fulfiled";
    static REJECTED="rejected";
    
    / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // Define an empty array to hold callbacks to then to prevent executor from changing status asynchronously
    this.callbacks=[];
    / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    
    // The class executes functions automatically when instantiated
    constructor (executor) {// define the initial value as undefined, which is used to save the arguments passed in resolve,reject
        this.value=undefined;
        // Define the initial state as pending
        this.status=HD.PENDING;
        / / add try... Log (a). If a does not return an error, execute the Rejected function
        try {
            executor(this.resolve,this.rejected);
        } catch (error) {
            this.rejected(error)
        }
    }
    // Define resolve
    resolve=(value) = >{
        // Only the state is pending can be changed to another state
        if(this.status == HD.PENDING){
            // Change promsie status
            this.status=HD.FULFILLED;
            //this.value records the value passed to resolve for use by the then function
            this.value=value;  
            
            / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
            // Execute the fulfilled function in the callbacks. Ensure that the THEN callback can be executed when resolve is asynchronous
            this.callbacks.map((callback) = >{
               callback.onFulfilled(this.value)
            })
            / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}}/ / same as above
    rejected=(reason) = >{
        if(this.status == HD.PENDING){
            this.status=HD.REJECTED;
            this.value=reason;  
            
            / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
            this.callbacks.map((callback) = >{
               callback.onRejected(this.value)
            })
            
            / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
        } 
    }
    then=(onFulfilled,onRejected) = >{
    
    / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
        // If the state is pending, store the then callback into the callbacks
        if(this.status==HD.PENDING){
            this.callbacks.push({
                onFulfilled:value= >{onFulfilled(value)},
                onRejected:value= >{onRejected(value)}
            })
        }; 
   / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
   
        // Execute different functions in THEN according to different states
        if(this.status==HD.FULFILLED){
            let result=onFulfilled(this.value);
        };
        if(this.status==HD.REJECTED){
            let result=onRejected(this.value); }; }}let p=new HD((res,rej) = >{
        console.log('initialize');
        res('success');
    })
    .then((value) = >{
        console.log(value)}) output: - initializers - successCopy the code

We use promise, and the most desired function should be the chain call of THEN. Obviously, then should return a PROMsie object, so that we can continue to call then methods, so as to realize the chain call of THEN. We continue to modify the above code:

        class HD{
        // Define three states
        static PENDING="pending";
        static FULFILLED="fulfiled";
        static REJECTED="rejected";
        
        // The class executes functions automatically when instantiated
        constructor (executor) {this.callbacks=[];
            // define the initial value as undefined, which is used to save the arguments passed in resolve,reject
            this.value=undefined;
            // Define the initial state as pending
            this.status=HD.PENDING;
            / / add try... Log (a). If a does not return an error, execute the Rejected function
            try {
                executor(this.resolve,this.rejected);
            } catch (error) {
                this.rejected(error)
            }
        }
        // Define resolve
        resolve=(value) = >{
            // Only the state is pending can be changed to another state
            if(this.status == HD.PENDING){
                // Change promsie status
                this.status=HD.FULFILLED;
                //this.value records the value passed to resolve for use by the then function
                this.value=value; 
                this.callbacks.map((callback) = >{
                    callback.onFulfilled(this.value)
                })
            } 
        }
        / / same as above
        rejected=(reason) = >{
            if(this.status == HD.PENDING){
                this.status=HD.REJECTED;
                this.value=reason;  
                this.callbacks.map((callback) = >{
                    callback.onRejected(this.value)
                })
            } 
        }
        then=(onFulfilled,onRejected) = >{
        
        / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
            var promise=new HD((resolve,reject) = >{
                // Execute different functions in THEN according to different states
                if(this.status==HD.FULFILLED){
                    try {
                        let result=onFulfilled(this.value)
                        //then returns the promise processing
                        if(result instanceof HD){
                            // If ondepressing returns a promise object, this will be further processed. The promise can not be returned directly
                            // result.then(value=>{
                            // // If result returned by then is a promise, then returns the value of resolve(value) so that the value returned by then is the value of result resolve
                            // resolve(value)
                            // },reason=>{
                            // reject(reason)
                            // })
                            // This sentence is a simplification of the previous paragraph
                            result.then(resolve,reject);

                        }else{
                            // If a promise object is not returned, return it
                            resolve(result)
                        }
                    } catch (error) {
                        reject(error)
                    }
                };
                if(this.status==HD.REJECTED){
                    try {
                        let result=onRejected(this.value)
                        if(result instanceof HD){
                            // resolve,reject is the parent
                            result.then(resolve,reject);
                        }else{
                            resolve(result)
                        }
                    } catch (error) {
                        reject(error)
                    }
                };
                if(this.status==HD.PENDING){
                    this.callbacks.push({
                        onFulfilled:value= >{
                            try {
                                let result=onFulfilled(value)
                                if(result instanceof HD){
                                    // Use the parent's resolve, reject to change the parent's value and state
                                    result.then(resolve,reject);
                                }else{
                                    resolve(result)
                                }
                            } catch (error) {
                                Reject (reject) {reject (reject); reject (reject); reject (reject)
                                reject(error)
                            }
                        },
                        onRejected:value= >{
                            try {
                                let result=onRejected(value)
                                // resolve(result)
                                // Processing then returns promise
                                if(result instanceof HD){
                                    result.then(resolve,reject);
                                }else{
                                    resolve(result)
                                }
                            } catch (error) {
                                // onRejected(error)
                                reject(error)
                            }
                        }
                    })
                }
            })
            return promise
        };
    / / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    }

let p=new HD((resolve,reject) = >{
            setTimeout(() = >{
                resolve('success1')},1000)
        })
        .then(value= >{
            return new HD((resolve,reject) = >{
                reject('reject')})},reason= >{
            return new HD((resolve,reject) = >{
                reject('reject2')
            })
        })
        .then(value= >{
            console.log('value',value)
        },reason= >{
            console.log('reason',reason)}) -reason rejectCopy the code

The above modification is mainly in the THEN function, including two aspects:

  1. The then method returns a Promise object;
  2. If a Promise object is still returned after the then callback, then calls the Promise object so that the return of THEN cannot be a nested promsie object.

(4) implementation Promise. Resolve, Promise. Reject to Promise. Resolve (). Then () forms, it can be seen that Promise. Resolve the returned object is also a Promise, And the Promise state should change to resolve. Promise. Reject again. So we can write these two functions and add two static methods to the code above:

        static resolve=(value) = >{
            return new HD((resolve,rejected) = >{
                resolve(value)
            })
        }
        static reject=(value) = >{
            return new HD((resolve,reject) = >{
                reject(value)
            })
        }
        
       HD.resolve('success111')
        .then((res) = >{
            console.log(res) }); -success111Copy the code

(5) Implement promise.all () method Promise.all can wrap multiple Promise instances into a new Promise instance. Also, success and failure return different values, with success returning an array of results and failure returning the first rejected state. Add static all methods:

     static all=(promises) = >{
            let value=[];
            return new HD((resolve,rejected) = >{
                promises.map((promise) = >{
                    promise.then((res) = >{
                        value.push(res);
                        if(value.length==promises.length){
                            resolve(value)
                        }
                        },(rej) = >{
                            rejected(rej)
                    })
                })
            })
        }
        
        let p=new HD((resolve,reject) = >{
        resolve('4')});let m=new HD((resolve,reject) = >{
        resolve('5')}); HD.all([p,m]).then((val) = >{console.log(val)},(res) = >{console.log(res)})'4'.'5']
Copy the code

If a promise function (rejected) executes the then function, then all returns the promise state (rejected) and returns the failed result. If a promise function (Rejected) executes the then function, then all returns the promise state (rejected). Other promises are suspended. If all incoming promises are in the resolve state, then the return value of each promSIE is saved with value, and the final promSIE result is returned when all promsie have been executed.

The promsie.race method is simpler to implement than the promise.all () method. As the name suggests, promse.race stands for race, which means, Promise.race([P1, P2, p3]) returns the fastest result, regardless of whether the result itself is a success or failure state. Add static race methods:

     static race=(promises) = >{
            return new HD((resolve,rejected) = >{
                promises.map((promise) = >{
                    promise.then(res= >{
                        resolve(res)
                    },rej= >{
                        rejected(rej)
                    })
                })
            })
        }
        
    let p=new HD((resolve,reject) = >{
        setTimeout(() = > {
            resolve('1')},2000);
    });
    let m=new HD((resolve,reject) = >{
        setTimeout(() = > {
            resolve('2')},1000);
    });
    HD.race([p,m]).then((val) = >{console.log(val)},(res) = >{console.log(res)}) output after one second:2
Copy the code

The race implementation is simple: Once a promise executes a then callback, call resolve or Rejected to change the promsie state returned by HD.race.

conclusion

The above code implements a promsie function that supports asynchronous calls, chain callbacks to THEN, resolve, Rejected, all, race, and more. Through the process of writing promises, we have a deeper understanding of promises. If the code does not understand, insufficient or incorrect place, welcome everyone in the comment area, refueling 💪. thank you