实现一个简单的 Promise

161 阅读13分钟

一、引言

为什么需要手写 Promise:

在现代前端开发中,异步编程是不可或缺的一部分,而Promise作为处理异步操作的核心工具,为我们提供了一种更优雅、更高效的方式来管理复杂的异步逻辑。通过手写实现一个简化版的Promise,我们能够更深入地理解其内部机制,从而更好地掌握异步编程的核心思想。以下是手写Promise的几个意义:

1. 深入理解异步编程机制

  • 通过手写Promise,我们可以更清晰地理解异步任务的调度和执行机制,从而更好地理解事件循环、任务队列等底层概念。

2. 掌握 Promise 核心原理

  • Promise的核心在于状态管理、链式调用和错误处理。
  • 通过手写实现,我们可以深入理解Promise的三种状态(pendingfulfilledrejected)是如何转换的,以及如何通过then方法实现链式调用和错误冒泡。

3. 为学习更复杂的异步方案打下基础

  • 理解Promise是学习async/awaitGenerator的基础。

目标:

在本篇文章中,我们将实现一个简化版的Promise,目标是:

1. 符合 Promises/A+ 基本规范

  • 支持Promise的三种状态(pendingfulfilledrejected)。
  • 实现then方法,支持链式调用和异步执行。

2. 支持链式调用

  • 通过then方法实现多个异步任务的顺序执行。

3. 支持异步执行

  • 确保Promise的异步任务能够正确地放入任务队列中执行,而不是同步执行。

4. 错误处理

  • 实现catch方法,支持错误捕获和冒泡。

二、Promise 的基本概念

1. Promise 三种状态

  • pending:初始状态,表示异步操作尚未完成。
  • fulfilled:异步操作已兑现。
  • rejected:异步操作已拒绝。
  • 状态只能从pending变为fulfilledrejected,且不可逆。

2. Promise 的基本用法

  • promise.then:用于注册兑现或拒绝的回调。
  • promise.catch:用于注册拒绝回调。
  • promise.finally:用于注册Promise完成后的回调,无论兑现或拒绝。
const promise = new Promise((resolve, reject) => {
  // 运行异步代码
})

promise.then(
  (value) => { /* 处理兑现结果 */ },
  (reason) => { /* 处理拒绝结果 */ }
)

promise.catch(
  (reason) => { /* 处理拒绝结果 */ },
)

promise.finally(
  () => { /* 处理操作完成 */ }
)

3. Promise 的链式调用

Promise支持链式调用,我们可以将.then.catch.finally依次串联起来。

const promise = new Promise((resolve, reject) => {
  // 运行异步代码
});

promise
  .then(handleFulfilledA, handleRejectedA)
  .then(handleFulfilledB)
  .then(handleFulfilledC, handleRejectedC)
  .catch(handleRejectedAny)
  .finally(handleResolved)

4. Promise 常用的静态方法

  • promise.resolve:创建一个已兑现的Promise
  • promise.reject:创建一个已拒绝的Promise
  • promise.race:返回第一个完成(无论兑现或拒绝)的Promise的结果。
  • promise.all:处理多个Promise,返回所有Promise的结果。如果所有Promise都兑现,返回一个包含所有兑现结果的数组;如果任一Promise拒绝,则整体拒绝,并返回第一个拒绝的原因。

三、实现一个简单的 Promise

1. 创建一个基础的 Promise 类

存储状态和值

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

class SimplePromise {
    constructor() {
        this.state = PENDING; // 初始状态
        this.value = null; // 兑现值或拒绝原因
        this.handlers = []; // 回调队列
    }
}

2. 实现resolvereject方法

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

class SimplePromise {
    constructor(executor) {
        this.state = PENDING; // 初始状态
        this.value = null; // 兑现值或拒绝原因
        this.handlers = []; // 回调队列

        try {
            executor(this.resolve.bind(this), this.reject.bind(this));
        } catch(error) {
            this.reject(error);
        }
    }
    
    resolve(value) {
        this.setValue(FULFILLED, value);
    }

    reject(reason) {
        this.setValue(REJECTED, reason);
    }

    setValue(state, value) {
        if (this.state !== PENDING) {
            return;
        }

        this.state = state; // 变更状态
        this.value = value; // 存储兑现值或拒绝原因
        this.handlers.forEach(handler => this.handle(handler)); // 执行回调
    }
    
    handle(handler) {
        // ...
    }
}

3. 实现then方法

(1) 初步实现then方法

我们先实现一个简单的then方法,用于注册回调函数。

then(onFulfilled, onRejected) {
    this.handle({
        onFulfilled,
        onRejected,
    });
}

handle(handler) {
    if (this.state === PENDING) {
        this.handlers.push(handler); // 状态未确定,加入回调队列
    } else {
        const callback = this.state === FULFILLED ? handler.onFulfilled : handler.onRejected;

        if (callback) {
            callback(this.value); // 执行回调
        }
    }
}
(2)实现 Promise 链式调用

如果要实现Promise的链式调用,then方法需要返回一个Promise实例。

如果then方法返回当前Promise实例,value的值将永远是第一个Promisevalue,无法实现链式调用的值传递。

因此每次then方法都需要返回一个新的Promise实例,并在handle方法里处理回调函数的返回值,将其传递给新的Promise实例。

then(onFulfilled, onRejected) {
    // 创建新 Promise 用于链式调用
    return new SimplePromise((resolve, reject) => {
        // 传递新 Promise 的 resolve 和 reject 到 handle 方法
        // 以便根据回调结果改变新 Promise 状态
        this.handle({
            onFulfilled: typeof onFulfilled === 'function' ? onFulfilled : null,
            onRejected: typeof onRejected === 'function' ? onRejected : null,
            resolve,
            reject,
        });
    });
}

handle(handler) {
    if (this.state === PENDING) {
        this.handlers.push(handler); // 状态未确定,加入回调队列
    } else {
        const callback = this.state === FULFILLED ? handler.onFulfilled : handler.onRejected;
        const next = this.state === FULFILLED ? handler.resolve : handler.reject;

        if (callback) {
            try {
                const result = callback(this.value); // 执行回调
                handler.resolve(result); // 回调函数执行成功,将结果传递给下一个 Promise
            } catch (error) {
                handler.reject(error); // 如果回调函数执行过程中抛出异常,将异常传递给下一个 Promise
            }
        } else {
            // 如果没有传递回调函数,将当前 Promise 的值传递给下一个 Promise
            next(this.value);
        }
    }
}
(3)处理then方法返回 Promise 实例的情况

then方法返回一个Promise实例时,我们需要等待该Promise完成,并根据其回调结果改变下一个Promise的状态。

then(onFulfilled, onRejected) {
    // 创建新 Promise 用于链式调用
    return new SimplePromise((resolve, reject) => {
        // 传递新 Promise 的 resolve 和 reject 到 handle 方法
        // 以便根据回调结果改变新 Promise 状态
        this.handle({
            onFulfilled: typeof onFulfilled === 'function' ? onFulfilled : null,
            onRejected: typeof onRejected === 'function' ? onRejected : null,
            resolve,
            reject,
        });
    });
}

handle(handler) {
    if (this.state === PENDING) {
        this.handlers.push(handler); // 状态未确定,加入回调队列
    } else {
        const callback = this.state === FULFILLED ? handler.onFulfilled : handler.onRejected;
        const next = this.state === FULFILLED ? handler.resolve : handler.reject;

        if (callback) {
            try {
                const result = callback(this.value); // 执行回调
                if (result instanceof SimplePromise) {
                    // 回调函数返回的 Promise
                    // 调用返回的 Promise 的 then 方法
                    // 并将下一个 Promise 的 resolve 和 reject 方法作为参数传入
                    // 以便在返回的 Promise 完成时,正确传递其结果
                    result.then(handler.resolve, handler.reject);
                } else {
                    handler.resolve(result); // 回调函数返回普通值,直接传递给下一个 Promise
                }
            } catch (error) {
                handler.reject(error); // 如果回调函数执行过程中抛出异常,将异常传递给下一个 Promise
            }
        } else {
            // 如果没有传递回调函数,将当前 Promise 的值传递给下一个 Promise
            next(this.value);
        }
    }
}
(4)处理resolve方法返回 Promise 实例的情况

如果resolve方法传入Promise,需要展平该Promise。即等待传入的Promise完成,并根据其结果决定当前Promise的状态

resolve(value) {
    if (value instanceof SimplePromise) {
        // 调用传入的 Promise 的 then 方法
        // 并将当前 Promise 的 resolve 和 reject 方法作为参数传入
        // 以便在传入的 Promise 完成时,正确传递其结果
        value.then(this.resolve.bind(this), this.reject.bind(this));
        return;
    }

    this.setValue(FULFILLED, value);
}

4. 模拟微任务的机制

当前的SimplePromise实现基本符合Promise的核心功能,但在事件循环和微任务的执行顺序上存在一些问题。

(1)事件循环与微任务

  • 原生Promise的回调函数会被放入微任务队列,在当前宏任务执行完毕后立即执行。
  • 当前的SimplePromise实现中,回调函数是同步执行的,没有模拟微任务的机制。导致回调函数的执行顺序与原生Promise不一致。

(2)resolve 方法中的递归调用

  • resolve方法中,如果传入的值是一个Promise实例,会递归调用then方法。这种实现没有考虑微任务的执行顺序。
  • 如果递归调用过多,可能会导致栈溢出。

为了符合事件循环和微任务的执行顺序,我们需要对SimplePromise进行以下改进:

(1)模拟微任务队列

  • 使用queueMicrotask模拟微任务的执行顺序。
  • 将回调函数放入微任务队列,确保它们在当前宏任务执行完毕后执行。

(2)优化 resolve 方法

  • resolve方法中,如果传入的值是一个Promise实例,使用微任务机制等待其完成,而不是直接递归调用。

以下是修改后的SimplePromise实现:

resolvereject方法:

resolve(value) {
    if (value instanceof SimplePromise) {
        // 使用微任务队列等待传入的 Promise 完成
        queueMicrotask(() => {
            // 调用传入的 Promise 的 then 方法
            // 并将当前 Promise 的 resolve 和 reject 方法作为参数传入
            // 以便在传入的 Promise 完成时,正确传递其结果
            value.then(this.resolve.bind(this), this.reject.bind(this));
        });
        return;
    }

    this.setValue(FULFILLED, value);
}

reject(reason) {
    this.setValue(REJECTED, reason);
}
  • 如果传入的值是一个Promise实例,使用queueMicrotask等待其完成。
  • 如果传入的值是普通值,使用queueMicrotask确保状态变更和回调函数的异步执行。

handle方法

handle(handler) {
    if (this.state === PENDING) {
        this.handlers.push(handler); // 状态未确定,加入回调队列
    } else {
        // 使用微任务执行回调
        queueMicrotask(() => {
            const callback = this.state === FULFILLED ? handler.onFulfilled : handler.onRejected;
            const next = this.state === FULFILLED ? handler.resolve : handler.reject;

            if (callback) {
                try {
                    const result = callback(this.value); // 执行回调
                    if (result instanceof SimplePromise) {
                        // 回调函数返回的 Promise
                        // 调用返回的 Promise 的 then 方法
                        // 并将下一个 Promise 的 resolve 和 reject 方法作为参数传入
                        // 以便在返回的 Promise 完成时,正确传递其结果
                        result.then(handler.resolve, handler.reject);
                    } else {
                        handler.resolve(result); // 回调函数返回普通值,直接传递给下一个 Promise
                    }
                } catch (error) {
                    handler.reject(error); // 如果回调函数执行过程中抛出异常,将异常传递给下一个 Promise
                }
            } else {
                // 如果没有传递回调函数,将当前 Promise 的值传递给下一个 Promise
                next(this.value);
            }
        });
    }
}
  • 使用queueMicrotask确保回调函数的异步执行。

四、功能扩展与优化

1. 添加catch方法

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

2. 添加finally方法

finally方法在Promise完成(无论兑现或拒绝)时会执行。虽然第一反应是通过this.then(onFinally, onFinally)实现:

finally(onFinally) {
    return this.then(onFinally, onFinally);
}

但这种实现方式存在一些问题,原因如下:

(1)回调函数不接收参数

原生的Promise.finally的回调函数不接收Promise的结果,它仅执行回调函数。

如果直接使用this.then(onFinally, onFinally)onFinally会接收到Promise的结果,这与规范不符。

Promise.resolve('success')
    .finally(() => {
        // 这里没有参数传入
        console.log('finally executed'); 
    })
    .then(value => console.log(value));

// 错误实现
// 直接使用`this.then(onFinally, onFinally)`
SimplePromise.resolve('success')
    .finally((result) => {
        console.log(result); // 输出:success(不符合规范)
        return result;
    })
    .then(value => console.log(value));

(2)不改变 Promise 的状态和值

原生的Promise.finally不会改变原Promise的状态和值。无论原Promise是兑现还是拒绝,finally执行后返回的Promise会保持原有的状态和值。

如果直接使用this.then(onFinally, onFinally),当onFinally返回一个值或抛出错误时,会改变原Promise的状态和值。

Promise.resolve('original value')
    .finally(() => {
        // 返回值不会影响原 Promise 的结果
        return 'new value';
    })
    .then(value => console.log(value)); // 输出:original value

// 错误实现
// 直接使用`this.then(onFinally, onFinally)`
SimplePromise.resolve('original value')
    .finally(() => {
        // 返回值会影响原 Promise 的结果
        return 'new value';
    })
    .then(value => console.log(value)); // 输出:new value

(3)处理 onFinally 返回 Promise 的情况

如果onFinally返回一个Promise,原生的Promise.finally会等待该Promise完成后再继续执行后续逻辑。

如果直接使用this.then(onFinally, onFinally),代码会直接继续执行,不会等待onFinally返回的Promise完成。

Promise.resolve('Original value')
    .finally(() => {
        return new Promise(resolve => {
            setTimeout(() => {
                console.log('onFinally Promise resolved');
                resolve();
            }, 1000);
        });
    }).then(value => console.log('finally value:', value));
// 输出:onFinally Promise resolved
// 输出:finally value: Original value

// 错误实现
// 直接使用`this.then(onFinally, onFinally)`
SimplePromise.resolve('Original value')
    .finally(() => {
        return new Promise(resolve => {
            setTimeout(() => {
                console.log('onFinally Promise resolved');
                resolve();
            }, 1000);
        });
    }).then(value => console.log('finally value:', value));
// 输出:finally value: Original value
// 输出:onFinally Promise resolved(顺序错误)

正确实现

为了符合规范,finally方法需要确保:

  1. 回调函数不接收参数。
  2. 不改变原Promise的状态和值。
  3. 正确处理onFinally返回的Promise

以下是正确实现:

finally(onFinally) {
    return this.then(
        value => SimplePromise.resolve(onFinally()).then(() => value),
        error => SimplePromise.resolve(onFinally()).then(() => { throw error; })
    );
}

实现解析:

  • SimplePromise.resolve(onFinally()):确保onFinally的返回值被包装成Promise,并等待其完成
  • .then(() => value):兑现时保留原Promise的值
  • .then(() => { throw error; }):拒绝时抛出原Promise的错误

3. 实现静态方法resolvereject

Promise.resolvePromise.rejectPromise的两个重要静态方法,用于快速创建已兑现或已拒绝的Promise对象。以下是实现细节:

Promise.resolve的实现:

  • 如果Promise.resolve传入的值是一个Promise对象,则直接返回该Promise对象;
  • 如果Promise.resolve传入的值是一个 thenable 对象,则调用其then方法,并返回一个已兑现的Promise对象;
  • 如果Promise.resolve其他值,则返回一个已兑现的Promise对象,其兑现值就是传入的值;
static resolve(value) {
    if (value instanceof SimplePromise) {
        return value; // 直接返回 Promise
    }

    if (typeof value === 'object' && typeof value.then === 'function') {
        return new SimplePromise((resolve, reject) => {
            value.then(resolve, reject); // 处理 thenable 对象
        });
    }

    return new SimplePromise((resolve) => resolve(value)); // 普通值
}

static reject(reason) {
    return new SimplePromise((_, reject) => reject(reason));
}

4. 实现静态方法allrace

static all(promises) {
    return new SimplePromise((resolve, reject) => {
        let results = new Array(promises.length);
        let completedCount = 0;

        if (promises.length === 0) {
            resolve(results);
            return;
        }

        promises.forEach((promise, index) => {
            SimplePromise.resolve(promise).then(
                value => {
                    results[index] = value;
                    completedCount++;

                    if (completedCount === promises.length) {
                        resolve(results)
                    }
                },
                reject // 任一 Promise 拒绝,整体拒绝
            );
        });
    })
}

static race(promises) {
    return new SimplePromise((resolve, reject) => {
        if (promises.length === 0) {
            return;
        }

        promises.forEach(promise => {
            SimplePromise.resolve(promise).then(resolve, reject); // 第一个完成的 Promise 决定结果
        });
    })
}

五、完整代码实现

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

class SimplePromise {
    static resolve(value) {
        if (value instanceof SimplePromise) {
            return value; // 直接返回 Promise
        }

        if (typeof value === 'object' && typeof value.then === 'function') {
            return new SimplePromise((resolve, reject) => {
                value.then(resolve, reject); // 处理 thenable 对象
            });
        }

        return new SimplePromise((resolve) => resolve(value)); // 普通值
    }
    
    static reject(reason) {
        return new SimplePromise((_, reject) => reject(reason));
    }
    
    static all(promises) {
        return new SimplePromise((resolve, reject) => {
            let results = new Array(promises.length);
            let completedCount = 0;

            if (promises.length === 0) {
                resolve(results);
                return;
            }

            promises.forEach((promise, index) => {
                SimplePromise.resolve(promise).then(
                    value => {
                        results[index] = value;
                        completedCount++;

                        if (completedCount === promises.length) {
                            resolve(results)
                        }
                    },
                    reject // 任一 Promise 拒绝,整体拒绝
                );
            });
        })
    }

    static race(promises) {
        return new SimplePromise((resolve, reject) => {
            if (promises.length === 0) {
                return;
            }

            promises.forEach(promise => {
                SimplePromise.resolve(promise).then(resolve, reject); // 第一个完成的 Promise 决定结果
            });
        })
    }

    constructor(executor) {
        this.state = PENDING; // 初始状态
        this.value = null; // 兑现值或拒绝原因
        this.handlers = []; // 回调队列

        try {
            executor(this.resolve.bind(this), this.reject.bind(this));
        } catch(error) {
            this.reject(error);
        }
    }
    
    resolve(value) {
        if (value instanceof SimplePromise) {
            // 使用微任务等待传入的 Promise 完成
            queueMicrotask(() => {
                value.then(
                  val => this.resolve(val), // 兑现时传递结果
                  err => this.reject(err)   // 拒绝时传递错误
                );
            });
            return;
        }

        this.setValue(FULFILLED, value);
    }

    reject(reason) {
        this.setValue(REJECTED, reason);
    }

    setValue(state, value) {
        // 状态不可逆
        if (this.state !== PENDING) {
            return;
        }

        this.state = state; // 变更状态
        this.value = value; // 存储兑现值或拒绝原因
        this.handlers.forEach(handler => this.handle(handler)); // 执行回调
    }
    
    then(onFulfilled, onRejected) {
        // 创建新 Promise 用于链式调用
        return new SimplePromise((resolve, reject) => {
            // 传递新 Promise 的 resolve 和 reject 到 handle 方法
            // 以便根据回调结果改变新 Promise 状态
            this.handle({
                onFulfilled: typeof onFulfilled === 'function' ? onFulfilled : null,
                onRejected: typeof onRejected === 'function' ? onRejected : null,
                resolve,
                reject,
            });
        });
    }
    
    catch(onRejected) {
        return this.then(null, onRejected);
    }
    
    finally(onFinally) {
        return this.then(
            value => SimplePromise.resolve(onFinally()).then(() => value),
            error => SimplePromise.resolve(onFinally()).then(() => { throw error; })
        );
    }
    
    handle(handler) {
        if (this.state === PENDING) {
            this.handlers.push(handler); // 状态未确定,加入回调队列
        } else {
            // 使用微任务执行回调
            queueMicrotask(() => {
                const callback = this.state === FULFILLED ? handler.onFulfilled : handler.onRejected;
                const next = this.state === FULFILLED ? handler.resolve : handler.reject;

                if (callback) {
                    try {
                        const result = callback(this.value); // 执行回调
                        if (result instanceof SimplePromise) {
                            // 回调函数返回的 Promise
                            // 调用返回的 Promise 的 then 方法
                            // 并将下一个 Promise 的 resolve 和 reject 方法作为参数传入
                            // 以便在返回的 Promise 完成时,正确传递其结果
                            result.then(handler.resolve, handler.reject);
                        } else {
                            handler.resolve(result); // 回调函数返回普通值,直接传递给下一个 Promise
                        }
                    } catch (error) {
                        handler.reject(error); // 如果回调函数执行过程中抛出异常,将异常传递给下一个 Promise
                    }
                } else {
                    // 如果没有传递回调函数,将当前 Promise 的值传递给下一个 Promise
                    next(this.value);
                }
            });
        }
    }
}

六、测试用例

1. 测试基本功能

const promise1 = new SimplePromise((resolve) => {
  setTimeout(() => {
    resolve('兑现')
  }, 1000);
});

promise1.then(value => {
    console.log(value); // 输出:兑现
});

2. 测试链式调用

const promise2 = new SimplePromise((resolve) => {
    setTimeout(() => {
        resolve(1)
    }, 100);
});

promise2
    .then(value => {
        console.log(value); // 输出:1
        return value + 1
    })
    .then(value => {
        console.log(value); // 输出:2
    });

3. 测试错误处理

const promise3 = new SimplePromise((resolve, reject) => {
    setTimeout(() => {
        reject('拒绝')
    }, 1000);
});

promise3.catch(error => {
    console.log(error); // 输出:拒绝
});

4. 测试finally方法

const promise4 = new SimplePromise((resolve) => {
    setTimeout(() => {
        resolve('finally test');
    }, 100);
});

promise4
    .finally(() => {
        console.log('finally executed');
    })
    .then(value => {
        console.log(value); // 输出:finally test
    });

5. 测试Promise.resolve方法

const promise5 = SimplePromise.resolve(100);

promise5.then(value => {
    console.log(value); // 输出:100
});

6. 测试Promise.reject方法

const promise6 = SimplePromise.reject(200);

promise6.catch(error => {
    console.log(error); // 输出:200
});

7. 测试Promise.all方法

const promise7 = SimplePromise.resolve(1);
const promise8 = SimplePromise.resolve(2);
const promise9 = SimplePromise.resolve(3);

SimplePromise.all([promise7, promise8, promise9])
    .then(values => {
        console.log(values); // 输出:[1, 2, 3]
    });

// 测试空数组
SimplePromise.all([])
    .then(values => {
        console.log(values); // 输出:[]
    });

// 测试包含非Promise值
SimplePromise.all([1, 2, 3])
    .then(values => {
        console.log(values); // 输出:[1, 2, 3]
    });

8. 测试Promise.race方法

const promise10 = new SimplePromise((resolve) => {
    setTimeout(() => {
        resolve('promise10');
    }, 200);
});

const promise11 = new SimplePromise((resolve) => {
    setTimeout(() => {
        resolve('promise11');
    }, 100);
});

SimplePromise.race([promise10, promise11])
    .then(value => {
        console.log(value); // 输出:promise11
    });

// 测试空数组
SimplePromise.race([])
    .then(value => {
        console.log(value); // 不会输出,因为没有Promise完成
    });

9. 测试链式调用中的错误处理

const promise12 = new SimplePromise((resolve, reject) => {
    setTimeout(() => {
        reject('error');
    }, 100);
});

promise12
    .then(value => {
        console.log(value); // 不会执行
    })
    .catch(error => {
        console.log(error); // 输出:error
        return 'recovered';
    })
    .then(value => {
        console.log(value); // 输出:recovered
    });

10. 测试resolve方法传入Promise的情况

const promise13 = new SimplePromise((resolve) => {
    setTimeout(() => {
        resolve('promise13');
    }, 100);
});

const promise14 = new SimplePromise((resolve) => {
    setTimeout(() => {
        resolve(promise13);
    }, 100);
});

promise14.then(value => {
    console.log(value); // 输出:promise13
});