网页里很多事情是「等」出来的:请求接口要等服务器响应,读文件要等磁盘。异步就是让程序在等待时别闲着。这篇讲清楚 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,异步代码就再也不会绕成毛线团了。