The introduction

Promise came along to solve the problem of callback hell in JS, to make the code cleaner, and was a specification and important feature in ES6. It’s easy to use, but do you know how it works? Now let’s take a look at how Promise actually works 😄

  • Promise specification

    The Promise specification is the rule that the Promise function needs to follow, and for THE Promise used in ES6, it followsPromise/A + specification.

    Since there are rules to follow, let’s follow them step by stepPromise.

1. Create the Promise class

See how promises are used

    const promise = new Promise((resolve, reject) = > {
        try {
            resolve('123');
        } catch(err) {
            reject('error'); }}); promise .then((msg) = > {
        console.log(msg)
    })
Copy the code
  • First of all it’s aA constructorAnd receive onefunctionAs a parameter,

    And thisfunctionThere areresolveandrejectTwo methods.resolveRepresents the value returned after success,rejectRepresents reason for refusing to return

Create a class called MyPromise based on the use of the Promise


    /** * @params {function} callback */
    class MyPromise {
        // The constructor accepts a function
        constructor(callback) {
            callback(this.resolve, this.reject); // Call this function
        }
        resolve = (value) = > {} // The resolve method executed in the callback
        reject = (reason) = > {} // The reject method executed in the callback
    }
    
    / / test
    var test = new MyPromise((resolve, reject) = > {
        console.log('my promise is running! ');
    }) // Print my promise is running!
Copy the code

2. Three states

Now our class is ready to execute the methods we passed in, but what about the resolve and reject methods it passed in? Let’s move on to the Promise specification

  • According to the specificationPromiseThere are three statespending(wait),fulfilled(Finished),rejected(Refused).
  • When the state ofpending, Promise can be changed tofulfilledorrejectedstate
  • When the state offulfilled, Promise cannot change its state; It must have a value and cannot be changed
  • When the state ofrejected, Promise cannot change its state; There must be a reason for the rejection and there must be no change

Follow the Promise rule and write the class you just created:

    const stateArr = ['pending'.'fulfilled'.'rejected']; // Three states
    /** * @params {function} callback */
    class MyPromise {
        constructor(callback) {
            this.state = stateArr[0]; // The current state
            this.value = null; // The return value when finished
            this.reason = null; // Failure cause
            
            callback(this.resolve, this.reject); // Call this function
        }
        
        // The resolve method executed in the callback
        resolve = (value) = > {
            // Determine whether the state needs to be pending
            if (this.state === stateArr[0]) {
               this.state = stateArr[1]; This is fulfilled. // This is fulfilled
               this.value = value; // Write the final return value}}// The reject method executed in the callback
        reject = (reason) = > {
            // Determine whether the state needs to be pending
            if (this.state === stateArr[0]) {
               this.state = stateArr[2]; // Update the status to rejected
               this.reason = reason; // Write the reason for the rejection}}}Copy the code

Test it out:

resolve
fulfilled
reject

3. Then method

Our MyPromise is written here, it can already implement status update and value pass, but how to output its value to our business? As you can see from the use of a Promise, it outputs values through the THEN method. Then is a necessary method. Take a look at the THEN specification:

  • Promise must provide onethenMethod to access its current or final value or reason
  • In the promisethenMethod takes two parametersonFulilledandonRejected

Here are the specifications (in part) for onFulilled and onRejected

  • onFulilledandonRejectedBoth are optional arguments:
    • ifonFulilledIt’s not a function, it has to be ignored
    • ifonRejectedIt’s not a function, it has to be ignored
  • ifonFulilledIs a function:
    • It must be called at fulfilled, in the promise methodvalueAs the first parameter
    • It must not be called before fulfilled
    • It cannot be called more than once
  • ifonRejectedIs a function:
    • It must be called when you rejected, in the promise methodreasonAs the first parameter
    • It must not be called before Rejected
    • It cannot be called more than once
  • Cannot be called until the execution context stack contains only platform codeonFulfilledoronRejected
  • onFulfilledandonRejectedIt has to be a function
  • thenIt can be called multiple times within the same promise
  • thenYou have to return apromise

Let’s design the THEN method according to the rules of the THEN function

    const stateArr = ['pending'.'fulfilled'.'rejected']; // Three states
    class MyPromise {
        constructor(callback) {
            this.state = stateArr[0]; // The current state
            this.value = null; // The return value when finished
            this.reason = null; // Failure cause
            
            callback(this.resolve, this.reject); // Call this function
        }
        
        // The resolve method executed in the callback
        resolve = (value) = > {
            // Determine whether the state needs to be pending
            if (this.state === stateArr[0]) {
               this.state = stateArr[1]; This is fulfilled. // This is fulfilled
               this.value = value; // Write the final return value}}// The reject method executed in the callback
        reject = (reason) = > {
            // Determine whether the state needs to be pending
            if (this.state === stateArr[0]) {
               this.state = stateArr[2]; // Update the status to rejected
               this.reason = reason; // Write the reason for the rejection}}/ / then method
        then = (onFulilled, onRejected) = > {
            // Judge if onFulilled and onRejected are functions, if not ignore it
            onFulilled = typeof onFulilled === 'function' ? onFulilled : (value) = > value;
            onRejected = typeof onRejected === 'function' ? onRejected : (reason) = > reason;
            
            This is very depressing
            if (this.state === stateArr[1]) {
                // Then must return a promise
                return new MyPromise((resolve, reject) = > {
                    try {
                        const result = onFulilled(this.value); // Execute the incoming onFulilled method
                        
                        // If onFulilled returns a Promise, then the then method is called
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject);
                        } else{ resolve(result); }}catch(err) { reject(err); }})}// If the state is rejected
            if (this.state === stateArr[2]) {
                // Then must return a promise
                return new MyPromise((resolve, reject) = > {
                    try {
                        const result = onRejected(this.reason); // Execute the onRejected method passed in
                        
                        // If onRejected returns a Promise, then call the then method
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject);
                        } else{ resolve(result); }}catch(err) { reject(err); }}}Copy the code

Test it out:

Successful return:

Failure returns:

4. Keep improving

So far, MyPromise is almost ready to run, but there is a serious bug. If the resolve method does not execute in context on an asynchronous request, this will cause the then method to fail

    var test = new MyPromise((resolve, reject) = > {
        setTimeout((a)= > {
            resolve(123);
        }, 2000)
    })
    .then(msg= > {
        console.log(msg);
        return 456;
    })
Copy the code

Because the state of the promise has not changed when the THEN method is called, and our THEN method does not have the logic to handle pending state. This causes the then method to not return anything when the asynchronous method is executed. For example, in the example above, JavScript has executed the THEN method, but the setTimeout method is still waiting in eventLoop. Here’s what you need to do:

  • willthenSave the method in and waitresolveorrejectThen call the one you just savedthenThe methods in
  • Because there may be multiplethenMethods are executed, so you need a data store for them

    Based on these two points, let’s change some code
    // Add two new arrays in constructor to install the resolve and reject methods in THEN
    constructor(callback) {
        this.resolveArr = [];
        this.rejectArr = [];
    }
    
    // Modify the resolve method
    resolve = (value) = > {
        // Determine whether the state needs to be pending
            if (this.state === stateArr[0]) {
               this.state = stateArr[1]; This is fulfilled. // This is fulfilled
               this.value = value; // Write the final return value
               
               this.resolveArr.forEach(fun= > fun(value)) // Loop through the resolve method inserted by then}}// Modify the reject method
    reject = (reason) = > {
        // Determine whether the state needs to be pending
            if (this.state === stateArr[0]) {
               this.state = stateArr[1]; This is fulfilled. // This is fulfilled
               this.reason = reason; // Write the final return value
               
               this.rejectArr.forEach(fun= > fun(reason)) // The loop executes the then inserted reject method}}// We need to add logic to the THEN method to capture pending state
    then = (onFulilled, onRejected) = > {
        // If the state is pending
        if (this.state === stateArr[0]) {
            return new Promise((resolve, reject) = > {
                // The function called when the insert was successful
                this.resolveArr.push((value) = > {
                    try {
                        const result = onFulilled(value);
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject);
                        } else{ resolve(result); }}catch(err) { reject(err); }})// The function called when the insert failed
                this.rejectArr.push((value) = > {
                    try {
                        const result = onRejected(value);
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject);
                        } else{ resolve(result); }}catch(err) {
                        reject(err)
                    }
                })
            })
        }
    }
Copy the code

There you go. Test it

    new MyPromise((resolve, reject) = > {
        setTimeout((a)= > {
            resolve(123);
        }, 2000)
    })
    .then(msg= > {
        console.log(msg);
        return new MyPromise((resolve, reject) = > {
			setTimeout((a)= > {
				resolve(456)},2000);
		})
    })
    .then(msg= > {
        console.log(msg);
    })
Copy the code

5.Promise’s other approach

The implementation of promises according to the Promise specification is almost complete. Finally, we will add the methods implemented in the Promise

  • Catch method
    // Implement in the MyPromise prototype
    class MyPromise {
        // Call reject in THEN
        catch = (reject) = > {
            this.then(null, reject); }}Copy the code
  • resolve
    MyPromise.resolve = (value) = > {
        return new MyPromise((resolve, reject) = > { resolve(value) });
    }
Copy the code
  • reject
    MyPromise.resolve = (reason) = > {
        return new MyPromise((resolve, reject) = > { reject(reason) });
    }
Copy the code
  • There areall.race.Finally (prototype method)In fact, it is based onPrototype in PromiseMethods andPromise the rulesImplementation, here is not a list of it. Those who need to know canTo go to the

The final code (The source address)

const stateArr = ['pending'.'fulfilled'.'rejected']; // Three states
class MyPromise {
    constructor(callback) {
        this.state = stateArr[0]; // The current state
        this.value = null; // The return value when finished
        this.reason = null; // Failure cause
        this.resolveArr = [];
        this.rejectArr = [];
        
        callback(this.resolve, this.reject); // Call this function
    }
    
    // The resolve method executed in the callback
    resolve = (value) = > {
        // Determine whether the state needs to be pending
            if (this.state === stateArr[0]) {
                this.state = stateArr[1]; This is fulfilled. // This is fulfilled
                this.value = value; // Write the final return value
               
                this.resolveArr.forEach(fun= > fun(value)) // Loop through the resolve method inserted by then}}// The reject method executed in the callback
    reject = (reason) = > {
        // Determine whether the state needs to be pending
            if (this.state === stateArr[0]) {
               this.state = stateArr[1]; This is fulfilled. // This is fulfilled
               this.reason = reason; // Write the final return value
               
               this.rejectArr.forEach(fun= > fun(reason)) // The loop executes the then inserted reject method}}/ / then method
    then = (onFulilled, onRejected) = > {
        // Judge if onFulilled and onRejected are functions, if not ignore it
        onFulilled = typeof onFulilled === 'function' ? onFulilled : (value) = > value;
        onRejected = typeof onRejected === 'function' ? onRejected : (reason) = > reason;

        // If the state is pending
        if (this.state === stateArr[0]) {
            return new MyPromise((resolve, reject) = > {
                // The function called when the insert was successful
                this.resolveArr.push((value) = > {
                    try {
                        const result = onFulilled(value);
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject);
                        } else{ resolve(result); }}catch(err) { reject(err); }})// The function called when the insert failed
                this.rejectArr.push((value) = > {
                    try {
                        const result = onRejected(value);
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject);
                        } else{ resolve(result); }}catch(err) {
                        reject(err)
                    }
                })
            })
            
        }
        
        This is very depressing
        if (this.state === stateArr[1]) {
            // Then must return a promise
            return new MyPromise((resolve, reject) = > {
                try {
                    const result = onFulilled(this.value); // Execute the incoming onFulilled method
                    
                    // If onFulilled returns a Promise, then the then method is called
                    if (result instanceof MyPromise) {
                        result.then(resolve, reject);
                    } else{ resolve(result); }}catch(err) { reject(err); }})}// If the state is rejected
        if (this.state === stateArr[2]) {
            // Then must return a promise
            return new MyPromise((resolve, reject) = > {
                try {
                    const result = onRejected(this.reason); // Execute the onRejected method passed in
                    
                    // If onRejected returns a Promise, then call the then method
                    if (result instanceof MyPromise) {
                        result.then(resolve, reject);
                    } else{ resolve(result); }}catch(err) { reject(err); }}}})// Call reject in THEN
    catch = (reject) = > {
        this.then(null, reject);
    }
}

MyPromise.resolve = (value) = > {
    return new MyPromise((resolve, reject) = > { resolve(value) });
}

MyPromise.resolve = (reason) = > {
    return new MyPromise((resolve, reject) = > { reject(reason) });
}

Copy the code

summary

This time we learned how the promise was realized:

  • Must be a constructor
  • Three states (pending, resolve, reject)
  • Then methods (the methods that promise must have)

Start with the constructor, go through the implementation of the three states, and finally implement the THEN method to implement the Promise according to the Promise rules. When you’re done, hand write a Promise in front of the interviewer. 😄