Writing in the front

Promise is the core grammar in ES6, and it’s one of the most frequently asked questions in interviews, so knowing the rules is a must. Learning to write by hand will help you stand out in an interview and take a solid step toward getting an offer

In my previous article, I translated the Promise A+ specification. Before reading this article, I recommend reading: 【Promise】Promises/A+ Chinese translation

Write a Promise

An overview of the Promise

Promise, a scheme for managing asynchronous programming, is a constructor that creates instances each time available new is used; It has three states: The three states are not affected by the outside world. The status can only change from pending to fullfilled and the status of rejected will not change once the status changes. Once the status changes, the status will not change again. Resolve, Reject, catch, finally, then, All, race, and done are thrown. In the latest proposal, the allSettled method is added, which will return either success or failure. We implement the whole Promise ourselves

The executor function

As we know, when a Promise instance is created, the executor function is immediately executed. The executor function passes two arguments, resolve and reject. If the executor function executes incorrectly, the Promise instance status changes to Rejected

class MyPromise{
    constructor(executor) {
        this.status = "pending"; // Initialize status as pending this.value = undefined; Resolve (resolved); resolve (resolved); resolve (resolved)let resolve = result => {
            if(this.status ! = ="pending") return; // Once the status changes, it does not change this.status ="resolved"; this.value = result; Reject (reject); reject (reject); reject (reject); reject (reject); reject (reject); reject (reject)let reject = reason => {
            if(this.status ! = ="pending") return;
            this.status = "rejected"; this.value = reason; Reject try {executor(resolve, reject)} catch(err) {reject(err)}}}Copy the code

So let’s verify, what does Promise look like now

let p1 = new MyPromise((resolve, reject) => {
    resolve(1);
})

let p2 = new MyPromise((resolve, reject) => {
    reject(2);
})

console.log(p1);
console.log(p2);
Copy the code

As you can see, the state has changed, and the values inside are also the result of success and the cause of failure. The then method takes two arguments. The first argument is executed on success and the second argument is executed on failure. Chain calls to then are the same as array calls, and each execution returns a Promise instance. If the successful function in the first THEN is null, it will continue to look down until the function that is not NULL is executed. The result of the previous THEN directly affects which function succeeds or fails in the next THEN

Then method

then(resolveFn, rejectFn) {// If the two arguments passed are not functions, the result will be returned directlylet resolveArr = [];
    let rejectArr = [];
    
    if(typeof resolveFn ! = ="function") {
        resolveFn = result => {
            returnresult; }}if(typeof rejectFn ! = ="function") {
        rejectFn = reason => {
            returnMyPromise.reject(reason); }}return new Mypromise((resolve, reject) => {
        resolveArr.push(result => {
            try {
                let x = resolveFn(result);
                
                if(x instanceof MyPromise) {
                    x.then(resolve, reject)
                    return;
                }
                
                resolve(x);
            } catch(err) {
                reject(err)
            }
        })
        
        rejectArr.push(reason => {
            try {
                let x = rejectFn(reason);
                
                if(x instanceof MyPromise) {
                    x.then(resolve, reject)
                    return;
                }
                
                resolve(x);
            } catch(err) {
                reject(err)
            }
        })
    })
}
Copy the code

Let’s clean up the above code

class MyPromise{
    constructor(executor) {
        this.status = "pending"; // Initialize status as pending this.value = undefined; This.resolvearr = []; this.resolvearr = []; / / initializationthenThis.rejectarr = []; / / initializationthen// Define the change method, because it seems like resolve and reject have a lot in common 🤔let change = (status, value) => {
            if(this.status ! = ="pending") return; // This. Status = status; this.value = value; // Determine the successful or failed method to execute based on the statuslet fnArr = status === "resolved"? this.resolveArr : this.rejectArr; // the methods in fnArr execute fnarr.foreach (item => {if(typeof item ! = ="function") return; item(this. value); Resolve (resolved); resolve (resolved); resolve (resolved)let resolve = result => {
            change("resolved"Reject (reject); reject (reject); reject (reject); reject (reject); reject (reject)let reject = reason => {
            change("rejected", reason); Reject {executor(resolve, reject)} Catch (err) {reject(err)}}then(resolveFn, rejectFn) {// If the two arguments passed are not functions, the result will be returned directlyif(typeof resolveFn ! = ="function") {
            resolveFn = result => {
                returnresult; }}if(typeof rejectFn ! = ="function") {
            rejectFn = reason => {
                returnMyPromise.reject(reason); }}return new MyPromise((resolve, reject) => {
            this.resolveArr.push(result => {
                try {
                    letx = resolveFn(result); // If x is a Promise instance, continue the callthenMethods the = = >thenThe realization of the chainif(x instanceof MyPromise) {
                        x.then(resolve, reject)
                        return; } // Instead of a promise instance, execute the successful resolve(x) method directly; } catch(err) { reject(err) } }) this.rejectArr.push(reason => { try {let x = rejectFn(reason);
                    
                    if(x instanceof MyPromise) {
                        x.then(resolve, reject)
                        return;
                    }
                    
                    resolve(x);
                } catch(err) {
                    reject(err)
                }
            })
        })
    }
}
Copy the code

So let’s see what happens

new MyPromise((resolve, reject) => {
    resolve(1);
}).then(res => {
    console.log(res, 'success');
}, err => {
    console.log(err, 'error');
})
Copy the code

At this point, the problem arises and we find that there seems to be no output. What if we make a small change to the above test example?

new MyPromise((resolve, reject) => {
    setTimeout(_ => {
        resolve(1);
    }, 0)
}).then(res => {
    console.log(res, 'success'); / / 1"success"
}, err => {
    console.log(err, 'error');
})
Copy the code

This is because the executor function is executed immediately after the Promise instance is created, but the then method is not executed, so the array of successes and failures will be empty. Why does setTimeout work? This is because in the event queue mechanism, setTimeout will be placed in the event queue and executed after the main thread is completed. Then method will store the success or failure function, so either the success or failure array already has a value, and then execute completely 👌 ~

However, we can’t use setTimeout as a solution. Since we are encapsulating, we need to solve the problem in the encapsulated function. In this way, we can also use resolve and reject to determine whether there is a value in the array. We can use setTimeout to delay execution as follows

Resolve, execute on success, change the state to Resolved, and return the resultletResolve = result => {// If there are values in the array, change the state immediatelyif(this.resolveArr.length > 0) {
        change("resolved", result)} // If there is no value, the state change is delayedlet timer = setTimeout(_ => {
        change("resolved", result) clearTimeout(timer); Reject (reject); reject (reject); reject (reject); reject (reject); reject (reject); reject (reject)letReject = Reason => {// If there are values in the array, change the state immediatelyif(this.rejectArr.length > 0) {
        change("rejected", reason); } // If there is no value, change the state laterlet timer = setTimeout(_ => {
        change("rejected", reason); clearTimeout(timer); }}, 0)Copy the code

Now let’s try it again

New MyPromise((resolve, reject) => {resolve();'I did it, ho ho ho ~~~~');            
    reject('I've already made it, you can't make me fail, huh?');
}).then(res => {
    console.log(res, 'success'); ~~~~ success}, err => {console.log(err,'error'); New MyPromise((resolve, reject) => {reject();'FAILED, I am so aggrieved, whoo-hoo ~ ~');
    resolve('It has failed ~ ~ ~');            
}).then(res => {
    console.log(res, 'success');         
}, err => {
    console.log(err, 'error'); New MyPromise((resolve, reject) => {reject(resolve, reject))'FAILED, I am so aggrieved, whoo-hoo ~ ~');
    resolve('It has failed ~ ~ ~');            
}).then(res => {
    console.log(res);
}, err => {
    console.log(err, 'error'); // failed, I am so wronged, whoooo ~ ~ errorreturn 'I will work hard, will not be knocked down by difficulties, I will succeed!! '
}).then(res1 => {
    console.log(res1, 'After unremitting efforts, I finally succeeded in the second time.'); // I will work hard, will not be knocked down by difficulties, I want to succeed!! }, err1 => {console.log(err1,'Failed a second time');
})
Copy the code

This perfectly solves the problem of not executing the THEN method on the first call. At the same time, the implementation of the chain call. For the chained call, I’m going to say a couple of words, but in fact, the chained call of the array is because the last time it returned was the same instance.

Catch method

The catch method catches the exception, which is the same as the second callback of the then method

catch(rejectFn) {
    return this.then(null, rejectFn)
}
Copy the code

Resolve method

We know that Promsie can also be used this way

let p1 = MyPromise.resolve(1);

console.log(p1);
Copy the code

We expected one, but now we’re sure to throw an error: myPromise.resolve is not a method

Now we need to encapsulate the resolve method. We need to make it clear that after resolve, a Promise supports a chain call to THEN, so we need to execute the resolve method and return a Promise instance

Static resolve(result) {// Returns a new Promise instance, and executes the resolve method in the promise instancereturn new MyPromise(resolve => {
        resolve(result)
    })
}
Copy the code

Reject method

Just like the resolve method, except that it accepts a failed function

Static Reject (reason) {// Returns a new PROMISE instance, which executes the Reject method in the Promise instancereturnnew MyPromise((_, reject) => { reject(reason); })}Copy the code

The done method

In the introduction to the ES6 standards, the done method is explained as follows: Whether the callback chain of a Promise object ends in a then method or a catch method, if the last method throws an error, it may not be caught. To do this, Promise provides a done method that is always at the end of the fallback chain, guaranteed to throw any errors that might occur. Now that we know what this method does, let’s start writing

done(resolveFn, rejectFn) {
    this.then(resolveFn, rejectFn)
        .catch(reason => {
            setTimeout(() => { throw reason; }, 0)})}Copy the code

It can accept callback functions with the fulfilled and Rejected states, or it can provide no arguments. But anyway, the done method catches any possible errors and throws them globally, right

The finally method

A finally method is a method that executes whether it succeeds or fails. Methods like this and the complete method ina small program are examples

finally(finallyFn) {
    let P = this.constructor;
    return this.then(
        value => P.resolve(finallyFn()).then(() => value),
        reason => P.reject(finallyFn()).then(() => reason)
    )
}
Copy the code

So let’s verify that

new MyPromise((resolve, reject) => {
    reject('FAILED, I am so aggrieved, whoo-hoo ~ ~');
    resolve('It has failed ~ ~ ~');
}).then(res => {
    console.log(res);
}, err => {
    console.log(err, 'error'); // failed, I am so wronged, whoooo ~ ~ errorreturn 'I will work hard, will not be knocked down by difficulties, I will succeed!! '
}).finally(() => {
    console.log('Has it been executed?'); // This will output"Has it been carried out?"
})
Copy the code

All methods

The all method takes an array and returns it when each instance of the array succeeds. It also returns an array, with each argument returning the result of a corresponding promise. If any of the items fails, all returns a failure

Static all(promiseList) {// Returns a new instance, which can be used after the callthenCatch and other methodsreturn new MyPromise((resolve, reject) => {
        letIndex = 0, // Success count results = []; // The result returnedfor(let i = 0; i < promiseList.length; i++) {
            letitem = promiseList[i]; // If item is not a Promise instanceif(! (item instanceof MyPromise))return;
            
            item.then(result => {
                index++;
                results[i] = result;
                if(index === promiseList.length) { resolve(results); } }).catch(reason => { reject(reason); })}})}Copy the code

Let’s verify that

// 1. Failure occurslet p1 = MyPromise.resolve(1);

let p2 = MyPromise.reject(2);

let p3 = MyPromise.resolve(3);

MyPromise.all([p1, p2, p3])
    .then(res => {
        console.log(res);
    }).catch(err => {
        console.log(err, 'err'); / / 2"err"}) // 2. There is no failurelet p1 = MyPromise.resolve(1);

let p2 = MyPromise.resolve(2);

let p3 = MyPromise.resolve(3);

MyPromise.all([p1, p2, p3])
    .then(res => {
        console.log(res, 'success'); / / [1, 2, 3]"success"
    }).catch(err => {
        console.log(err, 'err');
    })
Copy the code

Race method

The RACE method also takes an array of parameters, each of which is a Promise instance, and returns the result of the Promise instance method that changed its state the fastest

static race(promiseList) {
    return new MyPromise((resolve, reject) => {
        promiseList.forEach(item => {
            if(! (item instanceof MyPromise))return;
            
            item.then(result => {
                resolve(result);
            }).catch(err => {
                reject(err)
            })
        })
    })
}
Copy the code

validation

/ / 1.let p1 = MyPromise.resolve(1);

let p2 = MyPromise.reject(2);

letp3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res); / / 1'success'
    }).catch(err => {
        console.log(err, 'err');    
    })

// 2.
let p1 = MyPromise.reject(1);

let p2 = MyPromise.resolve(2);

let p3 = MyPromise.resolve(3);

MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res, 'success');   
    }).catch(err => {
        console.log(err, 'err'); / / 1'err'}) / / 3.let p1 = MyPromise.reject(1);

let p2 = MyPromise.reject(2);

let p3 = MyPromise.reject(3);

MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res, 'success');   
    }).catch(err => {
        console.log(err, 'err'); / / 1'err'
    })
Copy the code

Try implementing the allSettled method

The allSettled method also receives array parameters, but it returns either on success or failure

static allSettled(promiseList) {
    return new MyPromise((resolve, reject) => {
        let results = [];
        
        for(let i = 0; i < promiseList.length; i++) {
            let item = promiseList[i];
            
            if(! (item instanceof MyPromise))return; item.then(result => { results[i] = result; }, reason => { results[i] = reason; }) resolve(results); }})}Copy the code

validation

/ / 1.let p1 = MyPromise.resolve(1);

let p2 = MyPromise.resolve(2);

letp3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res); / / [1, 2, 3]'success'
    }).catch(err => {
        console.log(err, 'err');    
    })

// 2.
let p1 = MyPromise.reject(1);

let p2 = MyPromise.reject(2);

let p3 = MyPromise.reject(3);

MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res, 'success'); / / [1, 2, 3]'success'
    }).catch(err => {
        console.log(err, 'err'); }) / / 3.let p1 = MyPromise.resolve(1);

let p2 = MyPromise.reject(2);

let p3 = MyPromise.resolve(3);

MyPromise.race([p1, p2, p3])
    .then(res => {
        console.log(res, 'success'); / / [1, 2, 3]'success'
    }).catch(err => {
        console.log(err, 'err');       
    })
Copy the code

The last

This article implements a Promise of their own, source code has been uploaded to Github: MyPromise source address. Get it by yourself if you need it

You can also follow my public account “Web front-end diary”, more timely to receive push messages ~