The frontispiece language

City last month, white as snow, cicada temples beauty sorrow. — Wen Tingyun (Tang dynasty) “More Leakage son meet rare”

1. Constructor

Typically we use the Promise constructor to wrap an asynchronous task and excutor’s resolve method to get the value returned by the asynchronous task.

// Typical usage
const promise = new Promise((resolve, reject) = > {
    // Execute the asynchronous task in excutor. After the asynchronous task ends, use resolve to change the promise state to fullfilled
    setTimeout(() = > {
        resolve(100)},1000)})Copy the code

The state of a Promise object looks like this:

Initial state -- Pending indicates that the asynchronous task is pending. At present, the promise has not decided to complete the asynchronous task successfully. Fullfilled An error occurred during the completion of the asynchronous task. The fullfilled and Rejected states cannot be changed after they are specified. This kind of promise is usually referred to as the resolved promise resolve -> the promise is resolved to the fullfilled state, Reject -> resolve the promise to rejected state, and then bound the callback on the promiseCopy the code

To sum up, a Promise has three private properties:

  • state
    • Used to save the state of the current Promise
  • data
    • This parameter is used to save data after an asynchronous task is executed
  • callbacks
    • Save the data to be passed before the asynchronous task is completedPromise.thenMethod specifies the callback function to be called after the resolution

Promise’s constructor argument is a function signed (resolve, reject) => void.

  • resolve
    • willPromiseThe resolution offullfilledstate
    • After the asynchronous task is executeddataStored in thePromise
    • ifPromisethroughthenThe specified callback function before the asynchronous task completes, thenresolveThe saved callback method is placed at the end of the task queue
  • reject
    • willPromiseThe resolution ofrejectedstate
    • The asynchronous task was executed incorrectlyreasonStored in thePromise
    • ifPromisethroughthenThe specified callback function before the asynchronous task completes, thenrejectThe saved callback method is placed at the end of the task queue
  • An exception occurs
    • ifexcutorIf an exception occurs during execution, thenPromiseIt automatically decides to berejectedState,reasonIs the exception that occurred

Implementation:

constructor(excutor) {
    // State of the promise
    this.status = PENDING
    // The data saved by the promise
    this.data = undefined
    // Promise pre-saved callback function
    this.callbacks = []                   

    const resolve = (value) = > {

        // If the state of the promise has been decided beforehand, then nothing will be done
        if(! checkStatus(this.status)) {
            return
        }
        // 1. Switch to Resolved
        this.status = RESOLVED
        // 2. Save value data
        this.data = value
        // 3. If the callback function is to be executed, the callback is executed asynchronously immediately, that is, the callback is added to the task queue
        if (this.callbacks.length > 0) {
            this.callbacks.forEach(
                callbacksObj= > {
                    setTimeout(() = > {
                        callbacksObj.onResolved(value)
                    }, 0); }}})const reject = (reason) = > {
        if(! checkStatus) {return
        }

        this.status = REJECTED
        this.data = reason

        if(this.callbacks.length > 0) {
            this.callbacks.forEach(
                callbacksObj= > {
                    setTimeout(() = > {
                        callbacksObj.onRejected(reason)
                    }, 0); }}})// Catch the exception that Excutor executes, and if there is an exception, resolve the promise directly
    try {
        excutor(resolve, reject)
    } catch (error) {
        reject(error)
    }

}
Copy the code

2. then

The then method is used to specify the callback function to execute after the Promise resolution. These callback functions will only be put into the callback queue for execution after the Promise resolution. If the state of a Promise is pending, these callbacks are temporarily stored in this.callbacks.

Then (onFullfilled, onRejected): Promise onFullfilled(value): any -- Promise is lowered to the fullfilled state. If this parameter is not specified, the engine automatically replaces it with (x) => x onRejected(reason): any -- Promise is used for the rejected state. If not specified, the engine will automatically replace it with reason => {throw reason}Copy the code

The THEN method is two callback functions that are called in different cases. The return value is a Promise. The state of the Promise depends on the return value of the callback function.

  • The callback function returns a value of noPromise
    • thenThe return value is onefullfilledThe state of thePromiseAnd,valueReturns a value for the callback function
  • The return value of the callback function is onePromise
    • Then returns the same state as the Promise
  • The return value of the callback function is onethenable
    • The then return value is the Promise after this thenable calls the then method for the resolution
  • An exception occurred in the callback function
    • thenThe return value is onerejectedThe state of thePromise.reasonIs the exception that occurred
  • The callback function has no return value
    • thenThe return value is onefullfilledPromise.valueundefined

At the same time, a Promise has two states: undecided and resolved:

  • No resolution
    • Save the callback function for later invocation
  • Have the resolution
    • Call the callback function directly

Implementation:

then(onFullfilled, onRejected) {
    // The default value when onFullfilled is not specified
    onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : value= > value
    // Default value when onRejected is not specified
    onRejected = typeof onRejected === 'function' ? onRejected : reason= > {throw reason}

    return new NewPromise((resolve, reject) = > {
        const handler = (callback) = > {
            try {
                // Execute the callback function to get the return value
                const result = callback(this.data)

                // If the return value is a PROMISE, the state of the promise is used as the result
                if (result instanceof NewPromise) {
                    result.then(resolve, reject)
                } else {
                    // The return value is a normal value
                    resolve(result)
                }
            } catch (error) {
                // Throw an exception
                reject(error)
            }
        }
        // 1. In pending state
        if (this.status === PENDING) {
            // Save the callback to be called later in the Promise
            this.callbacks.push({
                onResolved: (value) = > {
                    handler(onFullfilled)
                },
                onRejected: (reason) = > {
                    handler(onRejected)
                }
            })

        }else if (this.status === RESOLVED) {
            // Resolved to queue the callback directly
            setTimeout(() = > {
                handler(onFullfilled)
            }, 0)}else {
            // Queue the callback directly in the onRejected state
            setTimeout(() = > {
                handler(onRejected)
            }, 0)}}}Copy the code

3. catch

The catch method is used to handle the result of the Rejected state that appears in the Promise. Because of the exception penetration mechanism, the implementation is a special THEN call

catch(onRejected) {
    return this.then(undefined, onRejected)
}
Copy the code

4. finally

Ideas:

Because both then and catch return a Promise, and the Promise could be onFullfilled or onRejected. So there are two cases to deal with.

Function signature:

finally(onfinally? : (() = > void) | undefined | null) :Promise<T>
// A callback returns a Promise
Copy the code

Implementation:

finally(callback) {
    return this.then(
        value= > {
            // Core: discard the return value of callback execution and pass the original promise value
            return NewPromise.resolve(callback()).then(
                () = > {
                    return value
                }
            )
        },
        reason= > {
            // Core: discard the reason for callback execution failure and pass the original promise reason
            return NewPromise.resolve(callback()).then(
                () = > {
                    throw reason
                }
            )
        }
    )

}
Copy the code

5. Promise.resolve()

Pormise takes a value and returns a Promise.

  • If the value is notpromiseorthenable
    • PromiseThe status ofonFullfilledAnd has a value ofvalue
  • If the value isPromise
    • The Promise state is the same as the incoming Promise, and the value is the same
  • If the value isthenable
    • Execute thenable’s then method with its resolution value as the value

Implementation:

static resolve(value) {

    // 1. value The value is Promise
    if (value instanceof NewPromise) {
        return value
    } else if (value.then){
        return new NewPromise((resolve, reject) = > {
            value.then(resolve, reject)
        })
    }else {
        return new NewPromise((resolve, reject) = > {
            resolve(value)
        })
    }               
}
Copy the code

6. Promise.reject()

Reject is a simple method that returns a Promise in the Reject state, with reason as the parameter value

static reject(reason) {
    return new NewPromise((resolve, reject) = > {
        reject(reason)
    })
}
Copy the code

7. Promise.all()

Argument: an iterable – just think of it as an array for simplicity

Return value: a Promise.

  • If the argument is an empty iterable, return onefullfilledThe state of the Promise
  • If the parameterPromiseState isfullfilled, then the status of the returned value becomesfullfilled, the obtained values are stored in an array
  • If the parameterPromiseOne of the states is zerorejected, the status of the returned value becomesrejectedThe one who failedPromisereasonStored in thereason
static all(promises) {
    return new NewPromise((resolve, reject) = > {
        let resolvedCount = 0
        const values = new Array(promises.length)
        promises.forEach((promise, index) = > {

            // Details: If the parameter is not a Promise, convert it to a Promise
            NewPromise.resolve(promise).then(
                value= > {
                    values[index] = value
                    resolvedCount++

                    // Details: count to make sure all Promise states are fullfilled
                    if (resolvedCount === promises.length) {
                        resolve(values)
                    }
                },
                reason= > {
                    reject(reason)
                }
            )
        })
    })
}
Copy the code

8. Promise.race()

// Similar to a race, the state of a race is the state of the promise that changes its state, whether it succeeds or fails
static race(promises) {
    return new NewPromise((resolve, reject) = > {
        promises.forEach((promise) = > {
            NewPromise.resolve(promise).then(resolve, reject)
        })
    })

}
Copy the code

9. Overall code

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function checkStatus(status) {
    if(status ! == PENDING) {return false
    }
    return true
}

class NewPromise {
    

    constructor(excutor) {
        this.status = PENDING
        this.data = undefined
        this.callbacks = []  
        
        const reject = (reason) = > {
            if(! checkStatus) {return
            }

            this.status = REJECTED
            this.data = reason

            if (this.callbacks.length > 0) {
                this.callbacks.forEach(
                    callbacksObj= > {
                        setTimeout(() = > {
                            callbacksObj.onRejected(reason)
                        }, 0); }}})const doResolved = (value) = > {
            // 1. Switch to Resolved
            this.status = RESOLVED
            // 2. Save value data
            this.data = value
            // 3. If the callback function is to be executed, the callback is executed asynchronously immediately, that is, the callback is added to the task queue
            if (this.callbacks.length > 0) {
                this.callbacks.forEach(
                    callbacksObj= > {
                        setTimeout(() = > {
                            callbacksObj.onResolved(value)
                        }, 0); }}})const resolve = (value) = > {

            if(! checkStatus(this.status)) {
                return
            }

            doResolved(value)
        }


        try {
            excutor(resolve, reject)
        } catch (error) {
            reject(error)
        }

    }

    then(onFullfilled, onRejected) {
        // The default value when onFullfilled is not specified
        onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : value= > value
        // Default value when onRejected is not specified
        onRejected = typeof onRejected === 'function' ? onRejected : reason= > {throw reason}

        return new NewPromise((resolve, reject) = > {
            const handler = (callback) = > {
                try {
                    // Execute the callback function to get the return value
                    const result = callback(this.data)

                    // If the return value is a PROMISE, the state of the promise is used as the result
                    if (result instanceof NewPromise) {
                        result.then(resolve, reject)
                    } else {
                        // The return value is a normal value
                        resolve(result)
                    }
                } catch (error) {
                    // Throw an exception
                    reject(error)
                }
            }
            // 1. In pending state
            if (this.status === PENDING) {
                // Save the callback to be called later in the Promise
                this.callbacks.push({
                    onResolved: (value) = > {
                        handler(onFullfilled)
                    },
                    onRejected: (reason) = > {
                        handler(onRejected)
                    }
                })

            }else if (this.status === RESOLVED) {
                // Resolved to queue the callback directly
                setTimeout(() = > {
                    handler(onFullfilled)
                }, 0)}else {
                // Queue the callback directly in the onRejected state
                setTimeout(() = > {
                    handler(onRejected)
                }, 0)}}}catch(onRejected) {
        return this.then(undefined, onRejected)
    }

    finally(callback) {
        return this.then(
            value= > {
                return NewPromise.resolve(callback()).then(
                    () = > {
                        return value
                    }
                )
            },
            reason= > {
                return NewPromise.resolve(callback()).then(
                    () = > {
                        throw reason
                    }
                )
            }
        )

    }

    static all(promises) {
        return new NewPromise((resolve, reject) = > {
            let resolvedCount = 0
            const values = new Array(promises.length)
            promises.forEach((promise, index) = > {
                
                NewPromise.resolve(promise).then(
                    value= > {
                        values[index] = value
                        resolvedCount++

                        if (resolvedCount === promises.length) {
                            resolve(values)
                        }
                    },
                    reason= > {
                        reject(reason)
                    }
                )
            })
        }) 

    }

    static race(promises) {
        return new NewPromise((resolve, reject) = > {
            promises.forEach((promise) = > {
                NewPromise.resolve(promise).then(resolve, reject)
            })
        })

    }

    static resolve(value) {

        // 1. value The value is Promise
        if (value instanceof NewPromise) {
            return value
            
        } else if (value.then){
            return new NewPromise((resolve, reject) = > {
                value.then(resolve, reject)
            })
        }else {
            return new NewPromise((resolve, reject) = > {
                resolve(value)
            })
        }               
    }

    static reject(reason) {
        return new NewPromise((resolve, reject) = > {
            reject(reason)
        })
    }
}
Copy the code