异步编程的 callback hell 到 async/await 演进

FreeGuideOnline 最新 2026-07-10

javascript console.log('开始');

setTimeout(() => { console.log('2 秒后执行的回调'); }, 2000);

console.log('结束'); // 输出顺序:开始 → 结束 → 2 秒后执行的回调


在 Node.js 中,读取文件也经常用回调:
```javascript
const fs = require('fs');
fs.readFile('data.txt', 'utf-8', (err, data) => {
  if (err) {
    console.error('读取失败', err);
    return;
  }
  console.log('文件内容:', data);
});

回调模式简单直接,但如果多个异步操作需要按顺序执行,并且后一个依赖前一个的结果,代码就会迅速失控。

回调地狱(Callback Hell)

假设需要完成三个连续操作:读取文件 → 根据内容查询数据库 → 将结果写入新文件。使用回调会写成这样:

fs.readFile('config.json', 'utf-8', (err, configStr) => {
  if (err) {
    console.error('读配置失败', err);
    return;
  }
  const config = JSON.parse(configStr);
  db.query('SELECT * FROM users WHERE id = ?', [config.userId], (err, users) => {
    if (err) {
      console.error('查询失败', err);
      return;
    }
    const user = users[0];
    fs.writeFile('result.txt', JSON.stringify(user), (err) => {
      if (err) {
        console.error('写入失败', err);
        return;
      }
      console.log('全部完成');
    });
  });
});

这就是经典的回调地狱

  • 代码层层嵌套,形成向右的“金字塔”,可读性极差。
  • 错误处理重复,每个回调都要判断 err,容易遗漏。
  • 流程逻辑不直观,难以维护和调试。

解救者:Promise

ES6 引入了 Promise,专门用来组织异步代码。Promise 表示一个异步操作的最终完成(或失败)及其结果值,它有三种状态:

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)

状态一旦改变就不可逆。通过 .then() 处理成功,.catch() 处理失败,链式调用可以彻底拉平嵌套。

用 Promise 改写上述例子。先将每个异步操作包装成返回 Promise 的函数:

function readFilePromise(path, encoding) {
  return new Promise((resolve, reject) => {
    fs.readFile(path, encoding, (err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });
}

function queryPromise(sql, params) {
  return new Promise((resolve, reject) => {
    db.query(sql, params, (err, results) => {
      if (err) reject(err);
      else resolve(results);
    });
  });
}

function writeFilePromise(path, data) {
  return new Promise((resolve, reject) => {
    fs.writeFile(path, data, (err) => {
      if (err) reject(err);
      else resolve();
    });
  });
}

然后串联调用:

readFilePromise('config.json', 'utf-8')
  .then(configStr => {
    const config = JSON.parse(configStr);
    return queryPromise('SELECT * FROM users WHERE id = ?', [config.userId]);
  })
  .then(users => {
    const user = users[0];
    return writeFilePromise('result.txt', JSON.stringify(user));
  })
  .then(() => {
    console.log('全部完成');
  })
  .catch(err => {
    console.error('流程出错', err);
  });

对比回调地狱,优劣立判:

  • 代码缩进保持在同一层级,逻辑一目了然。
  • 错误统一在最后的 .catch() 处理,避免遗漏。
  • .then() 链可以将异步步骤清晰的串联起来。

更优雅的终极方案:async/await

ES2017 带来了 async/await,它基于 Promise,但提供了近乎同步代码的书写体验,彻底消灭了回调痕迹。

  • async:声明一个异步函数,函数内部可以使用 await,并自动将返回值包装成 Promise。
  • await:等待一个 Promise 完成(fulfilled 或 rejected),并取出其结果。只能在 async 函数内部使用。如果 Promise 被 reject,会抛出异常,可以用 try...catch 捕获。

重写相同流程,代码变得非常直白:

async function processData() {
  try {
    const configStr = await readFilePromise('config.json', 'utf-8');
    const config = JSON.parse(configStr);
    const users = await queryPromise('SELECT * FROM users WHERE id = ?', [config.userId]);
    const user = users[0];
    await writeFilePromise('result.txt', JSON.stringify(user));
    console.log('全部完成');
  } catch (err) {
    console.error('流程出错', err);
  }
}

processData();