logo

手写Promise:从原理到实现的深度解析

作者:有好多问题2025.09.19 12:47浏览量:0

简介:本文深入解析Promise的核心机制,通过手写实现揭示其异步处理、状态管理和链式调用的底层逻辑,帮助开发者掌握Promise的设计精髓与实用技巧。

Promise(1)-手写Promise:从原理到实现的深度解析

一、Promise的核心价值与实现意义

Promise作为JavaScript异步编程的基石,解决了回调地狱(Callback Hell)问题,通过统一的接口规范和链式调用机制,使异步代码更易读、可维护。手写Promise不仅是理解其工作原理的最佳途径,更是提升代码设计能力的关键训练。本文将从零开始实现一个符合Promise/A+规范的简化版Promise,重点解析状态管理、异步任务调度和链式调用的核心逻辑。

1.1 Promise的三大核心特性

  • 状态机机制:Promise必须处于pendingfulfilledrejected三种状态之一,且状态一旦改变不可逆。
  • 异步通知:通过then方法注册的回调函数必须在当前调用栈清空后执行(微任务队列)。
  • 链式调用:每次调用then方法需返回一个新的Promise,实现异步任务的线性组合。

1.2 手写实现的必要性

原生Promise虽功能完善,但隐藏了实现细节。手写过程能暴露以下关键问题:

  • 如何保证状态变更的原子性?
  • 异步回调的执行时机如何控制?
  • 链式调用中值穿透(Value Chaining)的机制是什么?

二、Promise基础结构实现

2.1 构造函数与状态管理

  1. class MyPromise {
  2. constructor(executor) {
  3. this.state = 'pending'; // 初始状态
  4. this.value = undefined; // 成功值
  5. this.reason = undefined; // 失败原因
  6. this.onFulfilledCallbacks = []; // 成功回调队列
  7. this.onRejectedCallbacks = []; // 失败回调队列
  8. const resolve = (value) => {
  9. if (this.state === 'pending') {
  10. this.state = 'fulfilled';
  11. this.value = value;
  12. this.onFulfilledCallbacks.forEach(fn => fn());
  13. }
  14. };
  15. const reject = (reason) => {
  16. if (this.state === 'pending') {
  17. this.state = 'rejected';
  18. this.reason = reason;
  19. this.onRejectedCallbacks.forEach(fn => fn());
  20. }
  21. };
  22. try {
  23. executor(resolve, reject);
  24. } catch (err) {
  25. reject(err);
  26. }
  27. }
  28. }

关键点解析

  • 状态变更需通过resolve/reject方法显式触发,且仅在pending状态下有效。
  • 回调队列(onFulfilledCallbacks/onRejectedCallbacks)用于存储异步注册的回调函数。
  • 执行器(executor)中的同步错误需通过try-catch捕获并转为rejected状态。

2.2 then方法实现与链式调用

  1. then(onFulfilled, onRejected) {
  2. // 参数默认值处理(Promise/A+规范要求)
  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(() => { // 模拟微任务队列(实际需用MutationObserver或queueMicrotask)
  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. 每次调用then返回一个新的Promise(promise2),实现链式传递。
  2. 根据当前状态(fulfilled/rejected/pending)决定回调执行时机。
  3. 通过setTimeout模拟微任务队列(实际开发中需替换为queueMicrotask)。

2.3 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 ((typeof x === 'object' && x !== null) || 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. }

值穿透规则

  • x为普通值,直接resolve(x)
  • x为Promise或具有then方法的对象,递归解析其状态。
  • 严格防止循环引用和重复调用。

三、Promise/A+规范兼容性扩展

3.1 静态方法实现

  1. // Promise.resolve
  2. static resolve(value) {
  3. if (value instanceof MyPromise) {
  4. return value;
  5. }
  6. return new MyPromise(resolve => resolve(value));
  7. }
  8. // Promise.reject
  9. static reject(reason) {
  10. return new MyPromise((_, reject) => reject(reason));
  11. }
  12. // Promise.all
  13. static all(promises) {
  14. return new MyPromise((resolve, reject) => {
  15. const results = [];
  16. let count = 0;
  17. if (promises.length === 0) resolve(results);
  18. promises.forEach((promise, index) => {
  19. MyPromise.resolve(promise).then(
  20. value => {
  21. results[index] = value;
  22. count++;
  23. if (count === promises.length) resolve(results);
  24. },
  25. reason => reject(reason)
  26. );
  27. });
  28. });
  29. }

3.2 微任务队列优化

实际开发中需替换setTimeout为真正的微任务API:

  1. // 使用queueMicrotask(现代浏览器支持)
  2. const microtaskQueue = [];
  3. queueMicrotask(() => {
  4. while (microtaskQueue.length) {
  5. const callback = microtaskQueue.shift();
  6. callback();
  7. }
  8. });
  9. // 修改then方法中的异步执行部分
  10. setTimeout(() => { /* ... */ }, 0);
  11. // 替换为
  12. microtaskQueue.push(() => { /* ... */ });

四、手写Promise的实践价值

  1. 调试能力提升:通过自定义日志输出,精准定位异步流程中的问题。
  2. 性能优化:避免原生Promise的冗余封装,针对特定场景优化。
  3. 教学意义:帮助团队成员深入理解异步编程范式。

示例:添加调试日志的Promise

  1. class DebugPromise extends MyPromise {
  2. constructor(executor) {
  3. super(executor);
  4. this.log = [];
  5. }
  6. then(onFulfilled, onRejected) {
  7. const promise2 = super.then(
  8. (value) => {
  9. this.log.push(`Fulfilled with: ${value}`);
  10. return onFulfilled(value);
  11. },
  12. (reason) => {
  13. this.log.push(`Rejected with: ${reason}`);
  14. return onRejected(reason);
  15. }
  16. );
  17. return promise2;
  18. }
  19. }

五、总结与进阶方向

手写Promise完整实现了状态管理、异步调度和链式调用的核心逻辑,但仍有优化空间:

  1. 兼容性:处理更多边界情况(如then方法中抛出异常)。
  2. 性能:使用更高效的微任务调度方案。
  3. 扩展性:支持取消Promise、超时控制等高级功能。

通过本文的学习,开发者不仅能掌握Promise的底层原理,更能获得设计复杂异步系统的能力。建议结合Promise/A+规范测试用例(如promises-aplus-tests)验证实现正确性,为后续学习Async/Await和RxJS等高级异步方案打下坚实基础。

相关文章推荐

发表评论