Async/Await 语法糖与Generator异步方案
首先我们来看一下字节跳动的一道面试题:
setTimeout(function () {
console.log('setTimeout')
}, 0)
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
async1()
console.log('script end')
大家可以自己思考一下这道题的答案,然后去 nodejs 跑一下这段代码或者直接看我跑的
相信大家都和实际运行结果差不多,但是倒数第二条和倒数第三条的顺序反了,我一开始想的也是先打印 async1 end 再打印 script end,直到我去扒开 async 的语法糖,才知道了 async 的正确运行顺序。
我们应该不难理解,前两个输出先是 script start 和 async1 start,因为 setTimeout 是在“主线程”全部走完之后才会执行的,所以它的输出必定在 script end 之后。接着,由于 async1 的调用,代码执行第六行,输出 async1 start。后面,就到了扒开糖衣的时候了。
generator 异步方案
function* foo() {
console.log('test1')
//暂停执行并向外返回值
var s = yield 'next return data' //调用next后,返回对象值
// 注意,上面代码分两次next
console.log(s)
console.log('end code')
}
// 调用函数,不会立即执行,返回生成器对象
const gener = foo()
// 调用next方法,才会开始执行
// 返回包含 yield 内容的对象
const yields = gener.next()
console.log(yields) // () => {value:"xxxx", done: false}
// 对象中done表示生成器是否已经执行完毕
// 对象中value是函数中yield的返回值
// done:false 表示函数中的代码并没有执行完毕
//下一次 next 方法的调用,会从前面函数 yield 后的代码开始执行
// next() 方法的传参,是函数中 yield 的返回值
generator 是一种语法规则,其内容是在 function 后面或者函数名前面加个 * 号,并且函数中带有 yield 关键字,当首次调用这个函数的时候,函数不执行,只有调用函数的 next 方法的时候,函数开始执行,但是只会执行到 yield 后面的部分(前面赋值不执行),也就是返回 ‘next return data’ ,第二次调用next方法时,执行 yield 前面的赋值(赋的值是 next 传入的参数)操作和后面的代码。
function* fun(){
var b = yield ajax('http://localhost:8080') // ajax为我自己封装的ajax请求,返回一个promise对象
console.log(b)
var c = yield ajax('http://localhost:8080')
console.log(c)
}
const gener = fun()
var back1 = gener.next()
back1.value.then(backDate => {
var back2 = gener.next(backDate)
back2.value.then(backDate => {
gener.next(backDate)
})
})
我们可以像上面一样使用 generator 函数,可是这又有什么优势呢?当我们存在需要多次 next 的时候,这时候我们回调函数式有规律的,这时候我们只要将回调地狱换成以下代码即可
function h(res) {
if(res.done) return
res.value.then(backData => {
h(gener.next(backData))
})
}
h(back1)
讲到这里,我就可以告诉大家 async 就是根据这个逻辑进行封装的,我们只要将 * 换成 async,将 yield 换成 awiat,就是我们现在使用的语法糖了。当然也不只是替换那么简单,官方的在封装的过程中还是进行了很多优化 的,这里我就不细讲了,只要懂了这个逻辑,相信大家对 async 异步编程是怎么进行的都有了大概的了解了吧。
最后再讲一下开头的代码,
当代码执行到 awiat 处开启异步,等待 await 的结果,所以打印 async2,接着阻塞异步的代码,开始同步的操作,即执行19行代码输出 script end,同步操作执行完毕后才会回到异步的代码继续执行打印 async1 end,最后等所有同步异步结束后,开始执行定时器代码打印 setTimeout。