Promise
本教程共 76 篇 · 第 12 篇 · 更新于 2026-07-25 · 约 6 分钟阅读
12. Promise
本节目标:Promise 的状态机、链式调用、错误处理和常用静态方法。
回调地狱的解药,就是 Promise。它把异步操作封装成一个对象,这个对象有状态、能链式调用,还能把错误统一丢给末尾的 .catch()。从 ES6 开始,Promise 就成了 JavaScript 异步的标准答案。
Node.js 核心模块也纷纷跟进。node:fs/promises、node:timers/promises、node:dns/promises……几乎你叫得出名字的 I/O 操作,现在都有 Promise 版本。v24 LTS 环境下,写新代码没有理由不优先用它们。
Promise 的三种状态
一个 Promise 对象好比一张外卖订单:
- Pending(待定):你刚下单,商家还没出餐。
- Fulfilled(已完成):外卖送到了,你可以开吃。
- Rejected(已拒绝):商家退单了,你得另想办法。
状态一旦从 pending 变成 fulfilled 或 rejected,就再也回不去。这叫做已落定(settled)。
const order = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve('披萨已送达');
} else {
reject('商家打烊');
}
});
resolve 和 reject 是两个函数,由 Promise 构造函数自动注入。你调用 resolve(value),Promise 变成 fulfilled;调用 reject(reason),变成 rejected。
Tip如果在 executor 函数里抛出异常,Promise 会自动 rejected,相当于调用了
reject(err)。这很方便,也意味着你可以在里面写throw new Error(...)。
.then()、.catch()、.finally()
Promise 落定后,你需要处理结果。三个方法各司其职:
order
.then(result => {
console.log(result); // 披萨已送达
})
.catch(err => {
console.error('出错了:', err);
})
.finally(() => {
console.log('订单流程结束,不论成败');
});
.then(onFulfilled, onRejected):处理成功,也可顺带处理失败(但不常用)。.catch(onRejected):专门兜底错误,相当于.then(null, onRejected)。.finally(onFinally):不论成功失败,都会执行,适合清理资源。
链式调用的关键在于,.then() 会返回一个新的 Promise。你可以一直 .then().then().then() 下去,把嵌套拉成一条直线。
链式调用:把嵌套拉平
假设有一个读取用户、再读取订单、再读取详情的流程:
import { readFile } from 'node:fs/promises';
readFile('user.json', 'utf8')
.then(data => {
const user = JSON.parse(data);
console.log('用户:', user.name);
return readFile('orders.json', 'utf8'); // 返回新 Promise
})
.then(data => {
const orders = JSON.parse(data);
console.log('订单数:', orders.length);
return readFile('details.json', 'utf8');
})
.then(data => {
console.log('详情:', data);
})
.catch(err => {
console.error('任意一步出错:', err.message);
});
每一步 .then() 里返回一个新的 Promise,链条就会等待它完成再继续。如果中间某一步抛错或返回 rejected Promise,链条会跳过剩下的 .then(),直接进入 .catch()。
Warning忘记
return是新手最常见的坑。如果你在.then()里创建了 Promise 但没返回,链条不会等它,而是立刻进入下一个.then(),拿着undefined往下走。// 错误示范 .then(data => { readFile('next.txt'); // 没 return! })
静态方法:批量处理异步任务
Promise 构造函数本身提供了几个非常实用的静态方法,用来处理多任务场景。
Promise.all()
等所有 Promise 全部成功,返回结果数组。任意一个失败,整体立刻失败。
import { readFile } from 'node:fs/promises';
const files = ['a.txt', 'b.txt', 'c.txt'];
Promise.all(files.map(f => readFile(f, 'utf8')))
.then(contents => {
console.log(contents); // ['内容a', '内容b', '内容c']
})
.catch(err => {
console.error('至少一个文件读取失败:', err);
});
批量发请求、批量读文件,这是我最常用的模式。
Promise.allSettled()
如果你不想因为一颗老鼠屎坏了一锅粥,用 allSettled。它等所有 Promise 落定,返回一个数组,里面告诉你每个是成功还是失败:
const promises = [
Promise.resolve('ok'),
Promise.reject('fail'),
Promise.resolve('also ok'),
];
Promise.allSettled(promises).then(results => {
console.log(results);
// [
// { status: 'fulfilled', value: 'ok' },
// { status: 'rejected', reason: 'fail' },
// { status: 'fulfilled', value: 'also ok' }
// ]
});
Promise.race()
谁最先落定就听谁的,不论成功还是失败。
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时')), 5000);
});
const fetchData = fetch('https://api.example.com/data');
Promise.race([fetchData, timeout])
.then(response => console.log('拿到数据'))
.catch(err => console.error(err.message));
Promise.any()
等第一个成功的 Promise。如果全部失败,抛出一个 AggregateError。
const mirrors = [
fetch('https://mirror-a.com/data'),
fetch('https://mirror-b.com/data'),
fetch('https://mirror-c.com/data'),
];
Promise.any(mirrors)
.then(response => response.json())
.catch(err => console.error('所有镜像都挂了:', err.errors));
Promise.resolve() / Promise.reject()
快速创建一个已落定状态的 Promise,常用于测试或兼容层:
Promise.resolve(42).then(v => console.log(v)); // 42
Promise.reject('oops').catch(e => console.error(e));
Promise.withResolvers()(ES2024)
自 v22.18+/v24 起可用。如果你需要在 Promise 外部控制 resolve 和 reject,这招很实用:
const { promise, resolve, reject } = Promise.withResolvers();
setTimeout(() => resolve('时间到了'), 1000);
promise.then(v => console.log(v));
以前你得自己包一个 new Promise 并把 resolve/reject 挂到外部变量上,现在原生支持了。
Promise.try()(ES2024)
同步函数如果抛错,不会自动变成 rejected Promise。Promise.try() 帮你包一层:
function mightThrow() {
if (Math.random() > 0.5) throw new Error('boom');
return 'safe';
}
Promise.try(mightThrow)
.then(v => console.log(v))
.catch(e => console.error(e.message));
不用写 return new Promise((resolve, reject) => { try { ... } catch ... }) 那种啰嗦代码了。
错误处理:别遗漏 rejected Promise
未处理的 Promise 拒绝在 Node.js 里不会直接崩进程,但会触发 unhandledRejection 事件。从 v15 开始,如果没有监听器,Node.js 默认会打印警告并可能终止进程。别赌这个运气。
最佳实践:
- 链条末尾始终跟
.catch()。 - 用
try/catch配合async/await(下一章讲)。 - 给进程挂上
unhandledRejection事件做最后一道保险:
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
从回调转 Promise:util.promisify
旧模块或第三方库只提供回调风格怎么办?Node.js 内置了 util.promisify,把一个 error-first 回调函数包装成返回 Promise 的版本:
import { promisify } from 'node:util';
import { exec } from 'node:child_process';
const execPromise = promisify(exec);
const { stdout } = await execPromise('ls -la');
console.log(stdout);
Note现代 Node.js 核心模块大多已经自带 Promise 版本(
node:fs/promises、node:dns/promises等),优先用官方 Promise API,而不是promisify。