重写Promise

238 阅读6分钟

自定义 Promise 实现

下面是一个使用 JavaScript 手写的比较完善的 Promise 实现,包含了所有常用的方法。这个实现涵盖了 Promise 的基本功能以及静态方法,如 resolverejectallraceallSettledany

文件结构

假设我们将所有代码保存在 MyPromise.js 文件中。

MyPromise.js

class MyPromise {
    // 构造函数
    constructor(executor) {
        this.state = 'pending'; // 状态:pending, fulfilled, rejected
        this.value = undefined; // 成功时的值
        this.reason = undefined; // 失败时的原因
        this.onFulfilledCallbacks = []; // 成功回调队列
        this.onRejectedCallbacks = []; // 失败回调队列

        const resolve = (value) => {
            if (this.state === 'pending') {
                this.state = 'fulfilled';
                this.value = value;
                this.onFulfilledCallbacks.forEach(fn => fn(value));
            }
        };

        const reject = (reason) => {
            if (this.state === 'pending') {
                this.state = 'rejected';
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => fn(reason));
            }
        };

        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    // then 方法
    then(onFulfilled, onRejected) {
        // 确保 onFulfilled 和 onRejected 是函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

        const promise2 = new MyPromise((resolve, reject) => {
            if (this.state === 'fulfilled') {
                setTimeout(() => {
                    try {
                        const x = onFulfilled(this.value);
                        MyPromise.resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            } else if (this.state === 'rejected') {
                setTimeout(() => {
                    try {
                        const x = onRejected(this.reason);
                        MyPromise.resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0);
            } else if (this.state === 'pending') {
                this.onFulfilledCallbacks.push((value) => {
                    try {
                        const x = onFulfilled(value);
                        MyPromise.resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                });

                this.onRejectedCallbacks.push((reason) => {
                    try {
                        const x = onRejected(reason);
                        MyPromise.resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                });
            }
        });
        return promise2;
    }

    // catch 方法
    catch(onRejected) {
        return this.then(null, onRejected);
    }

    // finally 方法
    finally(onFinally) {
        return this.then(
            value => MyPromise.resolve(onFinally()).then(() => value),
            reason => MyPromise.resolve(onFinally()).then(() => { throw reason; })
        );
    }

    // 静态方法 resolve
    static resolve(value) {
        if (value instanceof MyPromise) {
            return value;
        }
        return new MyPromise((resolve) => resolve(value));
    }

    // 静态方法 reject
    static reject(reason) {
        return new MyPromise((_, reject) => reject(reason));
    }

    // 静态方法 all
    static all(promises) {
        return new MyPromise((resolve, reject) => {
            let count = 0;
            const results = [];
            const processData = (index, data) => {
                results[index] = data;
                count++;
                if (count === promises.length) {
                    resolve(results);
                }
            };
            for (let i = 0; i < promises.length; i++) {
                MyPromise.resolve(promises[i]).then(
                    data => processData(i, data),
                    reason => reject(reason)
                );
            }
        });
    }

    // 静态方法 race
    static race(promises) {
        return new MyPromise((resolve, reject) => {
            for (let i = 0; i < promises.length; i++) {
                MyPromise.resolve(promises[i]).then(resolve, reject);
            }
        });
    }

    // 静态方法 allSettled
    static allSettled(promises) {
        return new MyPromise((resolve) => {
            let count = 0;
            const results = [];
            const processData = (index, data) => {
                results[index] = data;
                count++;
                if (count === promises.length) {
                    resolve(results);
                }
            };
            for (let i = 0; i < promises.length; i++) {
                MyPromise.resolve(promises[i]).then(
                    value => processData(i, { status: 'fulfilled', value }),
                    reason => processData(i, { status: 'rejected', reason })
                );
            }
        });
    }

    // 静态方法 any
    static any(promises) {
        return new MyPromise((resolve, reject) => {
            let count = 0;
            const errors = [];
            for (let i = 0; i < promises.length; i++) {
                MyPromise.resolve(promises[i]).then(
                    value => resolve(value),
                    reason => {
                        errors[i] = reason;
                        count++;
                        if (count === promises.length) {
                            reject(new AggregateError(errors, 'All promises were rejected'));
                        }
                    }
                );
            }
        });
    }

    // 解析 Promise
    static resolvePromise(promise2, x, resolve, reject) {
        if (promise2 === x) {
            return reject(new TypeError('Chaining cycle detected for promise'));
        }
        let called = false;
        if (x != null && (typeof x === 'object' || typeof x === 'function')) {
            try {
                const then = x.then;
                if (typeof then === 'function') {
                    then.call(
                        x,
                        y => {
                            if (called) return;
                            called = true;
                            MyPromise.resolvePromise(promise2, y, resolve, reject);
                        },
                        r => {
                            if (called) return;
                            called = true;
                            reject(r);
                        }
                    );
                } else {
                    if (called) return;
                    called = true;
                    resolve(x);
                }
            } catch (error) {
                if (called) return;
                called = true;
                reject(error);
            }
        } else {
            resolve(x);
        }
    }
}

// 导出模块(如果在Node环境中使用)
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
    module.exports = MyPromise;
}

使用示例

const MyPromise = require('./MyPromise');

const promise = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('成功');
    }, 1000);
});

promise
    .then(value => {
        console.log(value); // 输出: 成功
        return '继续链式调用';
    })
    .then(value => {
        console.log(value); // 输出: 继续链式调用
        throw new Error('出错了');
    })
    .catch(error => {
        console.error(error); // 输出: Error: 出错了
    })
    .finally(() => {
        console.log('Promise 结束'); // 输出: Promise 结束
    });

说明

  1. 状态管理Promise 有三种状态:pending(待定)、fulfilled(已兑现)和rejected(已拒绝)。状态一旦从pending变为fulfilledrejected,便不可更改。

  2. 回调队列:当Promise处于pending状态时,所有的回调(onFulfilledonRejected)都会被存储在队列中,待状态改变后依次执行。

  3. 链式调用then方法会返回一个新的Promise,允许进行链式调用。通过resolvePromise方法处理返回值,确保链式调用的正确性。

  4. 静态方法

    • resolvereject:用于创建已解决或已拒绝的Promise
    • all:等待所有Promise完成,返回一个包含所有结果的Promise
    • race:竞争Promise,第一个完成的Promise的结果决定最终结果。
    • allSettled:等待所有Promise完成,无论是成功还是失败,返回所有结果。
    • any:返回第一个成功的Promise,如果全部失败则返回一个聚合错误。
  5. 错误处理:通过try-catch块捕获执行器和回调中的错误,并正确地将Promise状态设为rejected

这个实现遵循了 Promises/A+ 规范,确保了与原生Promise的兼容性。可以根据需要进一步扩展和优化。

resolvePromise 方法详解

在上述自定义 MyPromise 实现中,resolvePromise 方法是一个关键的辅助函数,用于处理 then 方法中返回的值,确保 Promise 链的正确性和遵循 Promises/A+ 规范。下面将详细解释 resolvePromise 方法的作用、工作原理以及为何如此设计。

作用

resolvePromise 的主要职责是:

  1. 解析回调返回值:在 then 方法中,onFulfilledonRejected 回调可能会返回一个值 x,这个值可能是普通值,也可能是一个 Promise 或类似 Promise 的对象(称为“thenable”)。resolvePromise 确保 x 被正确解析,并决定 promise2 的状态及其最终值。

  2. 处理链式调用:通过递归解析 Promise 链中的每一个步骤,确保最终结果的一致性和正确性,遵循 Promises/A+ 规范,避免出现循环引用等问题。

工作原理

resolvePromise 方法按照以下步骤工作:

1. 循环引用检测

if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise'));
}
  • 目的:防止 promise2x 相互引用,导致无限递归和内存泄漏。
  • 行为:如果 promise2x 指向同一个对象,立即拒绝 promise2,并抛出 TypeError

2. 检查 x 是否为对象或函数

if (x != null && (typeof x === 'object' || typeof x === 'function')) {
    // 处理 thenable
} else {
    resolve(x);
}
  • 目的:仅当 x 是对象或函数时,才有可能是 thenable,需要进一步解析;否则,直接将 x 作为 promise2 的成功值。

3. 获取 then 方法

const then = x.then;
  • 目的:尝试从 x 中获取 then 方法,以判断 x 是否为 thenable。

4. 检查 then 是否为函数

if (typeof then === 'function') {
    // 处理 thenable
} else {
    resolve(x);
}
  • 目的:只有当 then 是函数时,x 才被视为 thenable;否则,将 x 作为普通对象处理。

5. 调用 then 方法

then.call(
    x,
    y => {
        if (called) return;
        called = true;
        MyPromise.resolvePromise(promise2, y, resolve, reject);
    },
    r => {
        if (called) return;
        called = true;
        reject(r);
    }
);
  • 目的
    • 绑定 this:使用 callthis 绑定为 x,确保 then 方法在正确的上下文中执行。
    • 处理成功回调 y
      • 检查是否已经调用过(避免多次调用)。
      • 递归调用 resolvePromise,以解析 y,确保即使 y 也是 thenable,也能被正确解析。
    • 处理失败回调 r
      • 同样检查是否已经调用过。
      • 直接拒绝 promise2,传递错误原因 r

6. 错误处理

try {
    // 获取 then 并调用
} catch (error) {
    if (called) return;
    called = true;
    reject(error);
}
  • 目的:捕获在获取或调用 then 方法过程中抛出的任何错误,并将 promise2 拒绝,传递该错误。

7. 防止多次调用

let called = false;
// 在每个回调中检查 called
if (called) return;
called = true;
  • 目的:根据 Promises/A+ 规范,then 方法的 resolvereject 回调只能被调用一次。使用 called 标志确保无论是成功还是失败回调,只能执行一次。

为什么需要 resolvePromise

根据 Promises/A+ 规范,then 方法必须能够处理各种返回值,包括普通值、Promise、以及其他 thenable 对象。resolvePromise 的设计确保:

  1. 链式调用的正确性:通过递归解析每一个返回值,确保整个 Promise 链的状态和结果是正确的。

  2. 遵循规范:严格按照 Promises/A+ 规范处理各种情况,包括循环引用、thenable 解析、错误捕获等。

  3. 兼容性:确保自定义的 MyPromise 实现与原生 Promise 行为一致,能够与各种现有代码和库无缝配合。

resolvePromise 方法在自定义 Promise 实现中扮演了至关重要的角色。它不仅负责解析 then 回调返回的值,还确保 Promise 链的稳定性和规范性。通过仔细处理各种可能的情况,包括 thenable 对象、错误捕获和循环引用检测,resolvePromise 确保了 MyPromise 实现的可靠性和一致性,使其能够与原生 Promise 行为相匹配。