This article has participated in the call for good writing activities, click to view: back end, big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!

This article will be updated from time to time, and the date of the update will be reflected in the title for easy viewing. Don’t be afraid to get lost

Ecma-262 standard town building 🙏🏻, I wish you a happy friends to get rich ~

Learning materials

📌 Promise/A+ original 📌 Original Promise MDN

A+ foreword 💍

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 function to retrieve the end result or reason for the failure of another promise.

The specification details the behavior of the THEN method, providing an interactive basis on which all Promise/A+ standards that comply with promise implementations can rely.

Nonsense not to say, directly handwritten Promise to achieve 👊🏻

Have a certain foundation of small partners can directly look at the code, do not want to repeat too much text, implementation details directly with notes. If you have any questions, please leave a message

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

const isFunction = (fn) = > typeof fn === 'function'

class MPromise {
    // an instance of promise can be called promise.then(),promise.then()...
    // A list is required to hold the callbacks registered by the current PROMISE instance. When the promise state changes, all callbacks are executed in sequence
    fulfilledCallbacks = []
    rejectedCallbacks = []
    // Promise has three states. Initial state pending we use getters and setters to achieve the effect of status trigger.
    // To avoid a set and get loop, we need to borrow a relay value to store status
    _status = PENDING

    New Promise(function(resolve, reject) {... })
    constructor(executor) {
        // One of the terms represents the execution result of a promise
        this.value = undefined
        // One of the terms indicates the reason for the rejection of a promise
        this.reason = undefined

        try {
            // new Promise(function(resolve, reject) {... })
            // Executor is an externally passed method. Resolve and Rejected are simply called as normal functions
            // This refers to a global object, so manually bind this
            executor(this.resolve.bind(this), this.reject.bind(this))}catch (error) {
            this.reject(error)
        }
    }

    // Status setter: acts as a state trigger to execute bound callback functions
    set status(status) {
        this._status = status

        switch (this.status) {
            case FULFILLED: {
                this.fulfilledCallbacks.forEach((callback) = > {
                    callback(this.value)
                })
                break
            }
            case REJECTED: {
                this.rejectedCallbacks.forEach((callback) = > {
                    callback(this.reason)
                })
                break
            }
            default:
                break}}/ / state getter
    get status() {
        return this._status
    }

    // One of Promise's actions changes the state to completed
    // resolve(value) => PENDING -> FULFILLED
    resolve(value) {
        // Only PENDING states can be modified
        if (this.status === PENDING) {
            // Since value is used in the status setter, set value first before setting status
            this.value = value
            this.status = FULFILLED
        }
    }

    // One of Promise's actions changes the state to rejected
    // reject(reason) => PENDING -> REJECTED
    reject(reason) {
        // Only PENDING states can be modified
        if (this.status === PENDING) {
            // Set reason first before setting status
            this.reason = reason
            this.status = REJECTED
        }
    }

    // Parse the promise, changing the state of the promise to trigger the next callback
    // Each turn of the THEN method returns a promise that must be resolved by resolvePromise to trigger the next callback
    resolvePromise(newPromise, callbackResult, resolve, reject) {
        // case1: newPromise and callbackResult cannot be the same object
        // Programming rigor prevents circular references
        if (newPromise === callbackResult) {
            return reject(
                new TypeError('The promise and the return value are the same'))}if (callbackResult instanceof MPromise) {
            // case2: callbackResult is a promise
            // This is a big pity. // The callback function returns a promise, which will be implemented first (i.e. called. Then).
            Reject (reject); // Reject (reject); // Reject (reject)
            // todo: Whether to add a status judgment
            callbackResult.then((value) = > {
                // Continue with the resolvePromise call
                this.resolvePromise(newPromise, value, resolve, reject)
            }, reject)
        } else if (
            typeof callbackResult === 'object' ||
            isFunction(callbackResult)
        ) {
            // case3: callbackResult is an object or function
            // Because the then method should return a promise, the specification calls any object or function that has a THEN method a promise
            // Then callbackResult should have a then method, reject newPromise if not

            // The boundary case null is also object
            if (callbackResult === null) {
                return resolve(callbackResult)
            }

            try {
                let then = callbackResult.then
                // If then is an executable
                if (isFunction(then)) {
                    let called = false
                    // Call the then method to pass the result of the callback execution and continue parsing with newPromise
                    then.call(
                        callbackResult,
                        (value) = > {
                            // Continue with the resolvePromise call
                            // onFulfilled and onRejected can only be called once according to the specification.
                            // The execution of the callback changes the state of the current promise to trigger the callback of the next THEN method, so it cannot be called again.
                            // You need to manually set a flag to determine whether it has been called, and ignore it if it is not called for the first time.
                            if (called) return
                            called = true
                            this.resolvePromise(newPromise, value, resolve, reject)
                        },
                        (reason) = > {
                            if (called) return
                            called = true
                            reject(reason)
                        }
                    )
                } else {
                    // Non-executable objects are passed directly
                    return resolve(callbackResult)
                }
            } catch (error) {
                return reject(error)
            }
        } else {
            // case4: None of the above
            // callbackResult is passed directly to the next THEN
            return resolve(callbackResult)
        }
    }

    // Register now, delay callback
    then(onFulfilled, onRejected) {
        const parent = this

        // Filter parameters
        const onFulfilledFn = isFunction(onFulfilled) ?
            onFulfilled :
            (value) = > {
                // If ondepressing is not a function, then manually cancel a function (reason: pass on the value of the last promise).
                return value
            }

        const onRejectedFn = isFunction(onRejected) ?
            onRejected :
            (reason) = > {
                // If onRejected is not a function, then throw a function manually.
                // Throw (onRejected is not a function)
                throw reason
            }

        // Then should be a promise (the promise chain continues thenable)
        // Why return a new promise manually? Because no matter what the result of the callback registered with the THEN method is,
        // all promise returns a promise, and a new promise,
        Resolve /reject newPromise based on the result of the callback, then callback (if any)
        // Since we manually returned a newPromise, the newPromise changes the logic of the state
        There is a relationship between // and the results of callbacks executed on the then method. We define a function to parse the relationship,
        // Use this function to define how the state of newPromise flows from the result of the callback execution to the next.then.
        const newPromise = new MPromise((resolve, reject) = > {
            const self = this
            // Use the arrow function instead of pointing to this
            // this is a big pity function, which will execute the successful callback inside the function. Then call resolvePromise to resolve the newPromise
            const _fulfilledFn = () = > {
                queueMicrotask(() = > {
                    /* The code to run in the microtask */
                    try {
                        // This is a big pity
                        const returnValue = onFulfilledFn(parent.value)
                        // Parse the newPromise and onimplementfn execution results
                        this.resolvePromise(self, returnValue, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                })
            }
            // This is the pity function. // This is the pity function
            const _rejectedFn = () = > {
                queueMicrotask(() = > {
                    /* The code to run in the microtask */
                    try {
                        // REJECTED Status Executes onRejected
                        const returnValue = onRejectedFn(parent.reason)
                        // Parse the newPromise and onRejectedFn execution results
                        this.resolvePromise(self, returnValue, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                })
            }
            // What callback does newPromise perform based on the previous PROMISE state
            switch (parent.status) {
                case FULFILLED: {
                    / / synchronize resolve
                    _fulfilledFn()
                    break
                }
                case REJECTED: {
                    Reject / / synchronization
                    _rejectedFn()
                    break
                }
                case PENDING: {
                    // The asynchronous task first collects the registered callback listening state changes and executes the callbacks in batch order
                    // This allows all bound callbacks to be executed at once when the current promise state changes
                    parent.fulfilledCallbacks.push(_fulfilledFn)
                    parent.rejectedCallbacks.push(_rejectedFn)
                    break
                }
                default:
                    break}})return newPromise
    }

    catch (onRejected) {
        return this.then(null, onRejected)
    } finally(onFinally) {
        return Promise.resolve(onFinally)
    }

    The static method creates a new Resolved promise
    static resolve(value) {
        if (value instanceof MPromise) {
            return value
        }

        return new MPromise(function(resolve, reject) {
            resolve(value)
        })
    }

    // The static method creates a new Rejected Promise
    static reject(reason) {
        return new MPromise(function(resolve, reject) {
            reject(reason)
        })
    }

    / / / race usage reference MDN (https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/race)
    // Race uses two operations at the same time and wants to know which one to finish first
    static race(promiseList) {
        return new MPromise((resolve, reject) = > {
            if(! promiseList) {return reject(new TypeError('undefined is not iterable'))}if (promiseList.length === 0) {
                return resolve()
            }

            // if any of the promiseList promises is settled, resolve/reject the current promise
            promiseList.forEach((promise) = > {
                // promise. resolve wraps a non-promise object as a Promise return, and the Promise object returns directly
                MPromise.resolve(promise).then(
                    (value) = > {
                        resolve(value)
                    },
                    (reason) = > {
                        reject(reason)
                    }
                )
            })
        })
    }

    / / / all usage reference MDN (https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)
    // All can be used in concurrent requests
    static all(promiseList) {
        return new MPromise((resolve, reject) = > {
            if(! promiseList) {return reject(new TypeError('undefined is not iterable'))}const taskLen = promiseList.length
            const resolvedValueList = []

            if (taskLen === 0) {
                return resolve([])
            }

            // If the number of Settled Promises is equal to the number of promiseList, the execution is complete
            for (let i = 0; i < promiseList.length; i++) {
                MPromise.resolve(promiseList[i])
                    .then((value) = > {
                        // push into resolvedValueList
                        resolvedValueList[i] = value
                    })
                    .catch((e) = > {
                        // Catch the first error message or rejected promise reason
                        Reject (Reject is executed only once for each promise, even though repeated execution is only valid for the first time)
                        reject(e)
                    })
            }

            if (resolvedValueList.length === taskLen) {
                return resolve(resolvedValueList)
            }
        })
    }

    // [allSettled usage reference MDN] (https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled)
    static allSettled(promiseList) {
        return new MPromise((resolve, reject) = > {
            // Store the return values List
            const returnValueList = []
            for (let i = 0; i < promiseList.length; i++) {
                MPromise.resolve(promiseList[i])
                    .then(
                        (value) = > {
                            returnValueList[i] = {
                                status: FULFILLED,
                                value
                            }
                        },
                        (reason) = > {
                            returnValueList[i] = {
                                status: REJECTED,
                                reason
                            }
                        }
                    )
                    .finally(() = > {
                        // The callback that will be executed when the promise ends
                        if (returnValueList.length === promiseList.length) {
                            resolve(returnValueList)
                        }
                    })
            }
        })
    }
}

export default MPromise
Copy the code

✍🏻 : complete code has been uploaded to the repository. >

Some new knowledge of GET after experiencing handwriting implementation 🤙🏻 @0722

  1. Each layer of the Promise chain (then, Catch) returns a newly generated Promise object.

    To be honest, I always thought it was a promise. YY ability 🤯

  2. Whether or not each callback registered on the THEN method executes depends only on its previous promise state.

  3. Catch is implemented by calling THEN.

  4. This is a big pity. If the then method returns with the second parameter — the callback of the rejected state — then the promise state of the current THEN method return will become a pity state — that is, the callback of the catch method will not be fulfilled.

  5. The promise state is changed only once, and once the state is final, no call to resolve or Reject will change the promise state.

  6. When “previous callback returns Promise object state” is (updated @0722)

    Pending: Subsequent then methods were not executed

    If the previous Promise object state is pending, the next THEN method is not executed until the previous Promise object is not pending. ✅

    Depressing: The later then method performs ondepressing logic

    If the previous Promise object state was fulfilled, the next THEN method will also perform the onFulfilled callback and pass the value of the previous Promise. ✅

    Rejected: The later then method executes the onRejected logic

    If the previous Promise object state was Rejected, the next then method also executes the onRejected callback and passes the previous Promise’s reason for rejecting it. ✅

    This is one of the scenarios of the Promise Resolution Procedure specification, which enables the Promise chain to be passed on without being broken, whether this is a big pity or rejected (non-pending state).

    📌 The above conclusions correspond to the Promise/A+ specification text, part 2.3.2 >

Do the test 🏅

1. Implement chained execution with Promise (start next to last settled)

const p1 = new Promise((resolve, reject) = > {
    console.log(111)
    resolve(111)})const p2 = new Promise((resolve, reject) = > {
    console.log(222)
    resolve(222)})const p3 = new Promise((resolve, reject) = > {
    console.log(333)
    resolve(333)})const promiseList = [p1, p2, p3]

/** * reduce * []. Reduce (callbackfn, initialValue) ** Callbackfn example * function handler(total, currentItem) { * return total + currentItem * } */
const runAsChain = (promiseList) = > {
    console.log('runAsChain running... ')
    // use reduce to concatenate the promise array \
    return Array.from(promiseList).reduce((previousPromise, currentPromise) = > {
        return previousPromise.then(() = > {
            return currentPromise
        })
    }, Promise.resolve())
}

runAsChain(promiseList)
Copy the code

The first time I wrote this, I found that when I printed runAsChain running… The log in my promise was already printed. Look at my implementation and notice that when I define promiseList, I’m assigning a variable directly to a new Promise(). According to the previous practice of writing promises, the Promise constructor accepts arguments (executor functions), Is called when instantiated. That is, as soon as I use new Promise(), the function passed in must be executed immediately.

To rewrite the implementation, we first change the assignment to a function return. Instead of writing several promises manually, we package a function that generates promises.

const promiseCreator = (n) = > {
    return () = >
        new Promise((resolve, reject) = > {
            // To reflect the time difference, set a timer, n seconds from the last callback interval
            setTimeout(() = > {
                console.log(n, new Date().toTimeString())
                resolve(n)
            }, n * 1000)})}const promiseList = [promiseCreator(1), promiseCreator(2), promiseCreator(3)]

/** * reduce * []. Reduce (callbackfn, initialValue) ** Callbackfn example * function handler(total, currentItem) { * return total + currentItem * } */
const runAsChain = (promiseList) = > {
    console.log('runAsChain running... '.new Date().toTimeString())
    // borrow reduce to concatenate the promise array
    return Array.from(promiseList).reduce((previousPromise, currentPromise) = > {
        return previousPromise.then(() = > currentPromise())
    }, Promise.resolve())
}

/ / call
runAsChain(promiseList)
Copy the code

Print the following:

runAsChain running... 20:52:26 GMT+0800 (China Standard Time) 1 20:52:27 GMT+0800 (China Standard Time) 2 20:52:29 GMT+0800 (China Standard Time) // Interval 2 seconds 3 20:52:32 GMT+0800 (China Standard Time) // Interval 3 seconds [Done] exited with code=0 in 6.105 secondsCopy the code

2. The promise of the cancel

/ / to be added
Copy the code

3. Encapsulate a FETCH with timeout handling (borrow promise for state management)

function fetchWizTimeout(. fetchApiProps, time) {
    return new Promise((resolve, reject) = > {
        // Execute the fetch logic, resolve the current promise if the request succeeds, reject the promise if it fails
        fetch(fetchApiProps).then(resolve).catch(reject)
        // Since the code in new Promise is synchronous, the fetch request will be issued immediately
        // The promise state is changed only once
        Timer reject is also disabled if the request is completed before the timer responds
        setTimeout(reject, time)
    })
}
Copy the code

The promise state can only be changed once!

4. What is the state of the promise object output by the following code

const test = new Promise((resolve, reject) = > {
    setTimeout(() = > {
        reject(111)},100)
}).catch((reason) = > {
    console.log('error:', reason)
    console.log(test) / /? What is the status of test
})

setTimeout(() = > {
    console.log(test) / /? What is the status of test
}, 300)
Copy the code

Output result:

error: 111
Promise { <pending>} // the output of setTimeout is pending. The Promise is undefinedCopy the code

The third line will be fulfilled with Promise {undefined}, which is a pity state and value = undefined

Each layer on the promise chain generates a new promise, and each printed test refers to the state that Promise ().then() returns.

When 100 setTimeout is printed in the THEN method, the current promise has not completed execution (and no resolve/ Reject calls are made), so it is pending.

When 300 setTimeout is printed, test is finished. Although there is reject in the promise chain, catch is set. Catch is equivalent to intercepting the exception => return to normal => The chain can continue. The catch implementation also calls the THEN method, which will return a newly generated promise object, and we do not set the return value in the catch method. According to the specification, it is equivalent to resolve(null), so this is the depressing state.

Function foo(fn, interval, times) {} @0722

The essence of a handwritten question is to break down the steps and decide on the solution step by step. This way, you will find that the question may not be as complex as described, and it is not easy to get confused. Following this methodology, let’s do this problem:

➡️ Catch error can be automatically retry

➡️ catch error x seconds after automatic retry

➡️ Automatic retry n times and no longer retry

function foo(fn, internal, times) {}
Copy the code

6. Encapsulate a generic asynchronous function timeout logic (more than times reject)

// todo

Promise.all How to guarantee that I will go then method (@0805)

All receives a set of promises. After batch execution, it still returns a Promise, which can support continuous chain calls and deliver a set of Promise execution results. We’re only going to return this set of promises if they’re all in the fullfilled state.

So the question is how to make sure that errors don’t break the Promise chain. According to the A+ specification and the above handwritten implementation, we can quickly understand this because the internal principle of A promise is that no then method should return A promise, The catch method is also a chain-able feature that calls the THEN implementation (see the then method and resolvePromise in the above handwritten implementation for details). No exceptions (manually set to return the Promise state, return the special object or function). If we write a failure callback or catch interception, the Promise will return a resolve Promise. You can then proceed to the next successful callback for the THEN method.

So as long as we promise.all ([promise1. Catch (…), promise2. Catch (…)] ).then(…) Similarly, each promise is handled with a failure callback or catch intercept, and our exception does not affect the backward-passing of promise.all.

This is the most basic approach, but we’ll find that once we have a lot of promises executed in batches, adding a catch to each promise can be quite taxing. Promise then provides a static method, allSettled. This method is almost the same as promises.all, but the chained state is not affected by the executed Promise. That triggers the promise state returned by allSettled, which is the execution of the next THEN method. AllSettled will return the set of promise execution results to the next callback as an object like {status: xx, value: xx}. This is perfect for a scenario where promises need to be executed in bulk, without affecting the logic in the callback if they fail.

What is publish subscribe @0720

// todo

Principle of Async /await (Generator coroutine) @0721

// todo

// More topics to be added…

THE END