前言:async、await 语法是ES6新出的,主要是为了解决多个Promise函数产生的嵌套层级过多的问题。
例:你有abcde...N个异步操作。每个异步操作又是依赖于上一个的异步函数(Promise)的相应结果(.then)来执行的。那么你的代码大概是这样的:
const initData = 1;
funtcion init(data){
a(data).then((res)=>{
b(res.data).then((res)=>{
C(res.data).then((res)=>{
d(res.data).then((res)=>{
... // 一直嵌套下去
})
})
})
})
}
init(initData)
这样的代码不仅逻辑复杂还难以阅读,很容易产生bug。所以es6就出了个async、await 语法去解决这个问题,让代码变得简单易懂,容易维护:
const initData = 1;
function async init(data){
let res = await a(data);
res = await b(res.data);
res = await c(res.data);
res = await d(res.data);
......
}
init(initData)
在理解async await 之前需要去了解 ES6的Generator,因为async await是基于Generator实现的代码中断操作(也就是上一个await处理完时代码不会继续往下执行,所以看起来就是中断了代码)
1、Generator:
Generator和普通的function 函数区别在于function * 之后有个*来告诉js这是Generator函数,然后代码内部由yield (可以理解为代码分割)把代码分割成若干个片段。通过Generator执行后的返回值.next()去一个个按顺序执行。看下面例子会变得比较容易理解:
function * init(){
yield '第1次next,来执行的代码片段';
yield '第2次next,来执行的代码片段';
yield '第3次next,来执行的代码片段';
}
const f = init();
f.next() // {value: '第1次next,来执行的代码片段', done: false}
f.next() // {value: '第2次next,来执行的代码片段', done: false}
f.next() // {value: '第3次next,来执行的代码片段', done: false}
f.next() // {value: undefined, done: true}
f.next() // {value: undefined, done: true}
f.next() // {value: undefined, done: true}
.... // 接下来f.next() 的返回值都是{value: undefined, done: true},done:true表示代码片段已经执行完毕了,也就是执行到最后一个yield 。
// 上面的逻辑也理解为:init函数根据yield 标识把代码分割成了,
function next1(){
return {value: '第1次next,来执行的代码片段', done: false}
}
function next2(){
return {value: '第2次next,来执行的代码片段', done: false}
}
function next3(){
return {value: '第3次next,来执行的代码片段', done: false}
}
function nextEnd(){
return {value: undefined, done: true}
}
// 然后.next 一个个执行。
继续深入:next 函数也是可以接受参数的。他的参数就是yield 函数的返回值,通过下面例子来理解:
function * init(){
const res1 = yield '第1次next,来执行的代码片段';
console.log(res1,'res1')
const res2 = yield '第2次next,来执行的代码片段';
console.log(res2,'res2')
const res3 = yield '第3次next,来执行的代码片段';
console.log(res3,'res3')
};
const f = init();
f.next()
// f.next()的返回值是 {value:'第1次next,来执行的代码片段',done:false};
// 不会打印,还没执行到第一个console.log
f.next()
// f.next()的返回值是 {value:'第2次next,来执行的代码片段',done:false};
// 第1个console.log的是 undefined 'res1'
f.next()
// f.next()的返回值是 {value:'第3次next,来执行的代码片段',done:false};
// 第2个console.log的是 undefined 'res2'
f.next()
// f.next()的返回值是 {value:undefined ,done:true};
// 第3个console.log的是 undefined 'res3'
f.next()
// f.next()的返回值是 {value:undefined ,done:true};
// 没有东西打印
必须要注意的是,f.next() 返回值是 当前执行的yield 后面代码的值,不要return,自动返回({value:'第1次next,来执行的代码片段',done:false});而 const res1 = yield '第1次next,来执行的代码片段'; res的返回值是.next 传入的形参。但是值得注意的是:第一次执行.next,不需要传入参数,即使传入了也是无效,丝毫不会影响代码逻辑。例:
function * init(){
const res1 = yield '第1次next,来执行的代码片段';
console.log(res1,'res1')
const res2 = yield '第2次next,来执行的代码片段';
console.log(res2,'res2')
const res3 = yield '第3次next,来执行的代码片段';
console.log(res3,'res3')
};
const f = init();
f.next('init')
// f.next()的返回值是 {value:'第1次next,来执行的代码片段',done:false};
// 不会打印,还没执行到第一个console.log
f.next('init1')
// f.next()的返回值是 {value:'第2次next,来执行的代码片段',done:false};
// 第1个console.log的是 'init' 'res1' ,因为第一次执行.next。代码并没有执行到'const res1 = // ',这个阶段,所以你给next的传参肯定也是无效的。
f.next('init2')
// f.next()的返回值是 {value:'第3次next,来执行的代码片段',done:false};
// 第2个console.log的是 'init2' 'res2' ,第二次执行。代码已经执行到了'const res1 = '这个阶段,
// 然后会看见'const res1 = ',是接受上一个yield 的返回值。那么这个时候它就被.next入参修改了。
f.next('init3')
// f.next()的返回值是 {value:undefined ,done:true};
// 第3个console.log的是 'init3' 'res3'
f.next('init4')
// f.next()的返回值是 {value:undefined ,done:true};
// 没有打印东西,代码执行完毕了。
红色:第一次next;绿色:第二次next;蓝色:第三次next;橙色:第四次next。
2、实现async await
1.1、先用setTimeout 实现一个异步函数:awaitFn
function awaitFn(backData){
return function (callBack){
setTimeout(()=>{callBack(++backData)},1000)
}
}
1.2、再用Generator实现一个asyncFn函数,模拟async await代码结构:
function* asyncFn(){
let res = yield awaitFn(0);
console.log(res,'第1次')
res = yield awaitFn(res);
console.log(res,'第2次')
res = yield awaitFn(res);
console.log(res,'第3次')
res = yield awaitFn(res);
console.log(res,'第4次')
}
1.3、最关键的一步,通过Generator函数的特性,写一个递归函数来实现async await:
function init(fn){
const f = fn();
function next(data){
const res = f.next(data);
if(res.done) return res.value;
res.value((backData)=>{
next(backData);
})
}
next()
}
init(asyncFn) // 然后执行init,把之前的asyncFn当形参传入
结果:
每秒打印一次,返回值是入参自增。
到了这里我想你应该知道接下来应该怎么改了吧?我们来改动awaitFn函数和init:
awaitFn:
function awaitFn(backData){
// return function (callBack){
// setTimeout(()=>{callBack(++backData)},1000)
// }
return new Promise((rj)=>{
setTimeout(()=>{rj(++backData)},1000)
})
}
init:
function init(fn){
const f = fn();
function next(data){
const res = f.next(data);
if(res.done) return res.value;
Promise.resolve(res.value).then((backData)=>{
next(backData);
}) // 无论是不是Promise 函数都会以Promise 函数形式触发
}
next()
}
init(asyncFn) // 然后执行init,把之前的asyncFn当形参传入