JavaScript高级--async/await详解
- 首先
async/await
是一种用于处理异步操作的语法糖,在ES2017
被引入,用于更简洁地编写基于Promise
的链式回调代码 - 使用
async/await
主要是避免了传统的回调函数或Promise
链的嵌套 - 既然
async/await
是一种语法糖,那么其本质是什么呢?
以前的异步处理
- 现在有一个
requestApi
函数,用于模拟网络请求,传入什么则返回什么
function requestApi(params) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(params);
}, 1000);
});
}
- 首先提一个需求,当第一次传入
Jimmy
,拿到请求返回的结果为Jimmy
;第二次需要利用第一次获得的结果去发送网络请求,拿到Jimmyaaa
;而第三次再利用第二次的结果发送网络请求,拿到Jimmyaaabbb
params: 'Jimmy' ->> res1: 'Jimmy'
params: res1 + 'aaa' ->> res2: 'Jimmyaaa'
params: res2 + 'bbb' ->> res3: 'Jimmyaaabbb'
- 在使用
async/await
的情况下,代码如下所示
async function fetchData() {
const res1 = await requestApi('Jimmy');
const res2 = await requestApi(res1 + 'aaa');
const res3 = await requestApi(res2 + 'bbb');
console.log(res3); // Jimmyaaabbb
}
fetchData();
- 假如没有
async/await
呢?那就需要Promise
的链式调用了
requestApi('Jimmy').then((res) => {
requestApi(res + 'aaa').then((res) => {
requestApi(res + 'bbb').then((res) => {
console.log(res); // Jimmyaaabbb
});
});
});
生成器结合异步
- 如果使用
Promise
的链式调用,就会造成函数嵌套,形成回调地狱 - 可以通过生成器结合
Promise
来尝试解决这个问题
function* fetchData() {
// 调用next(value1),yield的返回值被res1接收,res1 = 'Jimmy'
const res1 = yield requestApi('Jimmy');
// 调用next(value2),yield的返回值被res2接收,res1 = 'Jimmyaaa'
const res2 = yield requestApi(res1 + 'aaa');
// 调用next(value3),yield的返回值被res3接收,res3 = 'Jimmyaaabbb'
const res3 = yield requestApi(res2 + 'bbb');
// 输出'Jimmyaaabbb'
console.log(res3);
}
const generator = fetchData(); // 首次调用函数不执行,而是返回生成器
generator.next().value.then((value1) => {
// 此时value1为第一个yield语句返回的promise结果,value1 = 'Jimmy'
generator.next(value1).value.then((value2) => {
// 此时value2为第二个yield语句返回的promise结果,value2 = 'Jimmyaaa'
generator.next(value2).value.then((value3) => {
// 此时value3为第三个yield语句返回的promise结果,value3 = 'Jimmyaaabbb'
generator.next(value3);
});
});
});
- 尝试对上面代码进行封装,因为
generator.next()
返回的是对象(如{ value: promise, done: false }
),可以利用其done
值判断来判断数据是否传输完成,结合递归进行优化
// 该函数接收一个生成器函数为参数
function execGenerator(GeneFun) {
const generator = GeneFun();
function exec(res) {
const result = generator.next(res);
if (result.done) return; // 如果result.done为true,则代表生成器函数执行完毕
// 如果result.done为false,则代表生成器函数执行中
result.value.then((res) => {
// 继续调用exec,把下一个yield的值传入,知道result.done为true为止
exec(res);
});
}
exec();
}
- 那么可以将
fetchData
函数传给execGenerator
处理,让其自动执行 - 如果有其他类似的需求,也可以将请求的函数交由
execGenerator
进行处理
// 传入一个生成器函数
execGenerator(function* (){
const res1 = yield requestApi('Jimmy');
const res2 = yield requestApi(res1 + 'aaa');
const res3 = yield requestApi(res2 + 'bbb');
console.log(res3);
});
// 对比async/await
async function fetchData() {
const res1 = await requestApi('Jimmy');
const res2 = await requestApi(res1 + 'aaa');
const res3 = await requestApi(res2 + 'bbb');
console.log(res3);
}
- 以上就是
async/await
语法糖的本质,其底层是生成器函数 - 当一个函数被
async
标记,遇到await
时函数则会等待,直到await
后的代码处理完后才往后执行 - 而生成器函数遇到
yield
语句时,函数也会暂停,待调用next
之后才会继续往后执行 - 也就是说,
async/await
语法糖相当于自动化的生成器函数
async关键字
async
关键字用于声明一个异步函数,它是asynchronous
单词的缩写,表示异步的
async
函数的多种写法
- 直接在普通函数前加上
async
关键字
async function foo() {}
- 箭头函数前加上
async
关键字
const bar = async () => {};
- 类方法前加上
async
关键字
class Foo {
async bar() {}
}
执行流程
- 异步函数的内部代码执行过程和普通的函数是一致的,在没有特殊处理的情况下也是会被同步执行
async function asyncFoo() {
console.log('foo start');
console.log('中间代码执行');
console.log('foo end');
}
console.log('前面代码');
asyncFoo(); // 这里不会异步执行,而是和普通函数一致
console.log('后续代码');
异步函数有返回值时,和普通函数会有区别
- 异步函数的返回值会被包裹到
Promise.resolve
中,所以其返回值一定是个promise
async function asyncBar() {
...
return 123;
}
const promise = asyncBar();
promise.then((res) => console.log('promise exec', res)); // promise exec 123
- 如果异步函数返回
Promise
,那么其本身Promise.resolve
的状态会由返回的Promise
决定
async function asyncBar() {
...
return new Promise((resolve, reject) => {
reject('error')
});
}
asyncBar().catch((err) => console.log('promise exec', err)); // promise exec error
- 如果异步函数返回
thenable
对象,那么其本身Promise.resolve
的状态由对象的then
方法决定
async function asyncBar() {
...
return {
then(resolve) {
reject('reject');
}
};
}
asyncBar().catch((err) => console.log('promise exec', err)); // promise exec reject
抛出异常
- 在普通函数中如果抛出了错误,那么该函数后的代码将不会执行
function asyncBaz() {
...
throw new Error('error');
}
asyncBaz();
// 下面代码都不会执行
console.log('后续代码');
- 如果在
async
函数中抛出异常,程序并不像普通函数一样崩掉,而是会作为Promise
的reject
值
async function asyncBaz() {
...
// 异步函数的异常,会作为异步函数返回的Promise的reject值
throw new Error('error');
}
asyncBaz().catch(console.log); // Error: error ....
// 下面代码照常执行,执行时机优先于catch回调
console.log('后续代码');
await关键字
async
函数的特殊之处就是可以在内部使用await
关键字,而普通函数中不可以
await
关键字的特点
- 通常使用
await
后面会跟一个表达式,该表达式返回一个Promise
async function baz() {
await 表达式(return Promise);
}
await
会等到Promise
的状态变成fulfilled
后才继续执行后续代码,如果是rejected
状态则不执行
function requestApi() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('success'), 1000);
});
}
async function baz() {
const res = await requestApi(); // 待1秒之后得到结果,才会继续执行后续代码
console.log(res); // success
}
baz();
- 在
await
等待期间,函数会呈现挂起状态,并将执行权交还给浏览器继续执行其他同步代码,待其他同步代码执行完成后,才会继续执行函数后续代码
async function baz() {
const res = await requestApi();
console.log(res);
}
baz();
console.log('后续代码');
console.log('后续代码');
- 当
await
后面跟的是一个普通的值,那么会将该值立即返回,但是也不会影响函数的执行顺序,因为其内部会将普通值用Promise.resolve
进行处理
async function baz() {
const res = await 123;
console.log(res); // 123
}
baz();
console.log('后续代码');
console.log('后续代码');
await
后也可以是thenable
对象,其返回的值是then
方法中resolve
的结果
async function baz() {
const res = await {
then(resolve) {
resolve('success');
}
};
console.log(res); // success
}
baz();
console.log('后续代码');
console.log('后续代码');
转载自:https://juejin.cn/post/7281535380590559243