Implementation of handwritten Promise (2) instance methods and static methods

1: What is Promise

Promise is a solution for asynchronous programming. It is actually a constructor. It has several methods such as all, reject, and resolve, and the prototype has methods such as then and catch.

Promise objects have the following two characteristics.

(1) The state of the object is not affected by the outside world. The Promise object represents an asynchronous operation and has three states: pending (in progress), fulfilled (successful) and rejected (failed). Only the result of the asynchronous operation can determine the current state, and no other operation can change this state. This is also the origin of the name Promise. Its English meaning is “commitment”, which means that it cannot be changed by other means.

(2) Once the status changes, it will not change again, and this result can be obtained at any time. There are only two possibilities for the state of a Promise object to change: from pending to fulfilled and from pending to rejected. As long as these two situations occur, the state is solidified and will not change again. It will always maintain this result. This is called resolved. If the change has already occurred, if you add a callback function to the Promise object, you will get the result immediately. This is completely different from an event. The characteristic of an event is that if you miss it and listen again, you will not get the result.

Two: Handwritten Promise

1. Instance methods (catch, finally)

Design ideas

internal call then

Handle exceptions

Code implementation

<body>
    <script>
        //define function
        function runAsynctask(callback) {// callback is a callback function
            // Call core api
            if (typeof queueMicrotask === 'function') { // The three functions are called to solve the browser incompatibility problem. First determine whether they are functions.
                queueMicrotask(callback)
            } else if (typeof MutationObserver === "function") {
                const obs = new MutationObserver(callback)
                const divNode = document.createElement('div')
                obs.observe(divNode, { childList: true })
                divNode.innerHTML = 'Change the following content'
            } else {
                setTimeout(callback, 0)
            }
        }

        const PENDING = 'pending'
        const FULFILLED = 'fulfilled'
        const REJECTED = 'rejected'
        class wePromise {
            state = PENDING // state
            result = undefined // reason
            #handlers = [] // [{onFulfilled,onReject},...]
            // Constructor
            constructor(func) {
                //Change status, pending => fulfilled
                const reslove = (result) => {
                    if (this.state === PENDING) {
                        this.state = FULFILLED
                        this.result = result
                        // When the following is asynchronous, save it first, and then execute it at this step, take out the saved function and execute it
                        this.#handlers.forEach(({ onFulfilled }) => { // Destructuring
                            onFulfilled(this.result)
                        })
                    }
                }
                //Change status, pending => rejected
                const reject = (result) => {
                    if (this.state === PENDING) {
                        this.state = REJECTED
                        this.result = result
                        this.#handlers.forEach(({ onReject }) => {
                            onReject(this.result)
                        })
                    }
                }

                //Exception handling
                try {
                    //Execute callback function
                    func(reslove,reject)
                } catch (error) {
                    reject(error)
                }
            }

            then(onFulfilled, onReject) {
                // Determine whether the parameter passed in is a function
                onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
                onReject = typeof onReject === 'function' ? onReject : x => { throw x }

                const p2 = new wePromise((reslove, reject) => {
                    // Determine the status after execution is completed
                    if (this.state === FULFILLED) {
                        runAsynctask(() => {
                            try {
                                // Get the return value
                                const x = onFulfilled(this.result) // Return result
                                reslovePromise(p2, x, reslove, reject)
                            } catch (error) {
                                // Handle exceptions
                                reject(error)
                            }

                        })
                    } else if (this.state === REJECTED) {
                        runAsynctask(() => {
                            try {
                                const x = onReject(this.result)
                                reslovePromise(p2, x, reslove, reject)
                            } catch (error) {
                                reject(error)
                            }
                        })
                    } else if (this.state === PENDING) { // The state has not changed yet, indicating that it is asynchronous
                        //Save callback function
                        this.#handlers.push({
                            onFulfilled: () => {
                                runAsynctask(() => {
                                    try {
                                        const x = onFulfilled(this.result) // Return result
                                        reslovePromise(p2, x, reslove, reject)
                                    } catch (error) {
                                        reject(error)
                                    }
                                })
                            },
                            onReject: () => {
                                runAsynctask(() => {
                                    try {
                                        const x = onReject(this.result) // Return result
                                        reslovePromise(p2, x, reslove, reject)
                                    } catch (error) {
                                        reject(error)
                                    }
                                })
                            }
                        })
                    }
                })


                return p2
            }
            catch(onReject) {
                // Call the then method internally
                return this.then(undefined, onReject)
            }
            finally(onFinally){
                return this.then(onFinally,onFinally)
            }
        }

        function reslovePromise(p2, x, reslove, reject) {
            // Handle duplicate references
            if (x === p2) {
                // throw error
                throw new TypeError('Chaining cycle detected for promise #<Promise>')
            }
            //Handle the returned Promise
            if (x instanceof wePromise) {
                // Call the then method
                x.then(res => reslove(res), err => reject(err))
            } else {
                // handle return value
                reslove(x)
            }
        }
        // Create object and call two methods
        const p = new wePromise((reslove, reject) => {
            // reslove('success')
            // reject('reject')
            throw 'throw-error'
        })
        p.then(res => {
            console.log('Successful callback 1:', res);
            // throw 'throw-error'
            // return 2
        }).catch(err => {
            console.log('Failure callback 1:', err);

        }).finally(() => {
            console.log('finally');
        })
    </script>
</body>

Operating effect

2. Static methods (reslove, reject, race)

reslove, reject design ideas

  • Determine the incoming value
  • Promise returns
  • Convert to Promise return

race design ideas

  • Return Promise
  • Determine whether it is an array
  • Waiting for the first one to be finalized

all design ideas

  • Return Promise
  • Determine whether it is an array
  • Empty array can be converted directly
  • Process all redemptions (record results –> judge all redemptions)
  • Handle first rejection

allSettled design ideas

  • Return Promise
  • Determine whether it is an array
  • Empty array can be converted directly
  • Waiting for everything to be finalized (record results –> process exchange or reject)

any design ideas

  • Return Promise
  • Determine whether it is an array
  • Empty arrays are rejected directly.
  • Waiting for results (first to cash out, all to reject)

Code implementation

<body>
    <script>
        //define function
        function runAsynctask(callback) {// callback is a callback function
            // Call core api
            if (typeof queueMicrotask === 'function') { // The three functions are called to solve the browser incompatibility problem. First determine whether they are functions.
                queueMicrotask(callback)
            } else if (typeof MutationObserver === "function") {
                const obs = new MutationObserver(callback)
                const divNode = document.createElement('div')
                obs.observe(divNode, { childList: true })
                divNode.innerHTML = 'Change the following content'
            } else {
                setTimeout(callback, 0)
            }
        }

        const PENDING = 'pending'
        const FULFILLED = 'fulfilled'
        const REJECTED = 'rejected'
        class wePromise {
            state = PENDING // state
            result = undefined // reason
            #handlers = [] // [{onFulfilled,onReject},...]
            // Constructor
            constructor(func) {
                //Change status, pending => fulfilled
                const reslove = (result) => {
                    if (this.state === PENDING) {
                        this.state = FULFILLED
                        this.result = result
                        // When the following is asynchronous, save it first, and then execute it at this step, take out the saved function and execute it
                        this.#handlers.forEach(({ onFulfilled }) => { // Destructuring
                            onFulfilled(this.result)
                        })
                    }
                }
                //Change status, pending => rejected
                const reject = (result) => {
                    if (this.state === PENDING) {
                        this.state = REJECTED
                        this.result = result
                        this.#handlers.forEach(({ onReject }) => {
                            onReject(this.result)
                        })
                    }
                }

                //Exception handling
                try {
                    //Execute callback function
                    func(reslove,reject)
                } catch (error) {
                    reject(error)
                }
            }

            then(onFulfilled, onReject) {
                // Determine whether the parameter passed in is a function
                onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
                onReject = typeof onReject === 'function' ? onReject : x => { throw x }

                const p2 = new wePromise((reslove, reject) => {
                    // Determine the status after execution is completed
                    if (this.state === FULFILLED) {
                        runAsynctask(() => {
                            try {
                                // Get the return value
                                const x = onFulfilled(this.result) // Return result
                                reslovePromise(p2, x, reslove, reject)
                            } catch (error) {
                                // Handle exceptions
                                reject(error)
                            }

                        })
                    } else if (this.state === REJECTED) {
                        runAsynctask(() => {
                            try {
                                const x = onReject(this.result)
                                reslovePromise(p2, x, reslove, reject)
                            } catch (error) {
                                reject(error)
                            }
                        })
                    } else if (this.state === PENDING) { // The state has not changed yet, indicating that it is asynchronous
                        //Save callback function
                        this.#handlers.push({
                            onFulfilled: () => {
                                runAsynctask(() => {
                                    try {
                                        const x = onFulfilled(this.result) // return result
                                        reslovePromise(p2, x, reslove, reject)
                                    } catch (error) {
                                        reject(error)
                                    }
                                })
                            },
                            onReject: () => {
                                runAsynctask(() => {
                                    try {
                                        const x = onReject(this.result) // Return result
                                        reslovePromise(p2, x, reslove, reject)
                                    } catch (error) {
                                        reject(error)
                                    }
                                })
                            }
                        })
                    }
                })


                return p2
            }

            //Instance method
            catch(onReject) {
                // Call the then method internally
                return this.then(undefined, onReject)
            }
            finally(onFinally) {
                return this.then(onFinally, onFinally)
            }

            // static method
            static reslove(value) {
                // Determine whether the passed in is a Promise object
                if (value instanceof wePromise) {
                    return value
                }
                return new wePromise((reslove, reject) => {
                    reslove(value)
                })

            }
            static reject(value) {
                return new wePromise((reslove, reject) => {
                    reject(value)
                })
            }
            static race(promises) {
                return new wePromise((reslove, reject) => {
                    // Determine whether it is an array
                    if (!Array.isArray(promises)) {
                        return reject(new TypeError('Argument is not iterable'))
                    }
                    // Wait for the first one to be finalized
                    promises.forEach(p => {
                        wePromise.reslove(p).then(res => { reslove(res) }, err => { reject(err) })
                    })
                })
            }
            // important
            static all(promises) {
                // Return Promise instance
                return new wePromise((reslove, reject) => {
                    // Determine whether it is an array
                    if (!Array.isArray(promises)) {
                        return reject(new TypeError('Argument is not iterable'))
                    }
                    // Empty array can be converted directly
                    promises.length === 0 & amp; & amp; reslove(promises)
                    // handle all redemptions
                    const results = []
                    let count = 0
                    promises.forEach((p, index) => {
                        wePromise.reslove(p).then(res => {
                            results[index] = res
                            count++
                            // Judge all cash
                            count === promises.length & amp; & amp; reslove(results)
                        }, err => {
                            // Handle the first rejection
                            reject(err)
                        })
                    })
                })
            }
            static allSettled(promises) {
                // Return Promise instance
                return new wePromise((reslove, reject) => {
                    // Determine whether it is an array
                    if (!Array.isArray(promises)) {
                        return reject(new TypeError('Argument is not iterable'))
                    }
                    // Empty array can be converted directly
                    promises.length === 0 & amp; & amp; reslove(promises)
                    // Waiting for everything to be finalized
                    const results = []
                    let count = 0
                    promises.forEach(p => {
                        //The reason why this static method is used is because it wraps the incoming object into wePromise
                        wePromise.reslove(p).then((res => {
                            // Handle redemption
                            results[index] = { status: FULFILLED, value: res }
                            count++
                            count === promises.length & amp; & amp; reslove(results)
                        }, err => {
                            results[index] = { status: REJECTED, reason: err }
                            count++
                            count === promises.length & amp; & amp; reslove(results)
                        }))
                    })

                })
            }
            static any(promises) {
                return new wePromise((reslove, reject) => {
                    // Determine whether it is an array
                    if (!Array.isArray(promises)) {
                        return reject(new TypeError('Argument is not iterable'))
                    }
                    // Empty arrays are rejected directly
                    promises.length === 0 & amp; & amp; reject(new AggregateError(promises, 'All promises were rejected'))

                    // Waiting for results
                    const errors = []
                    let count = 0
                    promises.forEach((p, index) => {
                        wePromise.reslove(p).then(res => {
                            // first to cash out
                            reslove(res)
                        }, err => {
                            // reject all
                            errors === promises.length & amp; & amp; reject(new AggregateError(errors, 'All promises were rejected'))
                        })
                    })


                })
            }

        }

        function reslovePromise(p2, x, reslove, reject) {
            // Handle duplicate references
            if (x === p2) {
                // throw error
                throw new TypeError('Chaining cycle detected for promise #<Promise>')
            }
            //Handle the returned Promise
            if (x instanceof wePromise) {
                // Call the then method
                x.then(res => reslove(res), err => reject(err))
            } else {
                // handle return value
                reslove(x)
            }
        }
        // Create object and call two methods
        const p = new wePromise((reslove, reject) => {
            // reslove('success')
            // reject('reject')
            throw 'throw-error'
        })
        p.then(res => {
            console.log('Successful callback 1:', res);
            // throw 'throw-error'
            // return 2
        }).catch(err => {
            console.log('Failure callback 1:', err);

        }).finally(() => {
            console.log('finally');
        })
    </script>
</body>

Operating effect

Please copy the code to test the running effect here. All instance methods and static methods have been implemented.

Three: Summary

Promise is a solution for asynchronous programming. It is very commonly used in project development at work. Proficient in mastering the content of Promise and being familiar with the underlying source code can enable us to have higher code writing capabilities in project development while reducing costs. I encountered various bugs when using Promise. Well, this concludes the Promise source code series. I hope it can be helpful to all of you!