首页 / Node.js 教程 / Promise

Node.js 教程

Promise

本教程共 76 篇 · 第 12 篇 · 更新于 2026-07-25 · 约 6 分钟阅读

Node.jsPromise异步then错误处理

12. Promise

本节目标:Promise 的状态机、链式调用、错误处理和常用静态方法。

回调地狱的解药,就是 Promise。它把异步操作封装成一个对象,这个对象有状态、能链式调用,还能把错误统一丢给末尾的 .catch()。从 ES6 开始,Promise 就成了 JavaScript 异步的标准答案。

Node.js 核心模块也纷纷跟进。node:fs/promisesnode:timers/promisesnode: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('商家打烊');
  }
});

resolvereject 是两个函数,由 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 外部控制 resolvereject,这招很实用:

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 默认会打印警告并可能终止进程。别赌这个运气。

最佳实践:

  1. 链条末尾始终跟 .catch()
  2. try/catch 配合 async/await(下一章讲)。
  3. 给进程挂上 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/promisesnode:dns/promises 等),优先用官方 Promise API,而不是 promisify