网页里很多事情是「等」出来的:请求接口要等服务器响应,读文件要等磁盘。异步就是让程序在等待时别闲着。这篇讲清楚 Promise 到底是什么,以及怎么用 async/await 把异步代码写得像同步一样顺。

为什么需要 Promise

先看一个模拟的异步操作——用 setTimeout 代替网络请求:

function fetchUser(name, callback) {
  setTimeout(() => {
    callback({ name: name, age: 20 });
  }, 1000);
}

fetchUser("小明", (user) => {
  console.log(user); // 1 秒后输出:{ name: '小明', age: 20 }
});

这种「传回调」的写法一旦嵌套两层以上就很难读,也就是常说的回调地狱。Promise 就是为了治这个病出现的。

Promise 的三种状态

Promise 是一个「承诺」对象:它代表一个未来才会有结果的操作,有三种状态——pending(进行中)、fulfilled(成功)、rejected(失败)。状态一旦改变就不会再变。

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("任务完成"); // 调 resolve 变成功,调 reject 变失败
  }, 1000);
});

p.then((result) => {
  console.log(result); // 1 秒后输出:任务完成
}).catch((err) => {
  console.log("出错了:" + err);
});

then 接住成功的结果,catch 接住失败的原因。链式调用是 Promise 最大的好处——上一步的结果直接传给下一步:

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

wait(500)
  .then(() => console.log("第一步完成"))
  .then(() => wait(500))
  .then(() => console.log("第二步完成")); // 依次输出两行

async/await:异步写出同步的感觉

async 放在函数前面,表示这个函数返回 Promise;await 用来等一个 Promise 出结果,代码看起来就像同步执行:

function wait(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

async function main() {
  console.log("开始");
  await wait(1000);
  console.log("1 秒后才执行这行"); // await 之后的代码会等它结束
  return "main 完成";
}

main().then((msg) => console.log(msg)); // 输出:main 完成

错误处理用 try/catch,和同步代码长得一模一样:

async function load() {
  try {
    const res = await fetch("https://httpbin.org/status/404");
    if (!res.ok) {
      throw new Error("请求失败,状态码 " + res.status);
    }
  } catch (err) {
    console.log("捕获错误:" + err.message); // 输出:捕获错误:请求失败,状态码 404
  }
}
load();

并行等待:Promise.all

多个互不依赖的任务,挨个 await 太浪费时间,用 Promise.all 一起等:

async function main() {
  const results = await Promise.all([wait(800), wait(800)]);
  console.log("全部完成", results); // 约 0.8 秒输出,而不是 1.6 秒
}
main();

小结

  • Promise 代表一个未来才有结果的操作,状态只有 pending、fulfilled、rejected 三种
  • then 接成功,catch 接失败,可以链式调用
  • async 函数返回 Promise,await 等待结果,让异步代码保持直线阅读
  • 异步错误用 try/catch 捕获
  • 无依赖的任务用 Promise.all 并行等待,省时间

把回调换成 Promise,再把 Promise 写成 async/await,异步代码就再也不会绕成毛线团了。