开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情
前言
今天让我们来聊聊JS对异步处理的终极操作:async
在ES7中提出了async
这个函数 它是Generator
函数的语法糖,用await
关键字来表示异步
接下来我们来一步步剖析:
传统promise.then解决的异步
比如这里,它是先执行foo()函数,再来执行then中的回调函数bar()
new Promise (()=>{
foo()
}).then(()=>{
bar()
})
复制代码
promise.then
在解决异步方面是挺好用,then
方法返回的一个新的promise
实例,来实现链式调用,然后,将传给 then
的函数和新 promise
的 resolve
一起 push
到前一个 promise
的 callbacks
数组中,达到承前启后的效果
console.log(100);
setTimeout(() => {
console.log(200);
})
Promise.resolve().then(() => {
console.log(300);
})
console.log(400);
打印结果:
100
400
300
200
复制代码
但是总感觉它不是那么优雅,所以ES7便诞生了async
函数,让一切优雅易懂
理解一下Generator函数
Generator
函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。Generator
函数将 JavaScript 异步编程带入了一个全新的阶段。
function* gen() {
yield 1
yield 2
yield 3
return 4
}
const g = gen()
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: false }
console.log(g.next()); // { value: 4, done: true }
复制代码
它里面给了一个封印,yield
,也就是暂停,当函数运行到这里时,暂停执行后面的操作,并将紧跟在yield
后面的那个表达式的值,作为返回的对象的value
属性值。
当return之后,也就是函数执行完毕,它的done值就会由false变成true。
next方法可以有参数
一句话说,next方法参数的作用,是为上一个yield语句赋值。由于yield永远返回undefined,这时候,如果有了next方法的参数,yield就被赋了值,比如下例,原本a变量的值是0,但是有了next的参数,a变量现在等于next的参数,也就是11。
手写Generator核心原理
关注它的核心也就是看看done如何变成true,看看下面的例子,用swith__case模仿一下
var context = {
next: 0,
prev: 0,
done: false,
// 新增代码
stop: function stop() {
this.done = true
}
}
function gen$(context) {
while (1) {
switch (context.prev = context.next) {
case 0:
context.next = 2;
return 'result1';
case 2:
context.next = 4;
return 'result2';
case 4:
context.next = 6;
return 'result3';
case 6:
// 新增代码
context.stop();
return undefined
}
}
}
let foo = function () {
return {
next: function () {
value = gen$(context);
done = context.done
return {
value,
done
}
}
}
}
复制代码
第一次执行gen$(context)
,swtich判断的时候,是用prev来判断这一次应该执行哪个case,执行case时再改变next的值,next表示下次应该执行哪个case。第二次执行gen$(context)
的时候,将next的值赋给prev。以及给context添加一个stop方法。用来改变自身的done为true。在执行$gen的时时候让context执行stop就好,这样执行到case为6的时候就会改变done的值了。
从中我们可以看出,「Generator实现的核心在于上下文的保存,函数并没有真的被挂起,每一次yield,其实都执行了一遍传入的生成器函数,只是在这个过程中间用了一个context对象储存上下文,使得每次执行生成器函数的时候,都可以从上一个执行结果开始执行,看起来就像函数被挂起了一样」
async/await
- 从上面我们可以得知,Promise 的方式虽然解决了 callback hell,但是这种方式充满了 Promise的 then() 方法,如果处理流程复杂的话,整段代码将充满 then。语义化不明显,代码流程不能很好的表示执行流程。
- Generator 的方式解决了 Promise 的一些问题,流程更加直观、语义化。但是 Generator 的问题在于,函数的执行需要依靠执行器,每次都需要通过 g.next() 的方式去执行。
这两种方法都有一些小弊端,那么接下来的终极async函数完美的解决了上面两种方式的问题,同时它自带执行器,执行的时候无需手动加载
像写hooks高级函数一样写async函数
先来看看官方给的async/await
function foo(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * 2)
}, 1000)
})
}
async function asyncFn2() {
const num1 = await foo(1)
const num2 = await foo(num1)
const num3 = await foo(num2)
return num3
}
asyncFn2().then(res => console.log(res))
复制代码
将前一个await返回的结果作为下一个的初始值,反复循环,直到return函数结束,这里我们可以看到打印出来的 asyncFn2()
为Promise { <pending> }
,async
函数是返回了一个promise对象,这样的promise.then
实现的链式调用,当我们的代码很长或者说需要嵌套的层数很多时,代码就会显得十分臃肿,那么怎么办呢,加上我们的Generator
函数。
function foo(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * 2)
}, 1000)
})
}
// async 具有async功能的函数
function generatorToAsync(generatorFn) {
return function() {
const gen = generatorFn.apply(this, arguments)
return new Promise((resolve, reject) => {
// const g = generatorFn()
// const next1 = g.next()
// next1.value.then(res1 =>
// const next2 = g.next(res1)
// next2.value.then(res2 => {
// const next3 = g.next(res2)
// next3.value.then(res3 => {
// resolve(g.next(res3).value) // { value: 8, done: true }
// })
// })
// })
function loop(key, arg) {
let res = null
res = gen[key](arg) // gen.next(8)
const { value, done } = res
if (done) {
return resolve(value)
} else {
// Promise.resolve(value) 为了保证 value 中的promise状态已经变更成 成功状态
Promise.resolve(value).then(val => loop('next', val))
}
}
loop('next')
})
}
}
function* gen() {
const num1 = yield foo(1)
const num2 = yield foo(num1)
const num3 = yield foo(num2)
return num3
}
const asyncFn = generatorToAsync(gen)
// console.log(asyncFn()); // Promise{}
asyncFn().then(res => {
console.log(res);
})
复制代码
定义一个高阶函数
generatorToAsync
,并将generatorFn
函数当作参数传进去,然后返回一个具有async功能的函数,然后再下面定义一个loop
函数,在其内部递归调用来知道我们要调用.then
的次数,在其内部给res一个初始null值,然后loop
函数使用'next'
作为参数,通过循环判断执行出来的结果状态是false还是true,是false就继续递归,true就直接resolve
出结果。从而实现async功能。
结语
定义高阶函数来实现具有async函数一样的功能,它不仅解决了代码冗余,同时,流程清晰,直观、语义明显。