行业知识
【JavaScript】async function和await的介绍和使用
简言
async/await是现在常用的一套处理异步函数的方式,更确切的是它们是用来处理Promise的方式。
async/await 的目的在于简化使用基于 promise 的 API 时所需的语法。
异步函数(async function)
async function 声明创建一个绑定到给定名称的异步函数。函数体内允许使用 await 关键字,这使得我们可以更简洁地编写基于 promise 的异步代码,并且避免了显式地配置 promise 链的需要。
上面sync指的就是异步函数。
有两种方式可以创建异步函数:
- async function 函数声明 — 创建声明一个异步函数
- async function 关键字表达式 — 创建一个异步匿名函数并赋给一个变量(也可以使用IIFE,立即执行函数)。
async function foo() { return 1;}const one = async function(){}(async function(){})()异步函数和普通函数区别:
- 异步函数可以处理promise。
- 每次调用异步函数时,都会返回一个新的 Promise 对象,该对象将会被解决为异步函数的返回值,或者被拒绝为异步函数中未捕获的异常。
- 异步函数可以包含零个或者多个 await 表达式。await 表达式通过暂停执行使返回 promise 的函数表现得像同步函数一样,直到返回的 promise 被兑现或拒绝。返回的 promise 的解决值会被当作该 await 表达式的返回值使用 async/await 关键字就可以使用普通的 try/catch 代码块捕获异步代码中的错误。
async function foo() { await 1;}相当于:
function foo() { return Promise.resolve(1).then(() => undefined);}await
await 操作符用于等待一个 Promise 兑现并获取它兑现之后的值。它只能在异步函数或者模块顶层中使用。
await expression;
expression为要等待的 Promise 实例,Thenable 对象,或任意类型的值。
当一个 Promise 被传递给 await 操作符,await 将等待该 Promise 兑现,并在兑现后返回该 Promise 兑现的值。
function resolveAfter2Seconds(x) { return new Promise((resolve) => { setTimeout(() => { resolve(x); }, 2000); });}async function f1() { let x = await resolveAfter2Seconds(10); console.log(x); // 10}f1();若表达式的值不是 Promise,await 会把该值转换为已兑现的 Promise,然后返回其结果。
async function f4() { const y = await Promise.resolve(10); console.log(y); //10 const obj = {}; console.log((await Promise.resolve(obj)) === obj); // true}f4();在模块的顶层,你可以单独使用关键字 await(异步函数的外面)。也就是说一个模块如果包含用了 await 的子模块,该模块就会等待该子模块,这一过程并不会阻塞其他子模块。
// fetch requestconst colors = fetch("../data/colors.json").then((response) => response.json());export default await colors;任何文件只要导入这个模块,后面的代码就会等待,直到 fetch 完成。
async / await 组合
单个使用体现不出来它们的强大,组合使用简化了使用基于 promise 的 API 时所需的语法。
示例:
async function f3() { const y = Promise.resolve(10); const x = await y; console.log(x); // 10 const z = await 20; console.log(z); // 20 const obj = {}; console.log((await obj) === obj); // true}console.log(f3());工作过程
每个 await 表达式之后的代码可以被认为存在于 .then 回调中。通过这种方式,可以通过函数的每个可重入步骤来逐步构建 promise 链。而返回值构成了链中的最后一个环。
示例:
async function foo() { const result1 = await new Promise((resolve) => setTimeout(() => resolve("1")), ); const result2 = await new Promise((resolve) => setTimeout(() => resolve("2")), );}foo();- foo 函数的第一行将会同步执行,其中 await 配置了待定的 promise。然后 foo 的进程将被暂停,并将控制权交还给调用 foo 的函数。
- 一段时间后,当第一个 promise 被兑现或拒绝的时候,控制权将重新回到 foo 内。在这里 1 被赋值给 result1。程序继续执行,并计算第二个 await 表达式。同样的,foo 的进程将被暂停,并交出控制权。
- 一段时间后,当第二个 promise 被兑现或拒绝的时候,控制权将重新回到 foo。在这里 2 被赋值给 result2。程序继续执行到返回表达式(如果有的话)。默认的返回值 undefined 将作为当前 promise 的兑现值被返回。
promise 链不是一次就构建好的,相反,promise 链是随着控制权依次在异步函数中交出并返回而分阶段构建的。promise是微任务的一种,所以这里体现了事件循环。
从顶层代码直到(并包括)第一个 await 表达式(如果有的话)都是同步运行的。
function resolveAfter2Seconds() { console.log("开始较慢兑现的 promise"); return new Promise((resolve) => { setTimeout(() => { resolve("slow"); console.log("较慢兑现的 promise 完成了"); }, 2000); });}function resolveAfter1Second() { console.log("开始较快兑现的 promise"); return new Promise((resolve) => { setTimeout(() => { resolve("fast"); console.log("较快兑现的 promise 完成了"); }, 1000); });}async function sequentialStart() { console.log("== sequentialStart 开始 =="); // 1. 启动一个计时器,并在计时器完成后打印结果 const slow = resolveAfter2Seconds(); console.log(await slow); // 2. 等待前一个计时器完成后,启动下一个计时器 const fast = resolveAfter1Second(); console.log(await fast); console.log("== sequentialStart 结束 ==");}async function sequentialWait() { console.log("== sequentialWait 开始 =="); // 1. 启动两个计时器,互不等待 const slow = resolveAfter2Seconds(); const fast = resolveAfter1Second(); // 2. 等待较慢的计时器完成后,打印结果 console.log(await slow); // 3. 等待较快的计时器完成后,打印结果 console.log(await fast); console.log("== sequentialWait 结束 ==");}sequentialWait()执行2秒,sequentialStart()执行3秒但是它们的结果顺序却相同。
结语
有人说async函数是生成器函数的语法糖,仁者见仁,智者见智。
async函数/await语句和生成器函数(GeneratorFunction)/yield 工作方式比较相似,但两者侧重点和解决问题方向不同,async/await很简单直接,目的在于简化使用基于 promise 的 API 时所需的语法,GeneratorFunction侧重控制函数的执行。
个人认为async函数不是生成器函数的语法糖,使用生成器可以模仿实现async函数工作,仅此而已。
下一篇:揭秘连接重置背后的故事