Node.js Promise 完全指南:使用技巧与面试常见问题

一、Promise 基础概念

Promise 是 JavaScript 中处理异步操作的核心对象,代表一个异步操作的最终完成或失败及其结果值。它解决了传统回调函数(Callback)带来的”回调地狱”问题,使异步代码更加可读、可维护。

三种状态

状态 说明
pending 初始状态,既未成功也未失败
fulfilled 操作成功完成
rejected 操作失败

状态一旦从 pending 转变为 fulfilled 或 rejected,就不可逆转。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve('操作成功');
} else {
reject(new Error('操作失败'));
}
}, 1000);
});

promise
.then(result => console.log(result))
.catch(err => console.error(err.message));

二、Promise 核心方法

1. .then() 链式调用

1
2
3
4
fetchUser(1)
.then(user => fetchOrders(user.id))
.then(orders => fetchOrderDetail(orders[0].id))
.then(detail => console.log(detail));

每个 .then() 返回一个新的 Promise,支持链式调用。

2. .catch() 错误处理

1
2
3
4
5
6
7
8
asyncOperation()
.then(result => {
// 处理结果
})
.catch(error => {
// 捕获上面任意环节抛出的错误
console.error('出错了:', error);
});

3. .finally() 最终执行

1
2
3
4
showLoading()
.then(data => render(data))
.catch(err => showError(err))
.finally(() => hideLoading()); // 无论成功失败都执行

三、Promise 静态方法

Promise.all — 全部成功才算成功

1
2
3
4
5
6
const [user, orders, products] = await Promise.all([
fetchUser(),
fetchOrders(),
fetchProducts(),
]);
// 任一 Promise reject,整体立即 reject

Promise.allSettled — 等待所有结果(ES2020)

1
2
3
4
5
const results = await Promise.allSettled([
fetchWithRetry(),
fetchBackup(),
]);
// results: [{status:'fulfilled', value:...}, {status:'rejected', reason:...}]

Promise.race — 取第一个完成的结果

1
2
3
4
5
6
const winner = await Promise.race([
fetchFromCDN(),
fetchFromOrigin(),
timeout(5000),
]);
// 第一个 resolve 或 reject 的结果

Promise.any — 取第一个成功的结果(ES2021)

1
2
3
4
5
6
7
const first = await Promise.any([
fetchFromServerA(),
fetchFromServerB(),
fetchFromServerC(),
]);
// 忽略所有失败,只要有一个成功就 resolve
// 全部失败则 reject AggregateError

四、实用技巧

1. 串行执行数组中的异步任务

1
2
3
4
5
6
7
async function sequentialTasks(tasks) {
const results = [];
for (const task of tasks) {
results.push(await task());
}
return results;
}

2. 并发限制(控制同时执行的 Promise 数量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
async function promisePool(tasks, concurrency) {
const results = new Array(tasks.length);
let index = 0;

async function worker() {
while (index < tasks.length) {
const i = index++;
results[i] = await tasks[i]();
}
}

const workers = Array.from({ length: concurrency }, worker);
await Promise.all(workers);
return results;
}

// 使用:最多同时执行 3 个请求
const files = list.map(file => () => uploadFile(file));
await promisePool(files, 3);

3. 带超时的 Promise

1
2
3
4
5
6
7
8
function withTimeout(promise, ms) {
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms)
);
return Promise.race([promise, timeout]);
}

const data = await withTimeout(fetchUser(1), 3000);

4. 重试机制

1
2
3
4
5
6
7
8
9
10
11
12
async function retry(fn, times = 3, delay = 1000) {
for (let i = 0; i < times; i++) {
try {
return await fn();
} catch (err) {
if (i === times - 1) throw err;
await new Promise(r => setTimeout(r, delay * (i + 1)));
}
}
}

const result = await retry(() => unstableAPI(), 3, 500);

5. Promise 化回调函数(promisify)

1
2
3
4
5
const { promisify } = require('util');
const fs = require('fs');

const readFileAsync = promisify(fs.readFile);
const content = await readFileAsync('./data.txt', 'utf-8');

五、async/await 最佳实践

1. 错误处理:try/catch

1
2
3
4
5
6
7
8
9
async function getUser() {
try {
const res = await fetch('/api/user');
const data = await res.json();
return data;
} catch (err) {
console.error('获取用户失败:', err);
}
}

2. 避免不必要的串行等待

1
2
3
4
5
6
7
8
9
// ❌ 慢:串行执行
const user = await fetchUser();
const posts = await fetchPosts();

// ✅ 快:并行执行
const [user, posts] = await Promise.all([
fetchUser(),
fetchPosts(),
]);

3. 顶层 await(Node.js 14.8+ ESM)

1
2
3
// module.mjs
const config = await loadConfig();
export default config;

六、面试常见问题

Q1:Promise 和 Callback 有什么区别?

对比项 Callback Promise
可读性 嵌套深,回调地狱 链式调用,扁平化
错误处理 每个回调单独处理 .catch() 统一捕获
组合能力 难以组合多个异步操作 all/race/any 等原生支持
状态管理 无法保证只调用一次 settle 后不可变

Q2:.then() 和 .catch() 的执行顺序是怎样的?

1
2
3
4
Promise.reject('err')
.then(() => console.log('A')) // 跳过
.catch(() => console.log('B')) // 输出 B
.then(() => console.log('C')); // 输出 C

.catch() 相当于 .then(null, onRejected),捕获前面的错误后,后续 .then() 正常执行。

Q3:如何手动实现一个 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
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];

const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};

const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};

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

then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };

const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) { reject(e); }
});
} else if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) { reject(e); }
});
} else {
this.onFulfilledCallbacks.push(() => { /* 同上 */ });
this.onRejectedCallbacks.push(() => { /* 同上 */ });
}
});

return promise2;
}
}

function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) return reject(new TypeError('Chaining cycle detected'));
if (x && (typeof x === 'object' || typeof x === 'function')) {
let called = false;
try {
const then = x.then;
if (typeof then === 'function') {
then.call(x, y => { if (!called) { called = true; resolve(y); } },
r => { if (!called) { called = true; reject(r); } });
} else { resolve(x); }
} catch (e) { if (!called) reject(e); }
} else { resolve(x); }
}

Q4:Promise.all 和 Promise.allSettled 的区别?

  • Promise.all:快速失败,任意一个 reject 则整体立即 reject。
  • Promise.allSettled:全部等待,不论成功失败都返回完整结果数组。

适用场景:all 用于必须全部成功的并行任务;allSettled 用于希望获取所有结果的场景(如批量上报)。

Q5:async 函数返回值是什么?

async 函数始终返回一个 Promise。

  • 如果函数 return value,返回的 Promise 以 value resolve。
  • 如果函数 throw error,返回的 Promise 以 error reject。
  • 如果 return 一个 Promise,会等待该 Promise settle。
1
2
async function foo() { return 42; }
foo().then(v => console.log(v)); // 42

Q6:如何捕获 async 函数中未处理的异常?

1
2
3
4
5
6
7
8
// 方式一:try/catch
// 方式二:.catch()
asyncFn().catch(err => log(err));

// 方式三:全局兜底
process.on('unhandledRejection', (reason) => {
console.error('Unhandled Rejection:', reason);
});

Q7:Promise 的微任务(Microtask)机制

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

setTimeout(() => console.log('2'), 0);

Promise.resolve().then(() => console.log('3'));

console.log('4');

// 输出顺序:1 → 4 → 3 → 2

规则:同步代码 > 微任务(Promise.then) > 宏任务(setTimeout)。


七、常见陷阱

1. 循环中使用 await

1
2
3
4
5
6
7
// ❌ 串行,慢
for (const url of urls) {
const res = await fetch(url);
}

// ✅ 并行
const results = await Promise.all(urls.map(url => fetch(url)));

2. 忘记 return 或 await

1
2
3
4
5
6
7
8
9
// ❌ 错误:.then 链中断
function getData() {
fetch('/api').then(res => res.json()); // 没有 return
}

// ✅ 正确
function getData() {
return fetch('/api').then(res => res.json());
}

3. resolve 一个 Promise

1
2
3
4
// resolve 另一个 Promise 时,会等待它 settle,状态透传
new Promise(resolve => {
resolve(Promise.reject('err')); // 外层变成 rejected
});

总结

场景 推荐方案
单个异步操作 async/await + try/catch
多个并行独立操作 Promise.all
需要全部结果(含失败) Promise.allSettled
只要最快成功 Promise.any
超时控制 Promise.race + timeout
并发限制 自实现 promisePool

掌握 Promise 是 Node.js 异步编程的基石,无论是日常开发还是面试,理解其底层机制(状态机、微任务队列、链式调用原理)都能让你游刃有余。