返回

深入理解 Node.js 异步:从回调到 async/await

前端

上周,我写了一篇题为“JS 异步编程的浅思”的文章,其中介绍了如何将反人类的异步回调演化到带有 async/await 的同步/顺序执行,这让我对异步编程的理解有了一个质的飞跃,达到了“异步编程的最高境界,就是根本不用关心它是不是异步”的水平。

Node.js 主要分为四大部分,Node Standard Library、V8 JavaScript 引擎、libuv 事件循环和 C++ 核心库。其中,libuv 事件循环是 Node.js 的核心,它是基于事件驱动的非阻塞 I/O 模型,允许 Node.js 在单线程中并发处理多个请求。

在 Node.js 中,异步编程是通过回调函数来实现的。回调函数是一个在异步操作完成后被调用的函数。例如,以下代码使用回调函数来读取一个文件:

fs.readFile('file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }

  console.log(data);
});

在上面的代码中,fs.readFile() 方法是一个异步操作,它会将文件的内容作为第二个参数传递给回调函数。当文件读取完成后,回调函数就会被调用。

回调函数的缺点是,当代码中有多个异步操作时,代码就会变得难以阅读和维护。为了解决这个问题,Node.js 引入了 Promise 对象。Promise 对象表示一个异步操作的结果,它可以被链式调用,从而使代码更易于阅读和维护。

以下代码使用 Promise 对象来读取一个文件:

fs.readFile('file.txt', 'utf8')
  .then((data) => {
    console.log(data);
  })
  .catch((err) => {
    console.error(err);
  });

在上面的代码中,fs.readFile() 方法返回一个 Promise 对象。当文件读取完成后,Promise 对象就会被解析,并调用 then() 方法传递给回调函数。如果文件读取失败,Promise 对象就会被拒绝,并调用 catch() 方法传递给回调函数。

Generator 函数是另一种处理异步操作的工具。Generator 函数是一种特殊的函数,它可以暂停执行,并在稍后继续执行。Generator 函数可以与 yield 关键字一起使用,yield 关键字可以暂停函数的执行,并将控制权交还给调用者。

以下代码使用 Generator 函数来读取一个文件:

function readFile(filename) {
  const data = yield fs.readFile(filename, 'utf8');
  console.log(data);
}

const generator = readFile('file.txt');
generator.next();

在上面的代码中,readFile() 函数是一个 Generator 函数。当调用 generator.next() 方法时,Generator 函数就会开始执行。当 Generator 函数执行到 yield fs.readFile(filename, 'utf8') 语句时,它就会暂停执行,并将控制权交还给调用者。

调用者可以继续执行其他任务,当文件读取完成后,Generator 函数就会继续执行,并调用 console.log(data) 语句将文件的内容打印到控制台。

async/await 语法是处理异步操作的最新工具。async/await 语法可以将异步操作写成同步代码,从而使代码更易于阅读和维护。

以下代码使用 async/await 语法来读取一个文件:

async function readFile(filename) {
  const data = await fs.readFile(filename, 'utf8');
  console.log(data);
}

readFile('file.txt');

在上面的代码中,readFile() 函数是一个 async 函数。当调用 readFile() 函数时,函数就会开始执行。当函数执行到 await fs.readFile(filename, 'utf8') 语句时,函数就会暂停执行,并等待文件读取完成。

当文件读取完成后,函数就会继续执行,并调用 console.log(data) 语句将文件的内容打印到控制台。

async/await 语法是处理异步操作的最佳工具。它可以将异步操作写成同步代码,从而使代码更易于阅读和维护。