JavaScript async/await 见解
什么是 async/await
async/await
是 JavaScript 中处理异步操作的一种新语法,是在 ES8(ECMAScript 2017) 中引入的。它被设计为 Promise 的语法糖(最甜的那种),用来让异步代码写起来看起来更像同步代码。这种看似“同步”的风格让代码更加简洁、可读,同时也更容易维护和调试。
简单来说,async 用于定义一个异步函数,而 await 用于等待一个异步操作完成。async/await
结合在一起,给开发者提供了一种更直观的方式来处理异步逻辑,而无需手动操作 Promise 的 then
、catch
或链式调用。你可以把它想象成一种让异步代码看起来更像同步代码的魔法。async 是用来声明一个函数是异步的,而 await 则是用来等待一个异步操作的完成。
为什么会出现 async/await
在JavaScript 关于Promise 讲到为了解决回调地狱这个问题, ES6 推出了Promise
对象,虽然 Promise 比回调函数好很多,解决了回调地狱的问题,但写起来还是有点复杂,尤其是当有很多异步操作需要按顺序执行时。async/await 的出现,就是为了让我们写异步代码更加简单、直观,就像是在写同步代码一样。
async/await 工作方式
1. async
- 用于定义一个异步函数。
- 在这个函数内部可以使用 await。
- 这个函数会默认返回一个 Promise。
2. await
- 用于等待一个 Promise 完成,并获取其结果。
- 会暂停当前代码的执行,直到 Promise 处理完成为止。
- 只能在 async 函数中使用。
举个例子,假设你需要先从服务器获取用户信息,然后根据用户信息获取用户的订单数据,最后根据订单数据获取订单详情。用传统的回调函数或 Promise 链来写,代码可能会非常复杂和难以理解。而用 async/await,你可以这样写:
async function getUserOrderDetails() {
try {
const user = await fetchUser();
const orders = await fetchOrders(user.id);
const orderDetails = await fetchOrderDetails(orders[0].id);
return orderDetails;
} catch (error) {
console.error('获取订单详情失败:', error);
}
}
再比如之前的Promise链式
getUser()
.then((user) => getOrders(user))
.then((orders) => processOrders(orders))
.then((result) => console.log("处理结果:", result))
.catch((err) => console.error(err));
就可以变成
async function fetchData() {
try {
const user = await getUser();
const orders = await getOrders(user);
const result = await processOrders(orders);
console.log("处理结果:", result);
} catch (error) {
console.error("发生错误:", error);
}
}
fetchData().catch((err) => console.error("出错了:", err));
这个代码的逻辑和同步代码看起来几乎一模一样,但实际上处理的是异步操作。await 会让代码看起来按顺序执行,而不是以异步的回调方式跳来跳去。 虽然两者功能相同,但 async/await 写起来更贴近人的思维方式。
await 会造成等待阻塞吗
首先,await 确实会让代码看起来像是“等待”了某个操作完成,但它并不会阻塞整个程序或者界面的运行。你可以把它想象成你在厨房里烤面包时的情景。
假设你在烤面包,烤面包机需要 3 分钟才能把面包烤好。在这 3 分钟里,你有两种选择:
傻傻地等:一直盯着烤面包机,啥也不干,等它烤好。这就像是一些编程语言中的“阻塞”操作,整个程序会停下来,直到操作完成。 做点别的事:在等待面包烤好的同时,你可以去做点别的事,比如洗个碗、扫个地,或者准备点别的早餐食材。一旦面包烤好了,烤面包机会“叮”一声提醒你,你再去拿面包。这种方式就是 await 的工作模式——它让你在等待异步操作完成的同时,可以去做其他事情,不会让你整个程序停下来。 在 JavaScript 中,当你在一个 async 函数里使用 await,它会暂停这个 async 函数的执行,等待后面的 Promise 完成。但是,它不会阻塞整个浏览器或者 Node.js 程序的运行。浏览器或者 Node.js 可以在等待的过程中继续执行其他代码,或者响应用户的操作。
await 等的到底是什么
await 主要是在等待一个 Promise 对象的完成。Promise 就像是烤面包机烤面包的过程,它代表了一个异步操作,最终会有一个结果(面包烤好了)或者一个错误(面包烤焦了)。
等待 Promise 完成:当 await 后面跟着一个 Promise 时,它会等待这个 Promise 被解决(resolved)或者被拒绝(rejected)。如果 Promise 被解决了,await 表达式的结果就是 Promise 解决的值;如果 Promise 被拒绝了,await 会抛出一个错误,你可以用 try/catch 来捕获这个错误。
const result = await someAsyncFunction(); // 等待 Promise 完成
console.log(result); // 输出 Promise 的成功值
等待其他值:如果 await 后面跟着的不是一个 Promise,而是一个普通的值(比如一个字符串、数字或者对象),那么 await 就会直接返回这个值,不会有任何的等待。这种情况有点像是你在等待烤面包的时候,突然发现旁边已经有一块现成的面包,那你就不需要等待了,直接拿过来用就行了。
const result = await 42; // 42 不是 Promise,直接返回
console.log(result); // 输出 42
如果 await 后面是一个函数,但没有返回 Promise 它也会直接执行并返回这个函数的结果,而不会等待异步完成。
function test() {
return "不是 Promise";
}
const result = await test(); // 不会等待异步,直接拿返回值
console.log(result); // 输出 "不是 Promise"
为了更直观一点,我们来看一段代码和它的执行流程:
async function example() {
console.log("开始");
const value1 = await new Promise((resolve) => setTimeout(() => resolve("值1"), 1000));
console.log("拿到第一个值:", value1);
const value2 = await new Promise((resolve) => setTimeout(() => resolve("值2"), 2000));
console.log("拿到第二个值:", value2);
console.log("结束");
}
example();
console.log("这个会先打印");
流程是这样的:
- 函数
example
一开始会立即执行,打印出“开始”。 - 遇到第一个
await
时,它会暂停函数的执行,等setTimeout
里的Promise
完成(等了 1 秒)。 其他代码,比如 console.log("这个会先打印"),会继续执行,不会被阻塞。 Promise
完成后,await
拿到结果“值1”,然后继续往下执行,打印“拿到第一个值: 值1”。- 再遇到第二个
await
,又会暂停函数执行,再等 2 秒。同样的,其他任务可以继续运行。 - 等 2 秒后,
await
拿到结果“值2”,然后继续往下执行,打印“拿到第二个值: 值2”。 - 最后打印“结束”。
await 的核心点总结
暂停但不阻塞: await 会暂停 async 函数内部的执行,但不会阻塞整个程序。这就是为什么在上面的例子中,console.log("这个会先打印") 能先输出。
只影响当前的 async 函数: await 只暂停自己所在的那个异步函数,它不会影响到外面的代码。换句话说,外面的代码会继续跑,不受它的影响。
等待的是 Promise 的完成状态:
- 如果 Promise 成功了,就返回结果。
- 如果 Promise 失败了,会抛出错误,这时需要用 try...catch 来捕获。
同步的代码不受影响: 如果 await 后面是同步代码或非 Promise 的内容,根本不会等待,而是直接继续执行。
举个🌰
async function waitExample() {
console.log("1. 开始执行");
const result = await new Promise((resolve) => {
setTimeout(() => resolve("异步结果"), 2000);
});
console.log("2. 拿到结果:", result);
}
waitExample();
console.log("3. 这个代码会先跑");
执行顺序是:
waitExample
开始执行,打印“1. 开始执行”。await
暂停waitExample
的执行,等 2 秒后Promise
完成。- 在等待的同时,console.log("3. 这个代码会先跑") 立即执行。
- 2 秒后,
Promise
完成,拿到结果“异步结果”,继续执行waitExample
,打印“2. 拿到结果: 异步结果”。
处理async/await异常抛出
无需 try-catch 即可轻松处理错误