logo

手写Promise全攻略:从零到一征服面试官!

作者:热心市民鹿先生2025.09.19 12:47浏览量:0

简介:掌握手写Promise核心原理,破解面试高频考点,提升代码实践能力与异步编程思维。

一、为什么面试官总爱问手写Promise?

在前端开发领域,Promise已成为处理异步操作的标准方案。面试官之所以频繁考察手写实现,主要有三大动机:

  1. 基础扎实度检验:手写Promise能暴露开发者对闭包、原型链、事件循环等核心概念的理解深度。例如,实现.then()的链式调用需要深入掌握回调队列管理。
  2. 问题解决能力评估:在无现成库的情况下,能否从零构建稳定异步方案,直接反映开发者的工程思维。某大厂面试题曾要求候选人现场修复一个存在内存泄漏的Promise实现。
  3. 源码级理解需求:主流框架(如Vue3的响应式系统)底层大量使用Promise变体,理解其原理有助于快速定位复杂问题。

典型失败案例:某候选人能熟练背诵Promise/A+规范,但在实现allSettled时错误地使用了Promise.race的逻辑,导致功能异常。这警示我们:机械记忆规范条文远不如理解实现本质。

二、手写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. 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. }

关键点

  • 状态机设计:必须严格限制状态单向变更(pending→fulfilled/rejected)
  • 异步回调队列:解决then方法调用时Promise尚未决议的问题
  • 错误捕获:executor执行异常需自动触发reject

2. 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. }

实现要点

  • 返回值穿透:当onFulfilled/onRejected未返回Promise时,需自动包装
  • 异步调度:使用setTimeout模拟微任务队列(实际实现需区分宏任务/微任务)
  • 错误边界:每个处理函数都需独立try-catch

3. resolvePromise解析函数

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

核心逻辑

  • 类型判断:处理x为Promise/thenable对象/普通值的情况
  • 状态锁定:防止多次调用resolve/reject
  • 递归解析:当返回值是Promise时需持续解析

4. 静态方法实现(all/race/allSettled)

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

实现技巧

  • 空数组处理:all([])应立即resolve
  • 索引保持:all方法需按输入顺序存储结果
  • 类型安全:使用MyPromise.resolve统一处理输入

三、面试高频变种题解析

1. 实现带取消功能的Promise

  1. class CancelablePromise {
  2. constructor(executor) {
  3. this.promise = new Promise((resolve, reject) => {
  4. this.resolve = resolve;
  5. this.reject = reject;
  6. executor(resolve, reject);
  7. });
  8. this.cancel = () => {
  9. this.reject(new Error('Promise canceled'));
  10. };
  11. }
  12. }
  13. // 使用示例
  14. const cp = new CancelablePromise((resolve) => {
  15. setTimeout(() => resolve('done'), 1000);
  16. });
  17. cp.cancel(); // 立即触发reject

应用场景:请求超时控制、用户操作中断处理

2. 实现Promise.retry

  1. Promise.retry = (fn, retries = 3, delay = 1000) => {
  2. return new Promise((resolve, reject) => {
  3. const attempt = () => {
  4. fn()
  5. .then(resolve)
  6. .catch((err) => {
  7. if (retries-- <= 0) {
  8. reject(err);
  9. } else {
  10. setTimeout(attempt, delay);
  11. }
  12. });
  13. };
  14. attempt();
  15. });
  16. };

实现要点

  • 递归调用控制
  • 指数退避策略(可扩展)
  • 剩余次数管理

四、实战建议与避坑指南

  1. 调试技巧

    • 使用process.nextTick(Node)或queueMicrotask模拟微任务
    • 在关键节点添加console.log跟踪状态变化
    • 绘制状态转换图辅助理解
  2. 常见错误

    • 忘记处理then方法的参数默认值
    • 在resolvePromise中未处理thenable对象的异常
    • 静态方法实现时未统一处理非Promise输入
  3. 性能优化

    • 避免在then回调中创建不必要的Promise
    • 对批量操作使用Promise.all优化
    • 合理设置异步任务的并发数

五、学习资源推荐

  1. 规范文档

  2. 实践工具

  3. 进阶阅读

    • 《You Don’t Know JS: Async & Performance》
    • Vue/React源码中Promise的应用案例

通过系统掌握上述内容,开发者不仅能轻松应对面试中的手写Promise问题,更能获得处理复杂异步场景的底层思维。建议每天实现一个Promise变种方法,持续2周即可达到熟练水平。记住:真正的掌握不在于背下代码,而在于理解每个设计决策背后的原因。

相关文章推荐

发表评论