JS 手写 Promise

196 阅读5分钟

原理参考:Promise - JavaScript | MDN (mozilla.org) 一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending) : 初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled) : 意味着操作成功完成。
  • 已拒绝(rejected) : 意味着操作失败。

image.png

构造函数实现

class MyPromise {
    state = 'pending' // 状态,'pending' 'fulfilled' 'rejected'
    value = undefined // 成功后的值
    reason = undefined // 失败后的原因

    resolveCallbacks = [] // pending 状态下,存储成功的回调
    rejectCallbacks = [] // pending 状态下,存储失败的回调

    constructor(fn) {
        const resolveHandler = (value) => {
            if (this.state === 'pending') {
                this.state = 'fulfilled'
                this.value = value
                this.resolveCallbacks.forEach(fn => fn(this.value))
            }
        }

        const rejectHandler = (reason) => {
            if (this.state === 'pending') {
                this.state = 'rejected'
                this.reason = reason
                this.rejectCallbacks.forEach(fn => fn(this.reason))
            }
        }

        try {
            fn(resolveHandler, rejectHandler)
        } catch (err) {
            rejectHandler(err)
        }
    }
}

原型代码实现

class MyPromise {
    state = 'pending' // 状态,'pending' 'fulfilled' 'rejected'
    value = undefined // 成功后的值
    reason = undefined // 失败后的原因

    resolveCallbacks = [] // pending 状态下,存储成功的回调
    rejectCallbacks = [] // pending 状态下,存储失败的回调

    constructor(fn) {
        const resolveHandler = (value) => {
            if (this.state === 'pending') {
                this.state = 'fulfilled'
                this.value = value
                this.resolveCallbacks.forEach(fn => fn(this.value))
            }
        }

        const rejectHandler = (reason) => {
            if (this.state === 'pending') {
                this.state = 'rejected'
                this.reason = reason
                this.rejectCallbacks.forEach(fn => fn(this.reason))
            }
        }

        try {
            fn(resolveHandler, rejectHandler)
        } catch (err) {
            rejectHandler(err)
        }
    }

    then(fn1, fn2) {
        fn1 = typeof fn1 === 'function' ? fn1 : (v) => v
        fn2 = typeof fn2 === 'function' ? fn2 : (e) => e

        if (this.state === 'pending') {
            const p1 = new MyPromise((resolve, reject) => {
                this.resolveCallbacks.push(() => {
                    try {
                        const newValue = fn1(this.value)
                        resolve(newValue)
                    } catch (err) {
                        reject(err)
                    }
                })

                this.rejectCallbacks.push(() => {
                    try {
                        const newReason = fn2(this.reason)
                        reject(newReason)
                    } catch (err) {
                        reject(err)
                    }
                })
            })
            return p1
        }

        if (this.state === 'fulfilled') {
            const p1 = new MyPromise((resolve, reject) => {
                try {
                    const newValue = fn1(this.value)
                    resolve(newValue)
                } catch (err) {
                    reject(err)
                }
            })
            return p1
        }

        if (this.state === 'rejected') {
            const p1 = new MyPromise((resolve, reject) => {
                try {
                    const newReason = fn2(this.reason)
                    reject(newReason)
                } catch (err) {
                    reject(err)
                }
            })
            return p1
        }
    }

    // 就是 then 的一个语法糖,简单模式
    catch(fn) {
        return this.then(null, fn)
    }
    
    // finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数
    // 这避免了同样的语句需要在then()和catch()中各写一次的情况。
    finally(cb) {
        return this.then(cb, cb)
    }
}

静态方法:

参考:看了就会,手写 Promise 全部 API 教程,包括处于 TC39 第四阶段草案的 Promise.any() - 掘金 (juejin.cn)

MyPromise.resolve

**Promise.resolve(value)**方法返回一个以给定值解析后的Promise 对象。如果这个值是一个 promise ,那么将返回这个 promise ;如果这个值是thenable(即带有"then"方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态;否则返回的promise将以此值完成。此函数将类promise对象的多层嵌套展平。

警告: 不要在解析为自身的thenable 上调用Promise.resolve。这将导致无限递归,因为它试图展平无限嵌套的promise。一个例子是将它与Angular中的异步管道一起使用。在此处了解更多信息。

MyPromise.resolve = (value) => {
    if (value instanceof MyPromise) return value // 如果传入的是一个promise 则直接返回
    // 如果这个值是thenable(即带有`"then" `方法),返回的promise会“跟随”这个thenable的对象,采用它的最终状态
    if (value instanceof Object && 'then' in value) {
        return new MyPromise((resolve, reject) => {
            value.then(resolve, reject)
        })
    }
    return new MyPromise((resolve, reject) => resolve(value))
}
MyPromise.reject = (reason) => {
    return new MyPromise((resolve, reject) => reject(reason))
}
MyPromise.reject = (reason) => {
    return new MyPromise((resolve, reject) => reject(reason))
}

MyPromise.all = (promiseList = []) => {
    const p = new MyPromise((resolve, reject) => {
        const res = []
        const len = promiseList.length
        const count = 0
        promiseList.forEach((p) => {
            p.then(data => {
                res.push(data)
                // 不能使用 index (forEach 同步执行)
                count++
                if (len === count) {
                    resolve(res)
                }
            }).catch(e => {
                reject(e)
            })
        })
    })
    return p
}

MyPromise.race = (promiseList = []) => {
    const p = new MyPromise((resolve, reject) => {
        promiseList.forEach(p => {
            p.then(data => {
                // Promise 的状态只能改变一次
                resolve(data)
            }).catch(e => {
                reject(e)
            })
        })
    })

    return p
}

MyPromise.any

本质上,这个方法和Promise.all()是相反的。

Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。

如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise 和AggregateError类型的实例,它是 Error 的一个子类,用于把单一的错误集合在一起。

  • 如果传入的参数是一个空的可迭代对象,则返回一个 已失败(already rejected) 状态的 Promise。
  • 如果传入的参数不包含任何 promise,则返回一个 异步完成 (asynchronously resolved)的 Promise。(即将非Promise值,转换为Promise并当做成功)
  • 只要传入的迭代对象中的任何一个 promise 变成成功(resolve)状态,或者其中的所有的 promises 都失败,那么返回的 promise 就会 异步地(当调用栈为空时) 变成成功/失败(resolved/reject)状态。(如果所有Promise都失败,则报错)

MyPromise.any = (promiseList = []) => {
    const p = new MyPromise((resolve, reject) => {
        if (!Array.isArray(promiseList)) return reject(new TypeError('Argument is not iterable'))
        let errors = []
        const len = promiseList.length
        let count = 0
        // 如果传入的参数是一个空的可迭代对象,则返回一个 已失败(already rejected) 状态的 Promise。
        if (promises.length === 0) {
            return reject(new AggregateError('All promises were rejected'))
        }

        promiseList.forEach(p => {
            p.then(data => {
                // Promise 的状态只能改变一次
                resolve(data)
            }).catch(e => {
                errors.push(e)
                count++
                if (count === len) {
                    reject(errors)
                }
            })
        })
    })

    return p
}
MyPromise.allSettled()

**Promise.allSettled()**方法返回一个在所有给定的promise都已经fulfilledrejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。

当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。

相比之下,Promise.all() 更适合彼此相互依赖或者在其中任何一个reject时立即结束。

用法参考:Promise.allSettled() - JavaScript | MDN (mozilla.org)

示例:

const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));
const promises = [promise1, promise2];

Promise.allSettled(promises).
  then((results) => results.forEach((result) => console.log(result)));

// Object { status: "fulfilled", value: 3 }
// Object { status: "rejected", reason: "foo" }

代码实现:

MyPromise.allSettled = (promiseList = []) => {
    return new MyPromise((resolve, reject) => {
        if (!Array.isArray(promiseList)) return reject(new TypeError('Argument is not iterable'))
        let result = [] // 存储结果
        let count = 0 // 计数器
        const len = promiseList.length
        // 如果传入的是一个空数组,那么就直接返回一个resolved的空数组promise对象
        if (promises.length === 0) return resolve(promiseList)

        promiseList.forEach(p => {
            // 非promise值,通过Promise.resolve转换为promise进行统一处理
            MyPromise.resolve(p).then(data => {
                count++
                result.push({
                    status: 'fulfilled',
                    value: data
                })
                if (len === count) {
                    resolve(result)
                }
            }).catch((reason) => {
                count++
                result.push({
                    status: 'rejected',
                    reason
                })
                if (len === count) {
                    resolve(result)
                }
            })
        })

    })
}