在 Node.js 中探索 Promise

¥Discover Promises in Node.js

Promise 是 JavaScript 中的一个特殊对象,表示异步操作的最终完成(或失败)及其结果值。本质上,Promise 是一个占位符,表示当前不可用但将来会可用的值。

¥A Promise is a special object in JavaScript that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Essentially, a Promise is a placeholder for a value that is not yet available but will be in the future.

把 Promise 想象成订购披萨:你没有立即收到货,但送货员承诺稍后会给你送来。你不知道确切的时间,但你知道结果要么是 "披萨已送达",要么是 "出现问题。"。

¥Think of a Promise like ordering a pizza: you don't get it right away, but the delivery person promises to bring it to you later. You don't know exactly when, but you know the outcome will either be "pizza delivered" or "something went wrong."

Promise 状态

¥Promise States

Promise 可以处于以下三种状态之一:

¥A Promise can be in one of three states:

  • 待处理:初始状态,异步操作仍在运行。

    ¥Pending: The initial state, where the asynchronous operation is still running.

  • 已实现:操作已成功完成,Promise 现已解析并返回一个值。

    ¥Fulfilled: The operation completed successfully, and the Promise is now resolved with a value.

  • 已拒绝:操作失败,Promise 已解析并返回一个原因(通常是错误)。

    ¥Rejected: The operation failed, and the Promise is settled with a reason (usually an error).

当你订购披萨时,你处于待处理状态,饥肠辘辘,满怀希望。如果披萨送到时是热腾腾的,而且有奶酪味,则表示你已进入 fulfilled 状态。但是,如果餐厅打调用说你的披萨掉在地上了,你就处于拒绝状态。

¥When you order the pizza, You're in the pending state, hungry and hopeful. If the pizza arrives hot and cheesy, you've entered the fulfilled state. But if the restaurant calls to say they've dropped your pizza on floor, you're in the rejected state.

无论你的晚餐以喜悦还是失望结束,一旦有了最终结果,Promise 就被视为已完成。

¥Regardless of whether your dinner ends in joy or disappointment, once there's a final outcome, the Promise is considered settled.

Promise 的基本语法

¥Basic Syntax of a Promise

创建 Promise 的最常见方法之一是使用 new Promise() 构造函数。构造函数接受一个包含两个参数的函数:resolvereject。这些函数用于将 Promise 从待处理状态转换为已完成或已拒绝状态。

¥One of the most common ways to create a Promise is using the new Promise() constructor. The constructor takes a function with two parameters: resolve and reject. These functions are used to transition the Promise from the pending state to either fulfilled or rejected.

如果执行器函数内部抛出错误,Promise 将被拒绝并返回该错误。执行器函数的返回值将被忽略:只能使用 resolvereject 来执行 Promise。

¥If an error is thrown inside the executor function, the Promise will be rejected with that error. The return value of the executor function is ignored: only resolve or reject should be used to settle the Promise.

const myPromise = new Promise((resolve, reject) => {
  let success = true;

  if (success) {
    resolve('Operation was successful!');
  } else {
    reject('Something went wrong.');
  }
});

在上述示例:

¥In the above example:

  • 如果 success 的条件为 true,则 Promise 被满足,并将值 'Operation was successful!' 传递给 resolve 函数。

    ¥If the success condition is true, the Promise is fulfilled and the value 'Operation was successful!' is passed to the resolve function.

  • 如果 success 的条件为 false,则 Promise 被拒绝,并将错误 'Something went wrong.' 传递给 reject 函数。

    ¥If the success condition is false, the Promise is rejected and the error 'Something went wrong.' is passed to the reject function.

使用 .then().catch().finally() 处理 Promise

¥Handling Promises with .then(), .catch(), and .finally()

创建 Promise 后,你可以使用 .then().catch().finally() 方法处理结果。

¥Once a Promise is created, you can handle the outcome by using the .then(), .catch(), and .finally() methods.

  • .then() 用于处理已完成的 Promise 并访问其结果。

    ¥.then() is used to handle a fulfilled Promise and access its result.

  • .catch() 用于处理已拒绝的 Promise 并捕获可能发生的任何错误。

    ¥.catch() is used to handle a rejected Promise and catch any errors that may occur.

  • .finally() 用于处理已完成的 Promise,无论该 Promise 是已解决还是已拒绝。

    ¥.finally() is used to handle a settled Promise, regardless of whether the Promise resolved or rejected.

const myPromise = new Promise((resolve, reject) => {
  let success = true;

  if (success) {
    resolve('Operation was successful!');
  } else {
    reject('Something went wrong.');
  }
});

myPromise
  .then(result => {
    console.log(result); // This will run if the Promise is fulfilled
  })
  .catch(error => {
    console.error(error); // This will run if the Promise is rejected
  })
  .finally(() => {
    console.log('The promise has completed'); // This will run when the Promise is settled
  });

Promise 链式调用

¥Chaining Promises

Promise 的一大优势在于它允许你将多个异步操作链接在一起。当你链接 Promise 时,每个 .then() 块都会等待前一个块完成后再运行。

¥One of the great features of Promises is that they allow you to chain multiple asynchronous operations together. When you chain Promises, each .then() block waits for the previous one to complete before it runs.

const { setTimeout: delay } = require('node:timers/promises');

const promise = delay(1000).then(() => 'First task completed');

promise
  .then(result => {
    console.log(result); // 'First task completed'
    return delay(1000).then(() => 'Second task completed'); // Return a second Promise
  })
  .then(result => {
    console.log(result); // 'Second task completed'
  })
  .catch(error => {
    console.error(error); // If any Promise is rejected, catch the error
  });

将 Async/Await 与 Promise 结合使用

¥Using Async/Await with Promises

在现代 JavaScript 中使用 Promise 的最佳方法之一是使用 async/await。这让你可以编写看起来像同步的异步代码,使其更易于阅读和维护。

¥One of the best ways to work with Promises in modern JavaScript is using async/await. This allows you to write asynchronous code that looks synchronous, making it much easier to read and maintain.

  • async 用于定义一个返回 Promise 的函数。

    ¥async is used to define a function that returns a Promise.

  • awaitasync 函数内部使用,用于暂停执行,直到 Promise 完成。

    ¥await is used inside an async function to pause execution until a Promise settles.

async function performTasks() {
  try {
    const result1 = await promise1;
    console.log(result1); // 'First task completed'

    const result2 = await promise2;
    console.log(result2); // 'Second task completed'
  } catch (error) {
    console.error(error); // Catches any rejection or error
  }
}

performTasks();

performTasks 函数中,await 关键字确保每个 Promise 都已完成,然后再转到下一个语句。这使得异步代码流更加线性和可读。

¥In the performTasks function, the await keyword ensures that each Promise is settled before moving on to the next statement. This leads to a more linear and readable flow of asynchronous code.

本质上,上面的代码的执行方式与用户编写的以下内容相同:

¥Essentially, the code above will execute the same as if the user wrote:

promise1
  .then(function (result1) {
    console.log(result1);
    return promise2;
  })
  .then(function (result2) {
    console.log(result2);
  })
  .catch(function (error) {
    console.log(error);
  });

顶层 Await

¥Top-Level Await

使用 ECMAScript 模块 时,模块本身将被视为原生支持异步操作的顶层作用域。这意味着你无需 async 函数即可使用 await 顶层

¥When using ECMAScript Modules, the module itself is treated as a top-level scope that supports asynchronous operations natively. This means that you can use await at the top level without needing an async function.

import { setTimeout as delay } from 'node:timers/promises';

await delay(1000);

Async/await 可能比提供的简单示例复杂得多。Node.js 技术指导委员会成员 James Snell 编写了一个 深入介绍,探讨了 Promise 和 async/await 的复杂性。

¥Async/await can be much more intricate than the simple examples provided. James Snell, a member of the Node.js Technical Steering Committee, has an in-depth presentation that explores the complexities of Promises and async/await.

基于 Promise 的 Node.js API

¥Promise-based Node.js APIs

Node.js 为其许多核心 API 提供了基于 Promise 的版本,尤其是在传统上使用回调处理异步操作的情况下。这使得使用 Node.js API 和 Promise 更加容易,并降低了 "回调地狱。" 的风险。

¥Node.js provides Promise-based versions of many of its core APIs, especially in cases where asynchronous operations were traditionally handled with callbacks. This makes it easier to work with Node.js APIs and Promises, and reduces the risk of "callback hell."

例如,fs(文件系统)模块在 fs.promises 下有一个基于 Promise 的 API:

¥For example, the fs (file system) module has a Promise-based API under fs.promises:

const fs = require('node:fs').promises;
// Or, you can import the promisified version directly:
// const fs = require('node:fs/promises');

async function readFile() {
  try {
    const data = await fs.readFile('example.txt', 'utf8');
    console.log(data);
  } catch (err) {
    console.error('Error reading file:', err);
  }
}

readFile();

在此示例中,fs.promises.readFile() 返回一个 Promise,我们使用 async/await 语法处理该 Promise,以异步方式读取文件内容。

¥In this example, fs.promises.readFile() returns a Promise, which we handle using async/await syntax to read the contents of a file asynchronously.

高级 Promise 方法

¥Advanced Promise Methods

JavaScript 的 Promise 全局变量提供了几种强大的方法,可帮助你更有效地管理多个异步任务:

¥JavaScript's Promise global provides several powerful methods that help manage multiple asynchronous tasks more effectively:

Promise.all()

此方法接受一个 Promise 数组,并返回一个新的 Promise,该 Promise 在所有 Promise 都完成之后会解析。如果任何 Promise 被拒绝,Promise.all() 将立即拒绝。但是,即使发生拒绝,Promise 仍会继续执行。处理大量 Promise 时,尤其是在批处理中,使用此函数可能会给系统内存带来压力。

¥This method accepts an array of Promises and returns a new Promise that resolves once all the Promises are fulfilled. If any Promise is rejected, Promise.all() will immediately reject. However, even if rejection occurs, the Promises continue to execute. When handling a large number of Promises, especially in batch processing, using this function can strain the system's memory.

const { setTimeout: delay } = require('node:timers/promises');

const fetchData1 = delay(1000).then(() => 'Data from API 1');
const fetchData2 = delay(2000).then(() => 'Data from API 2');

Promise.all([fetchData1, fetchData2])
  .then(results => {
    console.log(results); // ['Data from API 1', 'Data from API 2']
  })
  .catch(error => {
    console.error('Error:', error);
  });

Promise.allSettled()

此方法等待所有 Promise 解析或拒绝,并返回一个描述每个 Promise 结果的对象数组。

¥This method waits for all promises to either resolve or reject and returns an array of objects that describe the outcome of each Promise.

const promise1 = Promise.resolve('Success');
const promise2 = Promise.reject('Failed');

Promise.allSettled([promise1, promise2]).then(results => {
  console.log(results);
  // [ { status: 'fulfilled', value: 'Success' }, { status: 'rejected', reason: 'Failed' } ]
});

Promise.all() 不同,Promise.allSettled() 不会在失败时短路。它等待所有 Promise 解决,即使某些 Promise 被拒绝。这为批量操作提供了更好的错误处理,你可能希望了解所有任务的状态,无论是否失败。

¥Unlike Promise.all(), Promise.allSettled() does not short-circuit on failure. It waits for all promises to settle, even if some reject. This provides better error handling for batch operations, where you may want to know the status of all tasks, regardless of failure.

Promise.race()

只要第一个 Promise 完成,此方法就会解析或拒绝,无论它是解析还是拒绝。无论哪个 Promise 先完成,所有 Promise 都会被完全执行。

¥This method resolves or rejects as soon as the first Promise settles, whether it resolves or rejects. Regardless of which promise settles first, all promises are fully executed.

const { setTimeout: delay } = require('node:timers/promises');

const task1 = delay(2000).then(() => 'Task 1 done');
const task2 = delay(1000).then(() => 'Task 2 done');

Promise.race([task1, task2]).then(result => {
  console.log(result); // 'Task 2 done' (since task2 finishes first)
});

Promise.any()

只要其中一个 Promise 解析,此方法就会解析。如果所有 Promise 都被拒绝,它将拒绝并返回 AggregateError

¥This method resolves as soon as one of the Promises resolves. If all promises are rejected, it will reject with an AggregateError.

const { setTimeout: delay } = require('node:timers/promises');

const api1 = delay(2000).then(() => 'API 1 success');
const api2 = delay(1000).then(() => 'API 2 success');
const api3 = delay(1500).then(() => 'API 3 success');

Promise.any([api1, api2, api3])
  .then(result => {
    console.log(result); // 'API 2 success' (since it resolves first)
  })
  .catch(error => {
    console.error('All promises rejected:', error);
  });

Promise.reject()Promise.resolve()

¥Promise.reject() and Promise.resolve()

这些方法直接创建已拒绝或已解决的 Promise。

¥These methods create a rejected or resolved Promise directly.

Promise.resolve('Resolved immediately').then(result => {
  console.log(result); // 'Resolved immediately'
});

Promise.try()

Promise.try() 是一个方法,它执行给定的函数(无论是同步还是异步),并将结果封装在 Promise 中。如果函数抛出错误或返回一个被拒绝的 Promise,Promise.try() 将返回一个被拒绝的 Promise。如果函数成功完成,返回的 Promise 将以其值作为 fulfilled 状态。

¥Promise.try() is a method that executes a given function, whether it's synchronous or asynchronous, and wraps the result in a promise. If the function throws an error or returns a rejected promise, Promise.try() will return a rejected promise. If the function completes successfully, the returned promise will be fulfilled with its value.

这对于以一致的方式启动 Promise 链特别有用,尤其是在处理可能同步抛出错误的代码时。

¥This can be particularly useful for starting promise chains in a consistent way, especially when working with code that might throw errors synchronously.

function mightThrow() {
  if (Math.random() > 0.5) {
    throw new Error('Oops, something went wrong!');
  }
  return 'Success!';
}

Promise.try(mightThrow)
  .then(result => {
    console.log('Result:', result);
  })
  .catch(err => {
    console.error('Caught error:', err.message);
  });

在此示例中,Promise.try() 确保如果 mightThrow() 抛出错误,则会在 .catch() 块中捕获该错误,从而更容易在一个地方处理同步和异步错误。

¥In this example, Promise.try() ensures that if mightThrow() throws an error, it will be caught in the .catch() block, making it easier to handle both sync and async errors in one place.

Promise.withResolvers()

此方法创建一个新的 Promise 及其关联的解析和拒绝函数,并将它们返回到一个方便的对象中。例如,当你需要创建一个 Promise,但稍后在执行器函数外部解析或拒绝它时,可以使用这种方法。

¥This method creates a new promise along with its associated resolve and reject functions, and returns them in a convenient object. This is used, for example, when you need to create a promise but resolve or reject it later from outside the executor function.

const { promise, resolve, reject } = Promise.withResolvers();

setTimeout(() => {
  resolve('Resolved successfully!');
}, 1000);

promise.then(value => {
  console.log('Success:', value);
});

在此示例中,Promise.withResolvers() 让你完全控制 Promise 的解决或拒绝时间及方式,而无需内联定义执行器函数。此模式通常用于事件驱动编程、超时或与非基于 Promise 的 API 集成。

¥In this example, Promise.withResolvers() gives you full control over when and how the promise is resolved or rejected, without needing to define the executor function inline. This pattern is commonly used in event-driven programming, timeouts, or when integrating with non-promise-based APIs.

使用 Promise 进行错误处理

¥Error Handling with Promises

处理 Promise 中的错误可确保你的应用在发生意外情况时正常运行。

¥Handling errors in Promises ensures your application behaves correctly in case of unexpected situations.

  • 你可以使用 .catch() 来处理 Promise 执行过程中发生的任何错误或拒绝。

    ¥You can use .catch() to handle any errors or rejections that occur during the execution of Promises.

myPromise
  .then(result => console.log(result))
  .catch(error => console.error(error)) // Handles the rejection
  .finally(error => console.log('Promise completed')); // Runs regardless of promise resolution
  • 或者,在使用 async/await 时,可以使用 try/catch 块来捕获和处理错误。

    ¥Alternatively, when using async/await, you can use a try/catch block to catch and handle errors.

async function performTask() {
  try {
    const result = await myPromise;
    console.log(result);
  } catch (error) {
    console.error(error); // Handles any errors
  } finally {
    // This code is executed regardless of failure
    console.log('performTask() completed');
  }
}

performTask();

在事件循环中调度任务

¥Scheduling Tasks in the Event Loop

除了 Promise 之外,Node.js 还提供了其他几种在事件循环中调度任务的机制。

¥In addition to Promises, Node.js provides several other mechanisms for scheduling tasks in the event loop.

queueMicrotask()

queueMicrotask() 用于调度微任务,这是一种轻量级任务,在当前执行的脚本之后、任何其他 I/O 事件或计时器之前运行。微任务包括 Promise 解决和其他优先级高于常规任务的异步操作等任务。

¥queueMicrotask() is used to schedule a microtask, which is a lightweight task that runs after the currently executing script but before any other I/O events or timers. Microtasks include tasks like Promise resolutions and other asynchronous operations that are prioritized over regular tasks.

queueMicrotask(() => {
  console.log('Microtask is executed');
});

console.log('Synchronous task is executed');

在上面的例子中,"微任务已执行" 将在 "同步任务已执行," 之后、但在任何 I/O 操作(例如计时器)之前记录。

¥In the above example, "Microtask is executed" will be logged after "Synchronous task is executed," but before any I/O operations like timers.

process.nextTick()

process.nextTick() 用于安排在当前操作完成后立即执行回调。这在你希望确保回调尽快执行,但仍在当前执行上下文之后的情况下非常有用。

¥process.nextTick() is used to schedule a callback to be executed immediately after the current operation completes. This is useful for situations where you want to ensure that a callback is executed as soon as possible, but still after the current execution context.

process.nextTick(() => {
  console.log('Next tick callback');
});

console.log('Synchronous task executed');

setImmediate()

setImmediate() 用于在当前事件循环结束且所有 I/O 事件都处理完毕后执行回调。这意味着 setImmediate() 回调在任何 I/O 回调之后运行,但在计时器之前运行。

¥setImmediate() is used to execute a callback after the current event loop cycle finishes and all I/O events have been processed. This means that setImmediate() callbacks run after any I/O callbacks, but before timers.

setImmediate(() => {
  console.log('Immediate callback');
});

console.log('Synchronous task executed');

何时使用每个方法

¥When to Use Each

  • queueMicrotask() 用于需要在任何 I/O 或计时器回调之前立即运行的任务,通常用于 Promise 解析。

    ¥Use queueMicrotask() for tasks that need to run immediately after the current script and before any I/O or timer callbacks, typically for Promise resolutions.

  • process.nextTick() 用于应在任何 I/O 事件之前执行的任务,通常用于延迟操作或同步处理错误。

    ¥Use process.nextTick() for tasks that should execute before any I/O events, often useful for deferring operations or handling errors synchronously.

  • setImmediate() 用于应在 I/O 事件之后但在计时器之前运行的任务。

    ¥Use setImmediate() for tasks that should run after I/O events but before timers.

由于这些任务在当前同步流程之外执行,这些回调函数中未捕获的异常不会被周围的 try/catch 块捕获,如果处理不当(例如,将 .catch() 附加到 Promise 或使用像 process.on('uncaughtException') 这样的全局错误处理程序),可能会导致应用崩溃。

¥Because these tasks execute outside of the current synchronous flow, uncaught exceptions inside these callbacks won't be caught by surrounding try/catch blocks and may crash the application if not properly managed (e.g., by attaching .catch() to Promises or using global error handlers like process.on('uncaughtException')).

有关事件循环以及各个阶段执行顺序的更多信息,请参阅相关文章 Node.js 事件循环

¥For more information on the Event Loop, and the execution order of various phases, please see the related article, The Node.js Event Loop.