logo

从零到一:手写Promise全流程解析与实现

作者:渣渣辉2025.09.19 12:47浏览量:0

简介:本文详细解析如何从0开始手写一个Promise,涵盖状态管理、链式调用、异常处理等核心机制,适合想要深入理解Promise原理的开发者。

从零到一:手写Promise全流程解析与实现

1. 引言:为什么需要手写Promise?

Promise是JavaScript异步编程的核心,但原生Promise的实现隐藏了诸多细节。手写Promise不仅能帮助开发者深入理解其工作原理,还能在以下场景中发挥作用:

  • 面试中考察对异步编程的理解
  • 定制化Promise行为(如添加日志、超时控制)
  • 在无原生Promise的环境中(如旧浏览器)实现兼容
  • 教学目的,帮助他人理解Promise机制

2. Promise核心概念回顾

在开始实现前,我们需要明确Promise的几个核心特性:

  • 三种状态:pending(待定)、fulfilled(已兑现)、rejected(已拒绝)
  • 状态不可逆:一旦从pending变为fulfilled或rejected,就不能再改变
  • then方法:支持链式调用,接收onFulfilled和onRejected两个回调
  • 异步执行:resolve/reject的回调会被放入微任务队列

3. 基础结构搭建

3.1 构造函数实现

  1. class MyPromise {
  2. constructor(executor) {
  3. // 初始化状态和结果
  4. this.state = 'pending'; // pending, fulfilled, rejected
  5. this.result = undefined;
  6. // 存储回调队列(用于then的链式调用)
  7. this.onFulfilledCallbacks = [];
  8. this.onRejectedCallbacks = [];
  9. // resolve函数
  10. const resolve = (value) => {
  11. if (this.state === 'pending') {
  12. this.state = 'fulfilled';
  13. this.result = value;
  14. // 执行所有成功的回调
  15. this.onFulfilledCallbacks.forEach(fn => fn());
  16. }
  17. };
  18. // reject函数
  19. const reject = (reason) => {
  20. if (this.state === 'pending') {
  21. this.state = 'rejected';
  22. this.result = reason;
  23. // 执行所有失败的回调
  24. this.onRejectedCallbacks.forEach(fn => fn());
  25. }
  26. };
  27. // 执行executor,捕获可能的异常
  28. try {
  29. executor(resolve, reject);
  30. } catch (err) {
  31. reject(err);
  32. }
  33. }
  34. }

3.2 关键点解析

  1. 状态管理:使用state变量严格跟踪Promise状态
  2. 回调队列:存储then方法注册的回调,在状态变更时执行
  3. 异常处理:在executor执行时捕获同步错误

4. then方法实现

then方法是Promise的核心,它需要处理:

  • 异步执行回调
  • 链式调用(返回新的Promise)
  • 值穿透(如果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. // 根据当前状态决定如何处理
  7. if (this.state === 'fulfilled') {
  8. // 使用setTimeout模拟微任务(实际浏览器中使用queueMicrotask)
  9. setTimeout(() => {
  10. try {
  11. const x = onFulfilled(this.result);
  12. resolvePromise(promise2, x, resolve, reject);
  13. } catch (err) {
  14. reject(err);
  15. }
  16. }, 0);
  17. } else if (this.state === 'rejected') {
  18. setTimeout(() => {
  19. try {
  20. const x = onRejected(this.result);
  21. resolvePromise(promise2, x, resolve, reject);
  22. } catch (err) {
  23. reject(err);
  24. }
  25. }, 0);
  26. } else if (this.state === 'pending') {
  27. // 如果是pending状态,将回调存入队列
  28. this.onFulfilledCallbacks.push(() => {
  29. setTimeout(() => {
  30. try {
  31. const x = onFulfilled(this.result);
  32. resolvePromise(promise2, x, resolve, reject);
  33. } catch (err) {
  34. reject(err);
  35. }
  36. }, 0);
  37. });
  38. this.onRejectedCallbacks.push(() => {
  39. setTimeout(() => {
  40. try {
  41. const x = onRejected(this.result);
  42. resolvePromise(promise2, x, resolve, reject);
  43. } catch (err) {
  44. reject(err);
  45. }
  46. }, 0);
  47. });
  48. }
  49. });
  50. return promise2;
  51. }

5. resolvePromise:处理链式调用的核心

这是Promise实现中最复杂的部分,需要处理:

  • 返回值是Promise的情况
  • 循环引用检测
  • 抛出异常的处理
  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. // x是一个Promise(或类Promise对象)
  13. then.call(
  14. x,
  15. y => {
  16. if (called) return;
  17. called = true;
  18. resolvePromise(promise2, y, resolve, reject);
  19. },
  20. r => {
  21. if (called) return;
  22. called = true;
  23. reject(r);
  24. }
  25. );
  26. } else {
  27. // x是一个普通对象
  28. resolve(x);
  29. }
  30. } catch (err) {
  31. if (called) return;
  32. called = true;
  33. reject(err);
  34. }
  35. } else {
  36. // x是普通值
  37. resolve(x);
  38. }
  39. }

6. 静态方法实现

6.1 Promise.resolve

  1. static resolve(value) {
  2. if (value instanceof MyPromise) {
  3. return value;
  4. }
  5. return new MyPromise(resolve => resolve(value));
  6. }

6.2 Promise.reject

  1. static reject(reason) {
  2. return new MyPromise((_, reject) => reject(reason));
  3. }

6.3 Promise.all

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

6.4 Promise.race

  1. static race(promises) {
  2. return new MyPromise((resolve, reject) => {
  3. promises.forEach(promise => {
  4. MyPromise.resolve(promise).then(resolve, reject);
  5. });
  6. });
  7. }

7. 微任务队列的模拟实现

浏览器中使用queueMicrotask,Node.js中使用process.nextTick。这里我们简单模拟:

  1. // 在MyPromise类中添加
  2. static _microtaskQueue = [];
  3. static _isFlushing = false;
  4. static _flush() {
  5. if (this._isFlushing) return;
  6. this._isFlushing = true;
  7. while (this._microtaskQueue.length) {
  8. const callback = this._microtaskQueue.shift();
  9. callback();
  10. }
  11. this._isFlushing = false;
  12. }
  13. static queueMicrotask(callback) {
  14. this._microtaskQueue.push(callback);
  15. if (!this._isFlushing) {
  16. setTimeout(this._flush.bind(this), 0);
  17. }
  18. }

然后在then方法中使用:

  1. // 替换之前的setTimeout
  2. MyPromise.queueMicrotask(() => {
  3. try {
  4. const x = onFulfilled(this.result);
  5. resolvePromise(promise2, x, resolve, reject);
  6. } catch (err) {
  7. reject(err);
  8. }
  9. });

8. 完整实现代码

将以上所有部分组合起来,我们得到一个完整的Promise实现:

  1. class MyPromise {
  2. static _microtaskQueue = [];
  3. static _isFlushing = false;
  4. static _flush() {
  5. if (this._isFlushing) return;
  6. this._isFlushing = true;
  7. while (this._microtaskQueue.length) {
  8. const callback = this._microtaskQueue.shift();
  9. callback();
  10. }
  11. this._isFlushing = false;
  12. }
  13. static queueMicrotask(callback) {
  14. this._microtaskQueue.push(callback);
  15. if (!this._isFlushing) {
  16. setTimeout(this._flush.bind(this), 0);
  17. }
  18. }
  19. static resolve(value) {
  20. if (value instanceof MyPromise) {
  21. return value;
  22. }
  23. return new MyPromise(resolve => resolve(value));
  24. }
  25. static reject(reason) {
  26. return new MyPromise((_, reject) => reject(reason));
  27. }
  28. static all(promises) {
  29. return new MyPromise((resolve, reject) => {
  30. const results = [];
  31. let count = 0;
  32. if (promises.length === 0) {
  33. resolve(results);
  34. return;
  35. }
  36. promises.forEach((promise, index) => {
  37. MyPromise.resolve(promise).then(
  38. value => {
  39. results[index] = value;
  40. count++;
  41. if (count === promises.length) {
  42. resolve(results);
  43. }
  44. },
  45. reason => {
  46. reject(reason);
  47. }
  48. );
  49. });
  50. });
  51. }
  52. static race(promises) {
  53. return new MyPromise((resolve, reject) => {
  54. promises.forEach(promise => {
  55. MyPromise.resolve(promise).then(resolve, reject);
  56. });
  57. });
  58. }
  59. constructor(executor) {
  60. this.state = 'pending';
  61. this.result = undefined;
  62. this.onFulfilledCallbacks = [];
  63. this.onRejectedCallbacks = [];
  64. const resolve = (value) => {
  65. if (this.state === 'pending') {
  66. this.state = 'fulfilled';
  67. this.result = value;
  68. this.onFulfilledCallbacks.forEach(fn => fn());
  69. }
  70. };
  71. const reject = (reason) => {
  72. if (this.state === 'pending') {
  73. this.state = 'rejected';
  74. this.result = reason;
  75. this.onRejectedCallbacks.forEach(fn => fn());
  76. }
  77. };
  78. try {
  79. executor(resolve, reject);
  80. } catch (err) {
  81. reject(err);
  82. }
  83. }
  84. then(onFulfilled, onRejected) {
  85. onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
  86. onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
  87. const promise2 = new MyPromise((resolve, reject) => {
  88. const handleFulfilled = () => {
  89. MyPromise.queueMicrotask(() => {
  90. try {
  91. const x = onFulfilled(this.result);
  92. resolvePromise(promise2, x, resolve, reject);
  93. } catch (err) {
  94. reject(err);
  95. }
  96. });
  97. };
  98. const handleRejected = () => {
  99. MyPromise.queueMicrotask(() => {
  100. try {
  101. const x = onRejected(this.result);
  102. resolvePromise(promise2, x, resolve, reject);
  103. } catch (err) {
  104. reject(err);
  105. }
  106. });
  107. };
  108. if (this.state === 'fulfilled') {
  109. handleFulfilled();
  110. } else if (this.state === 'rejected') {
  111. handleRejected();
  112. } else if (this.state === 'pending') {
  113. this.onFulfilledCallbacks.push(handleFulfilled);
  114. this.onRejectedCallbacks.push(handleRejected);
  115. }
  116. });
  117. return promise2;
  118. }
  119. catch(onRejected) {
  120. return this.then(null, onRejected);
  121. }
  122. finally(callback) {
  123. return this.then(
  124. value => MyPromise.resolve(callback()).then(() => value),
  125. reason => MyPromise.resolve(callback()).then(() => { throw reason; })
  126. );
  127. }
  128. }
  129. function resolvePromise(promise2, x, resolve, reject) {
  130. if (promise2 === x) {
  131. return reject(new TypeError('Chaining cycle detected for promise'));
  132. }
  133. let called = false;
  134. if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
  135. try {
  136. const then = x.then;
  137. if (typeof then === 'function') {
  138. then.call(
  139. x,
  140. y => {
  141. if (called) return;
  142. called = true;
  143. resolvePromise(promise2, y, resolve, reject);
  144. },
  145. r => {
  146. if (called) return;
  147. called = true;
  148. reject(r);
  149. }
  150. );
  151. } else {
  152. resolve(x);
  153. }
  154. } catch (err) {
  155. if (called) return;
  156. called = true;
  157. reject(err);
  158. }
  159. } else {
  160. resolve(x);
  161. }
  162. }

9. 测试用例

  1. // 测试1:基本用法
  2. const promise = new MyPromise((resolve, reject) => {
  3. setTimeout(() => resolve('成功'), 1000);
  4. });
  5. promise.then(
  6. value => console.log(value), // 应该输出"成功"
  7. reason => console.error(reason)
  8. );
  9. // 测试2:链式调用
  10. MyPromise.resolve(1)
  11. .then(x => x + 1)
  12. .then(x => x * 2)
  13. .then(x => console.log(x)); // 应该输出4
  14. // 测试3:Promise.all
  15. const p1 = MyPromise.resolve(1);
  16. const p2 = MyPromise.resolve(2);
  17. const p3 = MyPromise.resolve(3);
  18. MyPromise.all([p1, p2, p3]).then(values => {
  19. console.log(values); // 应该输出[1, 2, 3]
  20. });
  21. // 测试4:异常处理
  22. new MyPromise((_, reject) => reject('错误'))
  23. .catch(err => console.error(err)); // 应该输出"错误"

10. 总结与扩展

通过手写Promise,我们深入理解了以下核心概念:

  1. 状态管理:Promise的三种状态及其转换规则
  2. 异步执行:微任务队列的实现机制
  3. 链式调用:then方法的返回值处理
  4. 异常处理:同步和异步错误的捕获

10.1 进一步优化方向

  1. 性能优化:减少微任务队列的开销
  2. 兼容性增强:支持更老的浏览器环境
  3. 功能扩展:添加finally、allSettled等方法
  4. 调试支持:添加堆栈跟踪功能

10.2 实际应用建议

  1. 在学习阶段,可以对比原生Promise的实现差异
  2. 在生产环境中,除非有特殊需求,否则建议使用原生Promise
  3. 可以通过扩展MyPromise类来添加自定义功能

手写Promise不仅是一次技术挑战,更是对JavaScript异步编程模式的深度理解。通过这个过程,开发者能够更好地掌握Promise的工作原理,从而写出更健壮、更高效的异步代码。

相关文章推荐

发表评论