JS中的async和await

概述

async/await是ES2017引入的特性专门用来简化异步编程。async和await是JavaScript中用于处理异步操作的关键字,它们针对前端异步编程,提供了更清晰、更简洁的语法。本文将详细介绍 async/await 的使用、及个人对其的一些理解。

async/await简介

async/await 是 JavaScript 中用于处理异步代码的语法糖, async用于定义一个异步函数,而 await用于暂停异步函数的执行,直到Promise的状态变成resolved或rejected。

  • async:定义一个异步函数,该函数会返回一个 Promise。即使函数内部没有显式返回 Promise,async函数也会隐式将返回值包装成 Promise。

  • await:只能在async函数中使用,用于等待Promise返回的状态。它会暂停函数的执行,直到Promise完成,并返回Promise的结果。

1
2
3
4
5
6
7
//async函数也会隐式将返回值包装成 Promise可以理解为如下代码:
async function foo() {
return 1;
}
function foo() {
return Promise.resolve(1);
}

但需要注意的是,虽然会隐式将返回值包装成 Promise但是它和正常显式返回Promise并不是等价的,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const p = new Promise((res, rej) => {
res(1);
});

async function asyncReturn() {
return p;
}

function basicReturn() {
return Promise.resolve(p);
}

console.log(p === basicReturn()); // true
console.log(p === asyncReturn()); // false

async/await使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 定义一个返回 Promise 的异步函数
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("异步数据获取成功!");
}, 2000);
});
}

// 使用 async/await 来处理异步函数
async function getData() {
console.log("开始获取数据...");
try {
const result = await fetchData();
console.log(result); // 输出 "数据加载成功!"
} catch (error) {
console.error("获取数据失败:", error);
}
}

getData();

以上,我们模拟了一个异步返回数据的函数fetchData,并且在getData中使用了async/await来处理这个异步函数。这样可以在getData感觉到是同步顺序的处理数据结果。

async/await中的异常处理

async/await 中可以使用 try…catch 来处理异步操作中的错误。

1
2
3
4
5
6
7
8
9
async function getData() {
try {
const result = await fetchData();
console.log(result);
} catch (error) {
console.error("错误:", error);
}
}

多个异步任务的顺行和并行

当我们需要等待多个异步任务都执行完成的时候执行某些操作的话,我们通常用Promise.all() 或 Promise.allSettled() 来处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 顺序执行
async function sequential() {
const result1 = await fetchData();
const result2 = await fetchData();
console.log(result1, result2); // 顺序执行,先后等待两个 fetchData 的结果
}

// 并行执行
async function parallel() {
const [result1, result2] = await Promise.all([fetchData(), fetchData()]);
console.log(result1, result2); // 并行执行,同时等待两个 fetchData 的结果
}
// 并行也可以使用Promise.allSettled()
async function parallel() {
const [result1, result2] = await Promise.allSettled([fetchData(), fetchData()]);
console.log(result1, result2); // 并行执行,同时等待两个 fetchData 的结果
}

在你有多个不依赖于彼此成功完成的异步任务时,或者你总是想知道每个 promise 的结果时,使用 Promise.allSettled() 。

相比之下,如果任务相互依赖,或者如果你想在任何 promise 被拒绝时立即拒绝,Promise.all() 返回的 Promise 可能更合适。

其他

其实用法就是如上非常简单。

在查阅资料的时候,在阮一峰老师的博客中写到:ES2017 标准引入了 async 函数,使得异步操作变得更加方便。 sync 函数是什么?一句话,它就是 Generator 函数的语法糖。

这句话在我感觉可能不是特别准确。

async/await 不能简单地说是 Generator 的语法糖,它实际上是一种全新的语法和控制流机制。虽然它们在行为上有相似之处(如暂停、恢复函数执行的能力),但 async/await 和 Generator 在设计理念、实现细节和使用方式上有根本性的不同

设计理念和用途的不同

  • Generator:主要用于生成序列、迭代器和控制异步流。
    通过 yield 暂停函数执行,并在外部调用 next() 时恢复执行。
    使用起来略显复杂,需要外部控制其执行流。

  • async/await: 专为简化异步代码的书写而设计,使其看起来像同步代码。
    await 会自动等待一个 Promise 解决,并返回结果;不需要手动调用 next()。
    语法简洁,易读易维护

实现机制的不同

  • Generator:由 Iterator迭代器驱动,使用 next()、throw() 和 return() 方法控制。
    yield 仅暂停当前执行并返回值,后续的执行需要手动控制。
  • async/await:
    基于 Promise,await 会等待 Promise 解决并返回结果或抛出异常。
    引擎将 async 函数中的代码拆分成多个微任务,挂载到微任务队列中异步执行。

语法和执行上的差异

  • Generator和 async/await 的差异不仅仅是语法,而是从根本上改变了代码的执行控制流。
  • async/await 的执行是自动的,内部实现是将代码拆分成可暂停执行的微任务序列,不需要显式地控制函数的暂停和恢复。

编译器兼容处理

为了在不支持 async/await 的环境中运行,Babel 等编译器会将 async/await 转换为 Generator 和 Promise 结合的代码。这是一些编译器的实现细节。