文章

手写Promise全家桶深度解析:从 A+ 规范到全部静态方法的终极实现

手写Promise全家桶深度解析:从 A+ 规范到全部静态方法的终极实现

一句话概括

手写 Promise 全家桶是检验 JavaScript 异步编程理解的终极试金石——从遵守 Promises/A+ 规范的完整 Promise 实现,到 Promise.all/Promise.race/Promise.allSettled/Promise.any 的精准逻辑,彻底吃透异步控制流的每一个细节。

背景与意义

为什么面试必考手写 Promise

在 JavaScript 面试中,”手写 Promise”的出现频率近五年持续上升,原因有三:

  1. 异步编程是 JS 的核心能力:从回调地狱到 Promise chain 到 async/await,Promise 是中间最关键的知识桥梁
  2. 考察底层理解:会使用 Promise.then() 不等于理解微任务队列、状态机转换、值穿透等机制
  3. 具备现实价值:很多 Promise 静态方法的执行顺序和错误处理是日常开发中极易踩坑的地方

Promise 生态

ECMAScript 2015 正式标准化了 Promise,之后各版本补充了静态方法:

版本方法行为
ES2015Promise.prototype.then/catch/finally基础链式调用
ES2015Promise.resolve / Promise.reject快速创建
ES2015Promise.all全部成功或任一失败(快速失败)
ES2015Promise.race竞速:第一个 settled 的
ES2020Promise.allSettled全部结束后返回,无论成功或失败
ES2021Promise.any任一成功即返回,全部失败则 AggregateError
ES2024Promise.withResolvers在外部控制 Promise 的 resolve/reject

基本概念

Promise 状态机

1
2
3
4
5
6
7
8
9
10
11
12
13
                  ┌──────────────────┐
                  │     Pending      │
                  │ (初始态,未决)    │
                  └───────┬──────────┘
                          │
                  ┌───────┴───────┐
                  │               │
                  ▼               ▼
          ┌────────────┐  ┌────────────┐
          │ Fulfilled  │  │ Rejected   │
          │ (已兑现)    │  │ (已拒绝)    │
          └────────────┘  └────────────┘
          (不可逆)       (不可逆)

Promise 的核心约束(来自 Promises/A+ 规范):

  1. 状态唯一性:Pending → Fulfilled 或 Pending → Rejected,不可逆转
  2. 值穿透:如果 onFulfilled 返回一个值,它将作为下一个 .then() 的输入
  3. Promise 展开:如果 onFulfilled 返回一个 Promise,下一个 .then() 会等待该 Promise 完成
  4. 异步执行:所有 .then() 回调必须异步执行(作为微任务)

最小示例

遵循 Promises/A+ 的最小 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// mini-promise.js — 最小但规范的 Promise 实现
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

class MiniPromise {
  constructor(executor) {
    this.state = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state !== PENDING) return;
      this.state = FULFILLED;
      this.value = value;
      this.onFulfilledCallbacks.forEach(fn => fn());
    };

    const reject = (reason) => {
      if (this.state !== PENDING) return;
      this.state = REJECTED;
      this.reason = reason;
      this.onRejectedCallbacks.forEach(fn => fn());
    };

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

  then(onFulfilled, onRejected) {
    const promise2 = new MiniPromise((resolve, reject) => {
      const handleFulfilled = () => {
        queueMicrotask(() => {
          try {
            if (typeof onFulfilled !== 'function') {
              resolve(this.value);
              return;
            }
            const x = onFulfilled(this.value);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      };

      const handleRejected = () => {
        queueMicrotask(() => {
          try {
            if (typeof onRejected !== 'function') {
              reject(this.reason);
              return;
            }
            const x = onRejected(this.reason);
            this.resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      };

      if (this.state === FULFILLED) {
        handleFulfilled();
      } else if (this.state === REJECTED) {
        handleRejected();
      } else {
        this.onFulfilledCallbacks.push(handleFulfilled);
        this.onRejectedCallbacks.push(handleRejected);
      }
    });

    return promise2;
  }

  resolvePromise(promise2, x, resolve, reject) {
    // 防止循环引用
    if (promise2 === x) {
      reject(new TypeError('Chaining cycle detected for promise'));
      return;
    }

    // 如果 x 是 Promise
    if (x instanceof MiniPromise) {
      x.then(
        value => resolve(value),
        reason => reject(reason)
      );
      return;
    }

    // 如果 x 是 thenable(有 then 方法的对象/函数)
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
      let then;
      try {
        then = x.then;
      } catch (err) {
        reject(err);
        return;
      }

      if (typeof then === 'function') {
        let called = false;
        try {
          then.call(
            x,
            (y) => {
              if (called) return;
              called = true;
              this.resolvePromise(promise2, y, resolve, reject);
            },
            (r) => {
              if (called) return;
              called = true;
              reject(r);
            }
          );
        } catch (err) {
          if (!called) reject(err);
        }
        return;
      }
    }

    // x 是普通值
    resolve(x);
  }

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

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

  static resolve(value) {
    if (value instanceof MiniPromise) return value;
    return new MiniPromise(resolve => resolve(value));
  }

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

// 使用
const p = new MiniPromise((resolve) => {
  setTimeout(() => resolve('Hello MiniPromise!'), 100);
});
p.then(val => console.log(val)); // Hello MiniPromise!

核心知识点拆解

1. Promise 的异步执行保证

Promises/A+ 规范要求所有 .then() 的回调必须是异步执行的,即使 Promise 已经 resolve。这是通过 queueMicrotask(或 process.nextTicksetTimeout 等)实现的:

1
2
3
4
5
6
7
8
9
// 演示异步保证
const p = new Promise(resolve => resolve('already resolved'));

console.log('1');
p.then(val => console.log('2:', val));
console.log('3');

// 输出: 1, 3, 2: already resolved
// 即使 Promise 已经 resolve,.then 的回调仍然异步执行

原因:如果在同一个微任务中同步执行 .then() 回调,那在 thenable 展开(recursive promise resolution)时可能导致栈溢出,而异步执行保证了每次 .then() 都在新栈帧中运行。

2. Promise.resolve 的 thenable 展开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Promise.resolve 的展开行为
const thenable = {
  then: function(resolve) {
    resolve(42);
  }
};

Promise.resolve(thenable).then(val => {
  console.log(val); // 42,不是 { then: fn }
});

// 对比:普通对象直接穿透
Promise.resolve({ key: 'value' }).then(val => {
  console.log(val); // { key: 'value' }
});

内部机制Promise.resolve(v) 如果检测到 vthen 方法,会创建一个 Promise 并通过 then.call(v, resolve, reject) 展平它。

3. Promise 链的值穿透

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Promise.resolve(1)
  .then()          // 没有传回调 → 值穿透,继续传递 1
  .then()          // 穿透
  .then(val => {
    console.log(val); // 1
    return Promise.resolve(2);
  })
  .then(val => {
    console.log(val); // 2
  });

// 错误穿透
Promise.reject('err')
  .catch()          // 错误穿透
  .catch()          // 穿透
  .catch(err => {
    console.log(err); // 'err'
    return 'recovered';
  })
  .then(val => {
    console.log(val); // 'recovered'
  });

实现中,当 onFulfilled 不是函数时,直接将 value 透传:typeof onFulfilled !== 'function' ? resolve(this.value)

4. Promise.all 的实现细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Promise.myAll = function (iterable) {
  return new Promise((resolve, reject) => {
    const entries = Array.from(iterable);
    if (entries.length === 0) {
      resolve([]);
      return;
    }

    const results = new Array(entries.length);
    let remaining = entries.length;

    entries.forEach((item, index) => {
      // 用 Promise.resolve 包裹,兼容非 Promise 值
      Promise.resolve(item).then(
        (value) => {
          results[index] = value;
          remaining--;
          if (remaining === 0) {
            resolve(results);
          }
        },
        (reason) => {
          // 快速失败:任一 rejected 立即拒绝
          reject(reason);
        }
      );
    });
  });
};

关键点

  • 输入可能不是 Promise,需要用 Promise.resolve 包裹
  • 结果数组保持输入顺序(通过 results[index] 索引赋值)
  • 快速失败:任一输入 rejected,整体立即 rejected
  • 空输入返回空数组的 resolved Promise

5. Promise.race

1
2
3
4
5
6
7
8
9
10
Promise.myRace = function (iterable) {
  return new Promise((resolve, reject) => {
    const entries = Array.from(iterable);
    if (entries.length === 0) return; // 返回 pending 状态的 Promise

    for (const item of entries) {
      Promise.resolve(item).then(resolve, reject);
    }
  });
};

关键点

  • “竞速”:第一个 settled(无论是 fulfilled 还是 rejected)的就决定整体结果
  • 空输入返回一个永远 pending 的 Promise(这是个陷阱!)
  • Array.from 确保支持任意可迭代对象(Set、Map、自定义 Iterator 等)

6. Promise.allSettled

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Promise.myAllSettled = function (iterable) {
  return new Promise((resolve) => {
    const entries = Array.from(iterable);
    if (entries.length === 0) {
      resolve([]);
      return;
    }

    const results = new Array(entries.length);
    let settled = 0;

    const onSettled = (index) => {
      settled++;
      if (settled === entries.length) {
        resolve(results);
      }
    };

    entries.forEach((item, index) => {
      Promise.resolve(item).then(
        (value) => {
          results[index] = { status: 'fulfilled', value };
          onSettled(index);
        },
        (reason) => {
          results[index] = { status: 'rejected', reason };
          onSettled(index);
        }
      );
    });
  });
};

关键点

  • 不会 rejected,总是 fulfilled(不管输入 Promise 的状态)
  • 结果元素格式化:{ status: 'fulfilled', value }{ status: 'rejected', reason }
  • 所有 settle 完成之前不会 resolve

7. Promise.any(ES2021)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Promise.myAny = function (iterable) {
  return new Promise((resolve, reject) => {
    const entries = Array.from(iterable);
    if (entries.length === 0) {
      reject(new AggregateError([], 'All promises were rejected'));
      return;
    }

    const errors = new Array(entries.length);
    let rejectedCount = 0;

    entries.forEach((item, index) => {
      Promise.resolve(item).then(
        (value) => {
          resolve(value);
        },
        (reason) => {
          errors[index] = reason;
          rejectedCount++;
          if (rejectedCount === entries.length) {
            reject(new AggregateError(errors, 'All promises were rejected'));
          }
        }
      );
    });
  });
};

关键点

  • 任一 fulfilled 就整体 resolve
  • 全部 rejected 则 reject AggregateError
  • 传入空数组立即 reject AggregateError
  • AggregateError 的 .errors 属性包含所有拒绝原因

8. 四种组合方法的对比

方法成功条件失败条件结果格式
all全部成功任一失败(快速失败)值数组
allSettled全部完成永不状态对象数组
race第一个成功第一个失败单个值/原因
any第一个成功全部失败单个值/AggregateError

实战案例:批量请求的智能调度器

使用手写的 Promise 全家桶实现一个可控制超时、优先级、降级的请求调度器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
interface RequestTask<T = any> {
  id: string;
  executor: () => Promise<T>;
  timeout: number;
  priority: number;
  fallback: () => T;
}

class BatchRequestScheduler {
  private tasks: RequestTask[] = [];

  add<T>(task: RequestTask<T>) {
    this.tasks.push(task);
    return this;
  }

  async executeAll<T = any>(mode: 'all' | 'allSettled' | 'race' | 'any' = 'all'): Promise<T[]> {
    // 按优先级排序
    this.tasks.sort((a, b) => b.priority - a.priority);

    // 为每个请求包裹超时逻辑
    const wrappedTasks = this.tasks.map(task => {
      return this.createTimeoutTask(task);
    });

    switch (mode) {
      case 'all':
        return Promise.myAll(wrappedTasks) as Promise<T[]>;
      case 'allSettled':
        return Promise.myAllSettled(wrappedTasks) as any;
      case 'race':
        return Promise.myRace(wrappedTasks) as Promise<T[]>;
      case 'any':
        return Promise.myAny(wrappedTasks) as Promise<T[]>;
    }
  }

  private createTimeoutTask<T>(task: RequestTask<T>): Promise<T> {
    return new Promise((resolve) => {
      // 用 race 实现超时控制
      Promise.myRace([
        task.executor(),
        new Promise((_, reject) =>
          setTimeout(() => reject(new Error(`[超时] ${task.id} 超过 ${task.timeout}ms 未响应`)), task.timeout)
        ),
      ]).then(resolve)
        .catch(() => {
          // 超时或失败时使用降级结果
          console.warn(`[降级] ${task.id} 使用降级值`);
          resolve(task.fallback());
        });
    });
  }

  // 分批执行(避免超并发)
  async executeInBatches<T>(batchSize = 3, mode: 'all' | 'allSettled' = 'all'): Promise<T[]> {
    const results: T[] = [];

    for (let i = 0; i < this.tasks.length; i += batchSize) {
      const batch = this.tasks.slice(i, i + batchSize);

      // 对每个 batch 内部用 all
      const batchResults = await Promise.myAll(
        batch.map(t => this.createTimeoutTask(t))
      ) as T[];

      results.push(...batchResults);
      console.log(`[批次] ${i / batchSize + 1} 完成,累计 ${results.length}/${this.tasks.length}`);
    }

    return results;
  }
}

// 使用
const scheduler = new BatchRequestScheduler();

const apiCall = (id: string, delay: number, fail = false) => () =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      if (fail) reject(new Error(`${id} failed`));
      else resolve(`${id} data`);
    }, delay);
  });

scheduler
  .add({
    id: 'user',
    executor: apiCall('user', 200),
    timeout: 1000,
    priority: 10,
    fallback: () => 'user fallback data',
  })
  .add({
    id: 'products',
    executor: apiCall('products', 3000), // 会超时
    timeout: 2000,
    priority: 5,
    fallback: () => 'products fallback data',
  })
  .add({
    id: 'orders',
    executor: apiCall('orders', 150, true), // 会失败
    timeout: 1000,
    priority: 8,
    fallback: () => 'orders fallback data',
  });

scheduler.executeAll('all').then(results => {
  console.log('最终数据:', results);
  // 即使有超时和失败的请求,因为有降级,最终仍然有完整结果
});

底层原理

1. V8 中的 Promise 实现(C++ 层)

V8 对 Promise 的实现位于 src/builtins/builtins-promise.ccsrc/objects/promise.tq(Torque 语言)。核心数据结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Torque 伪代码
@export
macro NewPromise(): Promise {
  const promise = new Promise;
  promise.state = PromiseState::kPending;
  promise.result = TheHole;  // 初始值的占位符
  promise.reactions_or_result = kEmpty;  // 反应链
  promise.has_handler = false;
  return promise;
}

// PromiseFulfillReactionJob 的实现
transitioning macro PromiseFulfill(Promise promise, Object value) {
  promise.state = PromiseState::kFulfilled;
  promise.result = value;

  // 遍历 reactions 链表
  let reaction = promise.reactions_or_result;
  while (reaction is not null) {
    // 将 .then() 的回调包装为 Microtask 加入队列
    EnqueueMicrotask(
      PromiseFulfillReactionJob,  // 处理函数
      reaction                     // 反应记录
    );
    reaction = reaction.next;
  }
}

关键洞察

  • V8 的 microtask 队列使用链表实现,PromiseFulfillReactionJob.then() 回调包装为 Microtask 节点
  • 每个 Promise 只维护一个 reactions_or_result 链,所有注册到该 Promise 的 .then() 回调组成链表
  • 当 Promise resolve 时,V8 遍历链表并将所有回调入队 microtask 队列,而不是同步执行

2. Promise 与 Event Loop 的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 此代码演示微任务、宏任务、渲染之间的优先级
setTimeout(() => console.log('macro 1'), 0);
setTimeout(() => console.log('macro 2'), 0);

Promise.resolve().then(() => {
  console.log('micro 1');
  Promise.resolve().then(() => console.log('micro 2'));
});

requestAnimationFrame(() => console.log('rAF'));

// 输出顺序(浏览器中):
// micro 1
// micro 2
// rAF
// macro 1
// macro 2

事件循环的处理模型:

1
2
3
4
5
6
1. 执行一个宏任务(从宏任务队列取出)
2. 清空微任务队列(所有 microtask)
3. 执行 requestAnimationFrame 回调(如果到渲染时机)
4. 更新渲染(样式、布局、绘制)
5. 检查 requestIdleCallback
6. 回到步骤 1,执行下一个宏任务

因此,Promise 的回调(微任务)一定在当前宏任务结束前执行完,且优先于 rAF 渲染。

3. 内存泄漏场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 未处理的 rejection 不一定是内存泄漏
// 但 Promise 链可能造成内存泄漏的常见场景:

// 场景 1:未结束的 Promise 链
function danglingPromise() {
  return new Promise(() => {
    // 既不 resolve 也不 reject → 这个 Promise 永远 pending
    // 所有依赖它的 .then() 回调永远不会执行
    // 但闭包不会被 GC(因为回调还被 Promise 持有)
  });
}

// 场景 2:创建大量 pending Promise
// 在递归中对同一个 Promise 链无限加 .then()
function infiniteChain() {
  let p = Promise.resolve();
  for (let i = 0; i < 1000000; i++) {
    p = p.then(() => {
      // 这个内部函数保持对外部作用域的引用,形成闭包
      return heavyOperation();
    });
  }
}
// 这样会创建 100 万个 Promise 对象,每个都引用前一个
// 产生严重内存占用

高频面试题解析

面试题 1:说说 Promise.all 与 Promise.allSettled 的区别。一个实际场景中,需要发送 10 个接口请求,但只想让最多 3 个同时进行,怎么做?

解答

区别

  • Promise.all 快速失败:任一 rejected 就整体 rejected,不管其他还 pendding
  • Promise.allSettled 等其他全部完成后返回,不会 rejected

并发限制实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
async function limitConcurrency(tasks, limit = 3) {
  const results = [];
  const executing = new Set();

  for (const [index, task] of tasks.entries()) {
    const promise = Promise.resolve().then(() => task());
    results.push(promise);
    executing.add(promise);

    const clean = () => executing.delete(promise);
    promise.then(clean, clean);

    if (executing.size >= limit) {
      // 等待其中一个完成
      await Promise.race(executing);
    }
  }

  return Promise.allSettled(results);
}

// 使用
const urls = Array.from({ length: 10 }, (_, i) =>
  () => fetch(`/api/page/${i}`).then(r => r.json())
);

limitConcurrency(urls, 3).then(results => {
  const successes = results.filter(r => r.status === 'fulfilled');
  const failures = results.filter(r => r.status === 'rejected');
  console.log(`成功 ${successes.length}, 失败 ${failures.length}`);
});

面试题 2:以下代码的输出顺序是什么?解释为什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
console.log('1');

setTimeout(() => {
  console.log('2');
  Promise.resolve().then(() => console.log('3'));
}, 0);

Promise.resolve().then(() => {
  console.log('4');
  setTimeout(() => console.log('5'), 0);
});

console.log('6');

解答

输出:1, 6, 4, 2, 3, 5

详细分析

  1. 执行同步代码:打印 1
  2. 遇到 setTimeout(fn1, 0),将 fn1 加入宏任务队列
  3. 遇到 Promise.resolve().then(fn2),将 fn2 加入微任务队列
  4. 打印 6
  5. 当前宏任务结束,清空微任务队列:
    • fn2: 打印 4,遇到 setTimeout(fn3, 0),将 fn3 加入宏任务队列
  6. 进入下一个宏任务 fn1: 打印 2,遇到 Promise.resolve().then(fn4),加入微任务队列
  7. 清空微任务:fn4 打印 3
  8. 进入下一个宏任务 fn3: 打印 5

面试题 3:手写一个 Promise.retry,让一个 Promise 最多重试 N 次,满足条件才 resolve,否则 reject。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
 * Promise.retry - 带条件的重试
 * @param {Function} fn - 返回 Promise 的函数
 * @param {number} maxRetries - 最大重试次数
 * @param {Object} options
 * @param {number} options.delay - 重试间隔(ms)
 * @param {number} options.backoff - 退避倍数
 * @param {Function} [options.retryIf] - 重试条件判断,默认全部错误都重试
 * @returns {Promise}
 */
Promise.retry = function (fn, maxRetries = 3, options = {}) {
  const { delay = 1000, backoff = 2, retryIf = () => true } = options;

  return new Promise(async (resolve, reject) => {
    let attempt = 0;
    let currentDelay = delay;

    while (attempt <= maxRetries) {
      try {
        attempt++;
        const result = await fn(attempt);
        return resolve(result);
      } catch (error) {
        console.warn(`[retry] 第 ${attempt}/${maxRetries + 1} 次失败:`, error.message);

        if (attempt > maxRetries || !retryIf(error, attempt)) {
          return reject(error);
        }

        // 等待一段时间再重试
        await new Promise(r => setTimeout(r, currentDelay));
        currentDelay *= backoff; // 指数退避
      }
    }
  });
};

// 使用 1:简单重试
const fetchWithRetry = () =>
  Promise.retry(() => fetch('https://api.example.com/data'), 3, { delay: 500 });

// 使用 2:只在 5xx 错误时重试
const smartRetryFetch = () =>
  Promise.retry(
    async () => {
      const resp = await fetch('/api/data');
      if (!resp.ok) throw resp;
      return resp.json();
    },
    3,
    {
      delay: 1000,
      backoff: 2,
      retryIf: (error) => error.status >= 500, // 只在服务端错误时重试
    }
  );

总结与扩展

手写 Promise 全家桶的意义不仅在于面试,更在于帮助开发者建立对 JavaScript 异步系统的完整认知:从状态机、微任务调度到值展开。

值得进一步探索的方向

  • Promise.withResolvers:ES2024 新增方法,允许在 executor 外部获取 resolve/reject,解决某些场景下需要new Promise() 的抽象需求
1
2
3
4
const { promise, resolve, reject } = Promise.withResolvers();
// 可以在任意地方 resolve/reject
setTimeout(() => resolve('done'), 1000);
return promise;
  • Async Generatorsasync function* 可以与 Promise 结合,实现流式处理和背压

  • async/await 的底层就是 Promise:每个 await 编译为 .then() 调用,构成 Promise 链

  • AbortSignal.timeout():可以与 Promise.race 组合来替代手动超时逻辑

1
2
// 更简洁的超时方案
const response = await fetch(url, { signal: AbortSignal.timeout(3000) });

Promise 是 JavaScript 异步编程的基石,手写它的时候,你实际上在写一个微小的”事件循环系统”。

本文由作者按照 CC BY 4.0 进行授权

© 独行的风. 保留部分权利。

本站采用 Jekyll 主题 Chirpy

本站总访问量 本站访客数 本文阅读量