一篇文章搞定 async/await ,再用起来得心应手
async /await 是什么?
async/await 是 ECMAScript 2017 引入的 异步编程解决方案。
Promise 也是 异步编程解决方案,那为什么又要引入 async/await呢?
ECMA-262 中 是这么说的:
- 用于改进异步编程体验。它们提供了更简洁的语法来定义返回 Promise 的函数
- 允许开发者编写像同步代码一样的风格,而不必手动管理 Promise 的链式调用
- 主要优势在于简化了异步代码的编写和理解,使得处理异步操作更加直观和容易
async/await 怎么使用呢?
先将其分解:
async部分 和await部分async 关键字开头 代表这是一个异步函数, 返回一个 Promise 对象,函数内部 return 语句返回的值,相当于执行了 Promise.then(res) 后回调参数的值。
例如:
async function foo () { return "Hello World"; } foo().then((res) => console.log(res)) // "Hello World"await 关键字 后面可接 Promise 对象 、任何具有
then()方法的对象、任何原始值、异步函数、Generator 对象注意,await 不能单独出现,必须和async 一起出现。
接 Promise 对象示例:
await会等待该 Promise 对象状态变为 resolved(已完成)或 rejected(已拒绝),然后返回 Promise 的值(如果状态是 resolved)或抛出异常(如果状态是 rejected)// 模拟promise function promiseFunc () { return new Promise((resolve, reject) => { let rand = Math.random(); rand > 0.5 ? resolve(rand) : reject(rand); }) } // 直接接Promise 对象 async function foo () { return await promiseFunc(); } foo().then( v => console.log("被resolve了",v), e => console.log("被reject了", e) )接任何具有then()方法的对象示例:具有类似 Promise 的行为,但并不是真正的 Promise。
class MyThenable { then(resolve, reject) { // 执行异步操作,最终调用 resolve 或 reject resolve("Hello"); } } async function foo () { return await new MyThenable(); } foo().then( v => console.log("被resolve了",v), e => console.log("被reject了", e) ) // 被resolve了 Hello接任何原始值示例:当
await后面跟随一个原始值时,它会立即将该值解析为已完成的 Promise,并返回该值本身。async function foo () { return await 22; // 等同于 Promise.resolve(22) } foo().then( v => console.log("被resolve了",v), e => console.log("被reject了", e) ) // 被resolve了 22接异步函数示例 : 在异步函数内部,可以使用
await关键字等待另一个异步函数的执行结果function promiseFunc () { return new Promise((resolve, reject) => { let rand = Math.random(); rand > 0.5 ? resolve(rand) : reject(rand); }) } async function foo () { return await promiseFunc(); } async function bar () { return await foo(); } bar().then( v => console.log("被resolve了",v), e => console.log("被reject了", e) ) // 被reject了 0.23166145735495758
当async 函数里面有多个await的时候是怎么样的呢?
function promiseFunc () {
return new Promise((resolve, reject) => {
let rand = Math.random();
rand > 0.5 ? resolve(rand) : reject(rand);
})
}
async function foo () {
const rst1 = await promiseFunc();
const rst2 = await promiseFunc();
return {
rst1,
rst2
}
}
foo().then(res=> console.log(res)).catch(err => console.log(err));
// 情况一
// [0.9080063738596382, 0.5729561832964878]
// 情况二
// reject: 0.13659417848799338
// 任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行.
// 如果想要保证后面的await 可以正常运行,可以使用 try catch
async function bar() {
let rst = 0;
try {
rst = await promiseFunc();
} catch (e) {
console.log("e",e)
}
return await (`哈哈:${rst}`);
}
bar().then((res) => console.log(res)).catch((err) => console.log(err));
// 当promiseFunc抛出异常时的结果是:后面的await正常输出了
// e reject: 0.33490751710711564
// 哈哈:0
// 当然如果不喜欢写try catch ,还可以这么写:
async function f() {
let rst = await promiseFunc().catch(function (err) {
console.log(err);
});
return await (`哈哈:${rst}`)
}
// 当promiseFunc抛出异常时的结果如下:
// reject: 0.19910784450616492
// 哈哈:undefined
// 当resolve的时候结果如下:
// 哈哈:0.6107829192741085
看看现实使用中的场景?
- fetch 请求数据,当出现有先后顺序的异步操作时很方便的用同步的写法
import mjFetch from 'mj-fetch'; // 获取token的fetch请求,实际返回的是一个Promise对象 const fetchToken = (data) => { return mjFetch.get('/token', data).catch((err) => console.log(err)); } // 获取用户信息的fetch请求,实际返回的是一个Promise对象 const fetchUserInfo = (data) => { return mjFetch.get('/user/info/yth', data).catch(err => window.alert("登录失败,请重新进入")); } // 定义获取用户信息的异步方法 const getUserInfo = async () => { const token = await fetchToken(); const rst = await fetchUserInfo({ token }); } // ========================== 如果不用 async/await ,而用 promise的方式,你可能要这么写 const getUserInfoPromise = () => { fetchToken().then((token) => { const rst = await fetchUserInfo({ token }); // 如果依赖多了,就可能变成回调地狱了 }) }
- node中常见的async /await 用法,举个例子:文件目录监听变化文件并根据配置规则读取文件内容
const { readFile } = require("node:fs/promise"); const watch = require("node-watch"); const monitor = async (folder) => { const asyncFunc1 = readFile('SETTING_ONE.txt', { encoding: 'utf-8' }); const asyncFunc2 = readFile('SETTING_TWO.txt', { encoding: 'utf-8' }); const [cfg1, cfg2] = await Promise.all([asyncFunc1, asyncFunc2]); // 并行获取配置内容 watch(folder, (evt, name) => { if (evt ==== 'update') { // readAlgc(name, cfg1, cfg2) 根据配置规则读取文件内容 } }) } // 开启监听 monitor('xx文件夹');
我们都知道Promise.resolve 、Promise.reject 是微任务,那么 async/await 中的 await 表达式是微任务吗?
先简单的介绍一下微任务、宏任务:
我们所知道的异步任务分为宏任务和微任务,微任务会被放入微任务队列,宏任务会被放入宏任务队列,在当前执行栈中,当执行栈为空时,会先检查当前的微任务队列,将所有微任务执行完毕后,再去执行下一个宏任务。
那么这些队列是什么,执行栈又是什么? 这就涉及到事件循环、调用堆栈和任务队列了。
事件循环:js中事件循环主要是用来处理js单线程下的异步编程问题。当执行一段js代码时,代码运行时创建的执行上下文,会被推入调用栈中 ,如果是异步任务,会移交给相应web api 去处理,并将处理完的回调事件,放入消息队列中,当主线程处理完调用栈的代码,事件循环就会不断地从消息队列中获取事件,并推入调用栈中执行。这个过程不断地循环,这种运行机制就是 Event Loop机制。
调用堆栈:存放的是执行上下文,先进后出。
任务队列:存放的是异步操作的回调函数,先进先出。
async/await中的await关键字会创建一个微任务(microtask),当执行到await关键字时,它会暂停 async 函数的执行,并等待 Promise 对象的状态变更。你理解了吗,来看看一道比较经典的面试题:
async function async1() { console.log('async1 start'); await async2(); console.log('async1 end'); } async function async2() { console.log('async2'); } setTimeout(function() { console.log('settimeout'); }); async1(); new Promise(function(resolve) { console.log('promise1'); resolve(); }).then(function() { console.log('promise2'); }) // 从上往下看,当前执行栈 [global] // 遇到setTimeout ,放入宏任务队列 // 执行async1(), 入栈 [global,async1()] 直接执行'async1 start', // 遇到await async2 创建微任务并放入微任务队列async2-p ,执行 'async2', 因为没有等到async2执行返回结果,所以不执行后续的,直接跳出 async1 函数。 // 接着遇到 new Promise,直接执行'promise1', resolve()放入微任务队列 // 当前任务执行完毕,开始去执行微任务队列中的任务 // async2-p执行,接着运行它的下一行代码 'async1 end' // resolve()执行,then回调触发 'promise2' // 微任务队列执行完毕,开始执行宏队列 // 'settimeout' // 所以最终的顺序是:"async1 start" 、"async2"、"promise1"、"async1 end" 、"promise2"、"settimeout" // 主线程:"async1 start" 、"async2"、"promise1" // 宏队列: [setTime cb()] // 微队列: [async2-p, resolve()]
Async /Await 的各种声明方式参考:
一些术语说明:
AsyncFunctionBody:异步函数的函数体,与普通函数体类似,但允许在其中使用 await 表达式.
AwaitExpression:异步函数内部使用的 await 表达式
异步函数声明(AsyncFunctionDeclaration):
async function name () {AsyncFunctionBody}
// 匿名函数声明
async function () {AsyncFunctionBody}
异步函数表达式(AsyncFunctionExpression):
const asyncFunc = async function () { AsyncFunctionBody }
异步方法的定义形式(AsyncMethod),用于在类(Class)中定义异步方法。
async ClassElementName (...) { AsyncFunctionBody }
异步箭头函数定义(Async Arrow Function Definitions)
const asyncArrowFunc = async () => {}
Await 解析规则:
await xxPromise; // await 后面接一个Promise对象的表达式 (任何返回 Promise 对象的函数或表达式)
// await 会暂停异步函数的执行,等待 Promise 对象的解析结果。
// 在 AsyncFunctionBody 中
async function asyncFunction() {
await someAsyncOperation();
}
// 在异步函数定义的形参中
async function asyncWithParams(awaitParam) {
await awaitParam;
}
// 在模块(Module)中
export async function asyncModuleFunction() {
await someAsyncModuleOperation();
}