跳到内容

在 Node.js 中探索 Promise

🌐 Discover Promises in Node.js

在 JavaScript 中,Promise 是一种特殊的对象,用于表示异步操作最终的完成(或失败)及其结果值。本质上,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.

把承诺想象成订披萨:你不能立刻得到它,但送餐员承诺稍后会送给你。你不知道确切的时间,但你知道结果要么是“披萨送到”,要么是“出了点问题”。

🌐 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:

  • 待定:初始状态,异步操作仍在进行中。
  • 已完成:操作已成功完成,Promise 现已以某个值解决。
  • 已拒绝:操作失败,Promise 因某个原因(通常是错误)而被拒绝。

当你订披萨时,你处于等待状态,既饥饿又充满希望。如果披萨热腾腾、奶酪丰富地送到,你就进入了完成状态。但是如果餐厅打电话来说他们把你的披萨掉在地上了,你就处于拒绝状态。

🌐 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.

无论你的晚餐以喜悦还是失望告终,一旦有了最终结果,这个承诺就被视为已完成

🌐 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  = new ((, ) => {
  const  = true;

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

在上面的例子中:

🌐 In the above example:

  • 如果 success 条件为 true,Promise 就会被履行,并且值 'Operation was successful!' 会传递给 resolve 函数。
  • 如果 success 条件为 false,Promise 将被拒绝,并且错误 'Something went wrong.' 会传递给 reject 函数。

使用 .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 并访问其结果。
  • .catch() 用于处理被拒绝的 Promise 并捕获可能发生的任何错误。
  • .finally() 用于处理已完成的 Promise,无论该 Promise 是解决(resolved)还是拒绝(rejected)。
const  = new ((, ) => {
  const  = true;

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


  .( => {
    .(); // This will run if the Promise is fulfilled
  })
  .( => {
    .(); // This will run if the Promise is rejected
  })
  .(() => {
    .('The promise has completed'); // This will run when the Promise is settled
  });

链式调用 Promise

🌐 Chaining Promises

Promises 的一个重要特点是它们允许你将多个异步操作链式连接。当你链式调用 Promises 时,每个 .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 { :  } = ('node:timers/promises');

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


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

使用 Async/Await 与 Promise

🌐 Using Async/Await with Promises

在现代 JavaScript 中使用 Promises 的最佳方法之一是使用 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 的函数。
  • awaitasync 函数内部使用,用于暂停执行,直到 Promise 处理完成。
async function () {
  try {
    const  = await promise1;
    .(); // 'First task completed'

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

();

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 () {
    .();
    return promise2;
  })
  .then(function () {
    .();
  })
  .catch(function () {
    .();
  });

顶层 Await

🌐 Top-Level Await

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

🌐 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 {  as  } from 'node:timers/promises';

await (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  = ('node:fs').;
// Or, you can import the promisified version directly:
// const fs = require('node:fs/promises');

async function () {
  try {
    const  = await .('example.txt', 'utf8');
    .();
  } catch () {
    .('Error reading file:', );
  }
}

();

在这个例子中,fs.readFile() 返回一个 Promise,我们使用 async/await 语法来异步读取文件内容。

🌐 In this example, fs.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 { :  } = ('node:timers/promises');

const  = (1000).(() => 'Data from API 1');
const  = (2000).(() => 'Data from API 2');

.([, ])
  .( => {
    .(); // ['Data from API 1', 'Data from API 2']
  })
  .( => {
    .('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  = .('Success');
const  = .('Failed');

.([, ]).( => {
  .();
  // [ { status: 'fulfilled', value: 'Success' }, { status: 'rejected', reason: 'Failed' } ]
});

Promise.all() 不同,Promise.allSettled() 在失败时不会短路。它会等待所有的 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 { :  } = ('node:timers/promises');

const  = (2000).(() => 'Task 1 done');
const  = (1000).(() => 'Task 2 done');

.([, ]).( => {
  .(); // '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 { :  } = ('node:timers/promises');

const  = (2000).(() => 'API 1 success');
const  = (1000).(() => 'API 2 success');
const  = (1500).(() => 'API 3 success');

.([, , ])
  .( => {
    .(); // 'API 2 success' (since it resolves first)
  })
  .( => {
    .('All promises rejected:', );
  });

Promise.reject()Promise.resolve()

🌐 Promise.reject() and Promise.resolve()

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

🌐 These methods create a rejected or resolved Promise directly.

.('Resolved immediately').( => {
  .(); // 'Resolved immediately'
});

Promise.try()

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

这对于以一致的方式启动 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 () {
  if (.() > 0.5) {
    throw new ('Oops, something went wrong!');
  }
  return 'Success!';
}

.()
  .( => {
    .('Result:', );
  })
  .( => {
    .('Caught error:', .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 及其相关的 resolve 和 reject 函数,并将它们以一个方便的对象返回。例如,当你需要创建一个 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 { , ,  } = .();

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

.( => {
  .('Success:', );
});

在这个例子中,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 过程中发生的任何错误或拒绝情况。
myPromise
  .then( => .())
  .catch( => .()) // Handles the rejection
  .finally( => .('Promise completed')); // Runs regardless of promise resolution
  • 或者,在使用 async/await 时,你可以使用 try/catch 块来捕获和处理错误。
async function () {
  try {
    const  = await myPromise;
    .();
  } catch () {
    .(); // Handles any errors
  } finally {
    // This code is executed regardless of failure
    .('performTask() completed');
  }
}

();

在事件循环中调度任务

🌐 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 解析以及其他异步操作等任务,它们的优先级高于常规任务。

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

.('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() 用于调度一个回调函数,在当前操作完成后立即执行。这在你希望回调尽快执行但仍在当前执行上下文之后的情况下非常有用。

.(() => {
  .('Next tick callback');
});

.('Synchronous task executed');

setImmediate()

setImmediate() 会安排一个回调在 Node.js 事件循环的检查阶段执行,该阶段在轮询阶段之后运行,而轮询阶段处理大多数 I/O 回调。

(() => {
  .('Immediate callback');
});

.('Synchronous task executed');

何时使用每种

🌐 When to Use Each

  • 对于需要在当前脚本执行完后、任何 I/O 或定时器回调之前立即运行的任务,通常用于 Promise 的解决方案,请使用 queueMicrotask()
  • 对于应该在任何 I/O 事件之前执行的任务,可以使用 process.nextTick(),这通常对延迟操作或同步处理错误非常有用。
  • 对于应该在轮询阶段之后运行、且大多数 I/O 回调已经被处理的任务,请使用 setImmediate()

因为这些任务在当前的同步流程之外执行,因此这些回调中的未捕获异常不会被周围的 try/catch 块捕获,如果没有适当处理(例如,通过给 Promise 添加 .catch() 或使用全局错误处理程序如 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.