logo

手写Promise全解析:从零实现到通过Promises/A+测试

作者:搬砖的石头2025.09.19 12:47浏览量:0

简介:本文深入解析如何手写实现符合Promises/A+规范的Promise类,涵盖状态管理、链式调用、异步处理等核心机制,并提供完整测试方案助你验证实现正确性。

手写Promise全解析:从零实现到通过Promises/A+测试

一、为何要手写Promise?

Promise作为现代JavaScript异步编程的核心,其内部机制对开发者理解异步流程至关重要。手写Promise不仅能加深对Promise工作原理的理解,更是掌握JavaScript事件循环、微任务队列等底层概念的最佳途径。此外,实现符合Promises/A+规范的Promise类,能确保你的代码与所有遵循该标准的库(如axios、bluebird等)无缝兼容。

核心价值:

  1. 深入理解异步机制:通过实现状态转换、微任务调度等逻辑,掌握Promise如何解决回调地狱问题
  2. 提升代码质量:理解规范要求能避免常见实现错误,如thenable对象处理不当、状态错误变更等
  3. 面试利器:手写Promise是前端高级工程师面试的经典考题,能全面考察JavaScript基础能力

二、Promise核心机制实现

1. 基础类结构

  1. class MyPromise {
  2. constructor(executor) {
  3. this.state = 'pending'; // pending, fulfilled, rejected
  4. this.value = undefined;
  5. this.reason = undefined;
  6. this.onFulfilledCallbacks = [];
  7. this.onRejectedCallbacks = [];
  8. try {
  9. executor(this.resolve.bind(this), this.reject.bind(this));
  10. } catch (err) {
  11. this.reject(err);
  12. }
  13. }
  14. // 状态变更方法将后续实现
  15. resolve(value) { /* ... */ }
  16. reject(reason) { /* ... */ }
  17. }

关键点解析

  • 初始状态必须为pending,这是规范要求的初始状态
  • 需要维护两个回调队列,分别处理fulfilledrejected状态的回调
  • 执行器函数(executor)必须同步执行,错误需要被捕获并转为rejected状态

2. 状态管理实现

  1. resolve(value) {
  2. if (this.state === 'pending') {
  3. this.state = 'fulfilled';
  4. this.value = value;
  5. this.onFulfilledCallbacks.forEach(fn => fn());
  6. }
  7. }
  8. reject(reason) {
  9. if (this.state === 'pending') {
  10. this.state = 'rejected';
  11. this.reason = reason;
  12. this.onRejectedCallbacks.forEach(fn => fn());
  13. }
  14. }

规范要求

  • 状态一旦变更不可逆转(fulfilled/rejected → pending不允许)
  • 同一Promise只能变更一次状态
  • 变更后需要执行所有对应的回调函数

3. then方法实现

  1. then(onFulfilled, onRejected) {
  2. // 参数可选处理
  3. onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  4. onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
  5. const promise2 = new MyPromise((resolve, reject) => {
  6. if (this.state === 'fulfilled') {
  7. setTimeout(() => {
  8. try {
  9. const x = onFulfilled(this.value);
  10. resolvePromise(promise2, x, resolve, reject);
  11. } catch (e) {
  12. reject(e);
  13. }
  14. }, 0);
  15. } else if (this.state === 'rejected') {
  16. setTimeout(() => {
  17. try {
  18. const x = onRejected(this.reason);
  19. resolvePromise(promise2, x, resolve, reject);
  20. } catch (e) {
  21. reject(e);
  22. }
  23. }, 0);
  24. } else if (this.state === 'pending') {
  25. this.onFulfilledCallbacks.push(() => {
  26. setTimeout(() => {
  27. try {
  28. const x = onFulfilled(this.value);
  29. resolvePromise(promise2, x, resolve, reject);
  30. } catch (e) {
  31. reject(e);
  32. }
  33. }, 0);
  34. });
  35. this.onRejectedCallbacks.push(() => {
  36. setTimeout(() => {
  37. try {
  38. const x = onRejected(this.reason);
  39. resolvePromise(promise2, x, resolve, reject);
  40. } catch (e) {
  41. reject(e);
  42. }
  43. }, 0);
  44. });
  45. }
  46. });
  47. return promise2;
  48. }

关键实现细节

  1. 异步调度:使用setTimeout将回调推入微任务队列(实际实现可用queueMicrotask
  2. 链式调用:每次then返回新Promise,实现链式调用
  3. 错误处理:必须用try-catch包裹回调执行
  4. 参数默认值:当不传回调时,需要提供默认的value传递和reason抛出

4. resolvePromise核心算法

  1. function resolvePromise(promise2, x, resolve, reject) {
  2. // 防止循环引用
  3. if (promise2 === x) {
  4. return reject(new TypeError('Chaining cycle detected for promise'));
  5. }
  6. // 防止多次调用
  7. let called = false;
  8. if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
  9. try {
  10. const then = x.then;
  11. if (typeof then === 'function') {
  12. then.call(
  13. x,
  14. y => {
  15. if (called) return;
  16. called = true;
  17. resolvePromise(promise2, y, resolve, reject);
  18. },
  19. r => {
  20. if (called) return;
  21. called = true;
  22. reject(r);
  23. }
  24. );
  25. } else {
  26. resolve(x);
  27. }
  28. } catch (e) {
  29. if (called) return;
  30. called = true;
  31. reject(e);
  32. }
  33. } else {
  34. resolve(x);
  35. }
  36. }

规范要点

  1. x为thenable对象:需要递归解析其then方法
  2. 只允许调用一次:通过called标志防止多次resolve/reject
  3. 错误处理:then方法执行出错应reject
  4. 循环引用检测:防止promise2 === x的情况

三、Promises/A+测试实现指南

1. 测试环境搭建

  1. 安装测试工具:

    1. npm install promises-aplus-tests -D
  2. 创建适配接口:
    ```javascript
    const MyPromise = require(‘./my-promise’);

MyPromise.deferred = function() {
const result = {};
result.promise = new MyPromise((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
};

module.exports = MyPromise;

  1. ### 2. 运行测试
  2. ```bash
  3. promises-aplus-tests ./my-promise-adapter.js

3. 常见测试失败点及修复

  1. 状态不可变测试失败

    • 问题:手动修改了已变更状态的promise
    • 修复:在resolve/reject方法中添加状态检查
  2. thenable解析失败

    • 问题:未正确处理then方法为getter属性的情况
    • 修复:使用Object.defineProperty测试getter场景
  3. 异步调度失败

    • 问题:使用同步方式执行回调
    • 修复:确保使用微任务调度(queueMicrotask/setTimeout)

四、进阶实现建议

  1. 性能优化

    • 使用更高效的微任务调度方案(如MutationObserver)
    • 避免不必要的数组遍历
  2. 功能扩展

    • 实现Promise.all/Promise.race等静态方法
    • 添加finally方法支持
    • 实现取消功能(需扩展规范)
  3. 错误监控

    • 添加全局未捕获reject处理
    • 实现Promise链的错误聚合

五、完整实现示例

  1. class MyPromise {
  2. // ... 前文实现代码 ...
  3. // 静态方法实现
  4. static resolve(value) {
  5. if (value instanceof MyPromise) {
  6. return value;
  7. }
  8. return new MyPromise(resolve => resolve(value));
  9. }
  10. static reject(reason) {
  11. return new MyPromise((_, reject) => reject(reason));
  12. }
  13. static all(promises) {
  14. return new MyPromise((resolve, reject) => {
  15. const results = [];
  16. let completed = 0;
  17. if (promises.length === 0) {
  18. return resolve(results);
  19. }
  20. promises.forEach((promise, index) => {
  21. MyPromise.resolve(promise).then(
  22. value => {
  23. results[index] = value;
  24. completed++;
  25. if (completed === promises.length) {
  26. resolve(results);
  27. }
  28. },
  29. reject
  30. );
  31. });
  32. });
  33. }
  34. // ... 其他方法实现 ...
  35. }
  36. // 适配Promises/A+测试
  37. MyPromise.deferred = function() {
  38. const result = {};
  39. result.promise = new MyPromise((resolve, reject) => {
  40. result.resolve = resolve;
  41. result.reject = reject;
  42. });
  43. return result;
  44. };
  45. module.exports = MyPromise;

六、总结与验证

实现符合Promises/A+规范的Promise类需要严格遵循以下核心原则:

  1. 三种状态:必须正确管理pending/fulfilled/rejected状态转换
  2. 异步执行:所有回调必须异步执行(微任务级别)
  3. 链式调用:每次then返回新Promise,实现方法链
  4. 值穿透:正确处理非Promise值的传递
  5. 错误处理:完善的错误捕获和传递机制

通过promises-aplus-tests的283个测试用例验证,可以确保实现的正确性。实际开发中,建议先实现核心功能,再逐步完善边缘情况和性能优化。

验证建议

  1. 编写单元测试覆盖所有状态转换场景
  2. 测试thenable对象的深度解析
  3. 验证错误传播路径
  4. 检查异步调度的顺序正确性

手写Promise不仅是技术挑战,更是深入理解JavaScript异步编程的绝佳途径。通过实现过程,你将获得对事件循环、执行上下文等底层概念的深刻理解,这对提升整体JavaScript开发水平具有重要价值。

相关文章推荐

发表评论