Promise

A PROMISE represents the end result of an asynchronous operation. The primary way to interact with a promise is through its THEN method, which registers a callback that receives either the final value of the promise or the reason why the promise was not satisfied.

specification

Promise A + specification

  1. The term

    • promiseIt’s the one withthenMethod objects or functions that behave in accordance with this specification.
    • thenableIt’s a definitionthenMethod object or function.
    • valueAny legalJavaScriptValue (includingundefined,thenableorpromise).
    • exceptionIs the use ofthrowStatement.
    • reasonIs a value that indicates why a promise is rejected.
  2. requirements

    • State of promise

      • Pending: Indicates the waiting state

        1. Can be converted to when in the wait statefulfilled / rejected
      • This is very depressing

        1. You must not transition to another state
        2. There must be a value that cannot be converted (value)
      • You never know unless you like it

        1. You must not transition to another state
        2. There must be another reason not to convert (reason)
    • A promise must provide a THEN method to access its current or final value or reason. The THEN method accepts two parameters: promise.then(onFulfilled, onRejected)

      • OnFulfilled and onRejected are both optional parameters

        1. ifonFulfilledIt’s not a function, it has to be ignored
        2. ifonRejectedIt’s not a function, it has to be ignored
      • If onFulfilled is a function

        1. Must be inpromiseComplete (resolve) is then called,promiseAs its first argument
        2. It must not bepromiseComplete (resolve) is called before
        3. It must not be called more than once
      • If onRejected is a function

        1. Must be inpromiseRefuse to (reject) is then called,promiseAs its first argument, the value (reason) of
        2. It must not bepromiseRefuse to (reject) is called before
        3. It must not be called more than once
      • Onfulfilled or onRejected cannot be called until the execution context stack contains only platform code [3.1]

      • OnFulfilled and onRejected must be called as functions (without this value)[3.2]

      • A THEN on the same PROMISE can be called multiple times

        1. ifpromiseIs completed,resolve), all correspondingonFulfilledNeed to followthenThe order of is executed sequentially
        2. ifpromiseIs rejected,reject), all correspondingonRejectedNeed to followthenThe order of is executed sequentially
      • [3.3] promise = promise.then (onFulfilled, onRejected)

        1. ifonFulfilledonRejectedReturn a valuexRun,[[Resolve]](promise2, x)
        2. ifonFulfilledonRejectedThrow an exceptionepromise2Must useeReject as a reason
        3. ifonFulfilledIt’s not a function andpromise1Is completed,resolve),promise2Need to return withpromise1The same value (value)
        4. ifonRejectedIt’s not a function andpromise1Is rejected,reject),promise2Need to return withpromise1The same value (reason)
    • The Promise resolver ([[Resolve]]) [[Resolve]] is an abstract concept that takes a Promise and x as input [[Resolve]](promise, x), if x is a thenable, It tries to make the promise take the state of X, assuming that X behaves at least a little like a promise. Otherwise, it will implement the promise with the value x. This thenable handling allows promise implementations to be more generic, as long as they expose A THEN method that adheres to promise /A+. It also allows implementations that adhere to the Promise/A+ specification to co-exist with less canonical but usable implementations, running [[Resolve]](Promise, x), performing the following steps

      • If the Promise and x point to the same object, reject the Promise with TypeError as the reason

      • If x is a promise, take its state [3.4]

        1. ifxThe state ispending,promiseMust continue topendingUntil thexBe completed or rejected.
        2. ifxThe state isresolveIs solved with the same valuepromise
        3. ifxThe state isrejectThe reason is the samepromise
      • If x is a function or an object

        1. makethenThe value ofx.then [3.5]
        2. If you getx.thenAn exception is thrown by the value ofe, it willeReject as a causepromise
        3. ifthenPhi is a function of phi with phixAs athisCall it, first argumentresolvePromise, the second parameterrejectPromise, including
          1. If and whenresolvePromiseThe value ofyWhen running[[Resolve]](promise, y)
          2. ifrejectPromiseFor one reasonrCall,rRefused topromise
          3. If it’s called at the same timeresolvePromiserejectPromise, or the same argument is called multiple times, then the first call takes precedence and subsequent calls are ignored. (forthenable )
          4. If the callthenThrow an exceptione
            1. ifresolvePromiserejectPromiseHas been called, ignore it.
            2. Otherwise, useeReject as a causepromise
        4. ifthenIt’s not a functionxComplete (resolve)promise
      • If x is not an object or function, use x to resolve the promise

If a promise is parsed by thenable and participates in a circular thenable chain, The recursive nature of [[Resolve]](promise, thenable) eventually causes [[Resolve]](promise, thenable) to be called again, which results in infinite recursion according to the above algorithm. Implementations are encouraged (but not required) to detect this recursion and reject the promise as TypeError. [3.6]

  1. annotations

    1. Here the platform code makes the engine, environment, and promise implementation code. In practice, this needs to ensure that onFulfilled and onRejected are executed asynchronously and should be executed on the new execution stack after the round of event loop in which the THEN method is called. This can be accomplished with a “macrotask” mechanism such as setTimeout or setImmediate, or with a “microtask” mechanism such as MutationObserver or Process.Nexttick. Because the implementation of a PROMISE is considered platform code, a task scheduling queue may already be included when the own handler is called

    2. In strict mode, this in them will be undefined; In non-strict mode, this will be a global object

    3. If all requirements are met, enable PROMISe2 === PromisE1. Each implementation should record whether or not promise2 === Promise1 can be generated and when will promise2 === promise1 occur

    4. In general, x is a true promise only if it comes from the current implementation. This rule allows those exception implementations to take the state of a Promise that meets known requirements

    5. This program first stores a reference to x.hen, then tests that reference, and then calls that reference, thereby avoiding multiple visits to the x.hen property. Such precautions are important to ensure consistency of visitor attributes, whose values can change between two retrieves

    6. Implementations should not place arbitrary limits on the depth of the Thenable chain, and assume that beyond that arbitrary limit there will be infinite recursion. Only true loops should raise a TypeError; If you encounter an infinite loop of thenable, always performing recursion is the correct behavior

outline

Code implementation

// ES6
// Three states
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {
    // State: the initial state is pending
    status = PENDING
    / / value
    value = null
    / / reasons
    reason = null
    // Execute the onFulfilled queue
    onFulfilledCallbacks = []
    // Execute the onRejected queue
    onRejectedCallbacks = []
    // The constructor
    constructor(executor){
        try {
            executor(this.resolve, this.reject)
        } catch (error) {
            this.reject(error)
        }
    }
    resolve = value= > {
        // Check whether the state is in the wait state
        if(this.status === PENDING){
            // Change the state
            this.status = FULFILLED
            / / assignment
            this.value = value
            // Loop call
            while(this.onFulfilledCallbacks.length){
                this.onFulfilledCallbacks.shift()(this.value)
            }
        }
    }
    reject = reason= > {
        // Check whether the state is in the wait state
        if(this.status === PENDING){
            // Change the status
            this.status = REJECTED
            // Assignment reason
            this.reason = reason
            // Loop call
            while(this.onRejectedCallbacks.length){
                this.onRejectedCallbacks.shift()(this.reason)
            }
        }
    }
    then(onFulfilled, onRejected){
        // This parameter is optional
        const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value
        const realOnRejected = typeof onRejected === 'function' ? onRejected : reason= > { throw reason }
        const promise1 = new MyPromise((resolve, reject) = > {
            // Create a function to complete the microtask
            const fulfilledMicrotask = () = > {
                queueMicrotask(() = > {
                    try{
                        let x = realOnFulfilled(this.value)
                        resolvePromise(promise1, x, resolve, reject)
                    }catch (error) {
                        reject(error)
                    }
                })
            }
            // Create a microtask to execute the rejected function
            const rejectMicrotask = () = > {
                queueMicrotask(() = > {
                    try{
                        let x = realOnRejected(this.reason)
                        resolvePromise(promise1, x, resolve, reject)
                    }catch (error) {
                        reject(error)
                    }
                })
            }
            // Execute directly after the status is determined
            if(this.status == FULFILLED){
                fulfilledMicrotask()
            }else if(this.status == REJECTED){
                rejectMicrotask()
            }else{
                // Asynchronously, join the queue
                this.onFulfilledCallbacks.push(fulfilledMicrotask)
                this.onRejectedCallbacks.push(rejectMicrotask)
            }
        })
        // then returns a new promise
        return promise1
    }
    / / catch method
    catch (onRejected) {
        this.then(null, onRejected)
    }
}

function resolvePromise(promise, x, resolve, reject){
    if(x === promise){
        // Call in a loop and report an error directly
        return reject(new TypeError('The promise and the return value are the same'));
    }
    if(typeof x === 'function' || typeof x === 'object') {// null returns directly
        if(x === null) return resolve(x)

        let then
        try {
            then = x.then
        } catch (error) {
            // There is no direct rejection
            return reject(error)
        }
        // If the object has a THEN method
        if(typeof then === 'function') {let called = false
            try {
                then.call(x, y= > {
                    // Ignore multiple times
                    if(called) return
                    called = true
                    // Then execute
                    resolvePromise(promise, y, resolve, reject)
                }, r= > {
                    // Ignore multiple times
                    if(called) return
                    called = true
                    reject(r)
                })
            } catch (error) {
                // 
                if(called) return
                called = true
                reject(error)
            }
        }else{
            // Then is not a function
            resolve(x)
        }
    }else{
        // If x is not an object or function, execute the promise with x as the argument
        resolve(x)
    }
}

/ / test
MyPromise.deferred = function(){
    var result = {}
    result.promise = new MyPromise(function(resolve, reject){
        result.resolve = resolve
        result.reject = reject
    })
    return result
}

module.exports = MyPromise
Copy the code
// ES5
var PENDING = 'pending';
var REJECTED = 'rejected';
var FULFILLED = 'fulfilled';
function MyPromise(executor){
    this.status = PENDING
    this.value = null;
    this.reason = null;
    this.onFulfilled = []
    this.onRejected = []
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
    try {
        executor(this.resolve, this.reject)
    } catch (error) {
        this.reject(error)
    }
}
MyPromise.prototype.resolve = function(value){
    if(this.status === PENDING){
        this.status = FULFILLED
        this.value = value
        while(this.onFulfilled.length){
            this.onFulfilled.shift()(this.value)
        }
    }
}
MyPromise.prototype.reject = function(reason){
    if(this.status === PENDING){
        this.status = REJECTED
        this.reason = reason
        while(this.onRejected.length){
            this.onRejected.shift()(this.reason)
        }
    }
}
MyPromise.prototype.then = function(onFulfilled, onRejected){
    var that = this
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(value){ return value }
    onRejected = typeof onRejected === 'function' ? onRejected : function(reason){ throw reason }
    var promise1 = new MyPromise(function(resolve, reject){
        var fulfilled = function(){
            // setTimeout can be used without queueMicrotask
            queueMicrotask(function(){
                try {
                    var x = onFulfilled(that.value)
                    resolvePromise(promise1, x, resolve, reject)
                } catch (error) {
                    reject(error)
                }
            })
        }
        var rejected = function(){
            queueMicrotask(function(){
                try {
                    var x = onRejected(that.reason)
                    resolvePromise(promise1, x, resolve, reject)
                } catch (error) {
                    reject(error)
                }
            })
        }
        if(that.status === FULFILLED){
            fulfilled()
        }else if(that.status === REJECTED){
            rejected()
        }else{
            that.onFulfilled.push(fulfilled)
            that.onRejected.push(rejected)
        }
    })
    return promise1
}
MyPromise.prototype.catch = function(onRejected){
    this.then(null, onRejected)
}
Copy the code

Execute the process

Refer to the article

Starting with a Promise interview question that gave me sleepless nights, delve into the details of how that Promise was implemented