深入理解 JavaScript Promise 的高效实现

引言

在现代 JavaScript 开发中,Promise 是异步操作的核心工具。无论是简单的回调替代,还是复杂的异步任务管理,Promise 都提供了一种简洁、高效的方式。然而,理解其内部实现和优化点不仅有助于掌握异步编程的本质,也能提升代码的性能与可维护性。

本文将探索如何实现一个高效、功能完善的 Promise,并剖析其中的核心机制与优化细节。


Promise 的核心机制

Promise 的底层基于状态机思想,其状态具有以下特性:

  1. 三种状态:

    • pending(初始状态):表示异步操作尚未完成。
    • fulfilled(已完成):表示异步操作成功完成。
    • rejected(已拒绝):表示异步操作失败。
  2. 不可逆性:

    • 状态只能从 pending 转为 fulfilled 或 rejected,一旦变更,状态将无法再次改变。
  3. 异步回调触发:

    • 注册的回调函数(通过 then 或 catch)必须在状态变更后异步执行,符合 JavaScript 事件循环的微任务调度规则。

优化后的 Promise 实现

以下是优化版 Promise 的完整实现,涵盖了基本功能、链式调用、静态方法等,同时对性能和易用性进行了提升。

javascript 复制代码
class OptimizedPromise {
    constructor(executor) {
        this.state = "pending"; // 初始状态
        this.value = undefined; // 成功的值
        this.reason = undefined; // 失败的原因
        this.callbacks = []; // 存储回调函数

        const resolve = (value) => {
            if (this.state === "pending") {
                this.state = "fulfilled";
                this.value = value;

                queueMicrotask(() => {
                    this.callbacks.forEach(({ onFulfilled }) => {
                        if (onFulfilled) onFulfilled(this.value);
                    });
                });
            }
        };

        const reject = (reason) => {
            if (this.state === "pending") {
                this.state = "rejected";
                this.reason = reason;

                queueMicrotask(() => {
                    this.callbacks.forEach(({ onRejected }) => {
                        if (onRejected) onRejected(this.reason);
                    });
                });
            }
        };

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

    then(onFulfilled, onRejected) {
        return new OptimizedPromise((resolve, reject) => {
            const handleCallback = () => {
                try {
                    if (this.state === "fulfilled") {
                        if (onFulfilled) {
                            const result = onFulfilled(this.value);
                            resolve(result instanceof OptimizedPromise ? result : result);
                        } else {
                            resolve(this.value);
                        }
                    } else if (this.state === "rejected") {
                        if (onRejected) {
                            const result = onRejected(this.reason);
                            resolve(result instanceof OptimizedPromise ? result : result);
                        } else {
                            reject(this.reason);
                        }
                    }
                } catch (error) {
                    reject(error);
                }
            };

            if (this.state === "pending") {
                this.callbacks.push({
                    onFulfilled: handleCallback,
                    onRejected: handleCallback,
                });
            } else {
                queueMicrotask(handleCallback);
            }
        });
    }

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

    finally(callback) {
        return this.then(
            (value) => {
                callback();
                return value;
            },
            (reason) => {
                callback();
                throw reason;
            }
        );
    }

    static resolve(value) {
        return new OptimizedPromise((resolve) => resolve(value));
    }

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

    static all(promises) {
        return new OptimizedPromise((resolve, reject) => {
            const results = [];
            let completed = 0;

            promises.forEach((promise, index) => {
                OptimizedPromise.resolve(promise)
                    .then((value) => {
                        results[index] = value;
                        completed++;
                        if (completed === promises.length) resolve(results);
                    })
                    .catch(reject);
            });
        });
    }

    static race(promises) {
        return new OptimizedPromise((resolve, reject) => {
            promises.forEach((promise) => {
                OptimizedPromise.resolve(promise).then(resolve).catch(reject);
            });
        });
    }
}

实现细节与优化点

1. 状态管理

通过 state 来记录当前的状态,并在 resolve 和 reject 中处理状态流转。不可逆状态设计确保了操作的幂等性。

2. 异步调度

使用 queueMicrotask 实现微任务调度,确保回调函数异步执行,比传统的 setTimeout 更高效且符合 ECMAScript 规范。

3. 链式调用

then 方法中处理返回值类型,支持普通值和嵌套 Promise:

  • 如果返回普通值,直接 resolve。
  • 如果返回另一个 Promise,则等待其完成再继续。

4. 静态方法

  • resolve 和 reject :快速创建已完成或已拒绝的 Promise。
  • all :并发处理多个 Promise,返回所有完成结果。
  • race :返回第一个完成的 Promise,无论成功还是失败。

完整测试用例

测试基础功能

javascript 复制代码
const p1 = OptimizedPromise.resolve(1);
p1.then((value) => console.log("Resolved:", value)); // 输出 Resolved: 1

const p2 = new OptimizedPromise((resolve, reject) => {
    setTimeout(() => resolve(2), 100);
});
p2.then((value) => console.log("Async Resolved:", value)); // 输出 Async Resolved: 2

测试链式调用

javascript 复制代码
p1.then((value) => value * 2)
  .then((value) => new OptimizedPromise((resolve) => resolve(value + 3)))
  .then((value) => console.log("Chained Value:", value)); // 输出 Chained Value: 5

测试 all 和 race

javascript 复制代码
const p3 = OptimizedPromise.reject("Error!");
OptimizedPromise.all([p1, p2]).then((values) => console.log("All Resolved:", values)); // 输出 All Resolved: [1, 2]
OptimizedPromise.race([p2, p3]).catch((reason) => console.log("Race Rejected:", reason)); // 输出 Race Rejected: Error!

测试 finally

javascript 复制代码
p2.finally(() => console.log("Cleaning up...")).then((value) => console.log("Value:", value));
// 输出:
// Cleaning up...
// Value: 2

总结

这份优化后的 Promise 实现从状态管理、异步调度、链式调用到静态方法都做到了高效与规范。通过支持 finally 和链式嵌套,解决了真实开发中可能遇到的复杂场景。

核心亮点:

  1. 微任务调度确保异步回调顺序。
  2. 支持所有常见静态方法(resolve、reject、all、race)。
  3. 完整覆盖异常处理和链式调用。

通过深入理解其实现细节,你不仅可以更好地掌握 Promise 的原理,还能在实际开发中写出更高效、健壮的异步代码。

相关推荐
陈随易10 分钟前
Vue 编译器换掉 source map,快了三四成
前端·后端·程序员
ZhengEnCi1 小时前
12 种高级 UI 交互效果及对应 AI 提示词:从按压回弹到底部抽屉的物理感教学
前端·css·交互设计
IT_陈寒4 小时前
Redis大key删除为什么会导致服务卡顿?我的线上事故复盘
前端·人工智能·后端
系统集成-鹰翔4 小时前
ERP图片改名后仍命中识别缓存:缓存键、十分钟到期与调用计数
javascript·经验分享·后端
用户2705077725245 小时前
MCP 最小 Server 实测:3 个 JSON-RPC 报错与修法
前端
用户499335025075 小时前
一人VIP账号多人共用:Web 代理里的会话共享怎么实现
前端
用户老李5 小时前
无线电自学教程 05:第一台收音机——从矿石机到 SDR
前端
IPIPD5 小时前
Node.js请求遇到503怎么重试?先解析Retry-After,再做有上限退避
前端
葡萄城技术团队5 小时前
dsh-spreadjs-editor 的集成链路:把 SpreadJS Designer 嵌进 Agent 工作台
前端
FlyWIHTSKY6 小时前
vue2和vue3有什么区别,用法上有哪些区别
前端·javascript·vue.js