异步无处不在:终极解决方案(四)
(。・∀・)ノ゙嗨,我是你稳定更新、持续输出的勾勾。
前三篇内容,让我们认识了 JS 世界中的同步和异步模式,也清楚了回调地狱的产生和破解。
回忆一下第三篇的结尾:
虽然我们脱离了回调地狱,但是 .then 的链式调用依然不太友好。
频繁的 .then 并不符合自然的运行逻辑,Promise 的写法只是回调函数的改进,使用then 方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。
Promise 的最大问题是代码冗余。原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得很不清楚。
于是,在 Promise 的基础上,Async 函数出现了。
终极异步解决方案,
千呼万唤地在 ES2017 中发布了。
Async/Await 语法糖
Async 函数使用起来,也是很简单。
将调用异步的逻辑全部写进一个函数中,函数前面使用 async 关键字。
在函数中异步调用逻辑的前面使用 await ,异步调用会在 await 的地方等待结果,然后进入下一行代码的执行,这就保证了代码的后续逻辑,可以等待异步的 ajax 调用结果了。
而代码看起来的执行逻辑,和同步代码几乎一样。
async function callAjax(){
var a = await myAjax('./d1.json')
console.log(a);
var b = await myAjax('./d2.json');
console.log(b)
var c = await myAjax('./d3.json');
console.log(c)
}
callAjax();
注意:await 关键词只能在 async 函数内部使用。
因为使用简单,很多人也不会探究其使用的原理,无非就是两个单词加到前面用就好了。虽然会用,日常开发看起来也没什么问题,但是一遇到 Bug 调试,就只能凉凉。面试的时候也总是知其然不知其所以然。
之前也写过一篇 BAT 的异步面试真题:字节百度前端面试真题:异步处理方案(内附答案)。有兴趣的小伙伴可以再次回顾下。
咱们先来一个面试题试试,你能运行出正确的结果吗?
async 面试题
请写出以下代码的运行结果:
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')
答案我放在最后面,你也可以自己写出来运行一下。
想要把结果搞清楚,我们需要引入另一个内容:Generator 生成器函数。
先看一段代码:
function * foo(){
console.log('test');
// 暂停执行并向外返回值
yield 'yyy'; // 调用 next 后,返回对象值
console.log(33);
}
// 调用函数 不会立即执行,返回 生成器对象
const generator = foo();
// 调用 next 方法,才会 *开始* 执行
// 返回 包含 yield 内容的对象
const yieldData = generator.next();
console.log(yieldData) //=> {value: "yyy", done: false}
// 对象中 done ,表示生成器是否已经执行完毕
// 函数中的代码并没有执行结束
// 下一次的 next 方法调用,会从前面函数的 yeild 后的代码开始执行
console.log(generator.next()); //=> {value: undefined, done: true}
你会发现,在函数声明的地方,函数名前面多了 * 星号,函数体中的代码有个 yield ,用于函数执行的暂停。
简单点说就是,这个函数不是个普通函数,调用后不会立即执行全部代码,而是在执行到 yield 的地方暂停函数的执行,并给调用者返回一个遍历器对象。
yield 后面的数据,就是遍历器对象的 value 属性值。
如果要继续执行后面的代码,需要使用遍历器对象中的 next() 方法,代码会从上一次暂停的地方继续往下执行。
是不是 so easy!
同时,在调用 next 的时候,还可以传递参数,函数中上一次停止的 yeild 就会接受到当前传入的参数。
function * foo(){
console.log('test');
// 下次 next 调用传参接受
const res = yield 'yyy';
console.log(res);
}
const generator = foo();
// next 传值
const yieldData = generator.next();
console.log(yieldData)
// 下次 next 调用传参,可以在 yield 接受返回值
generator.next('test123');
Generator 的最大特点就是让函数的运行可以暂停。
不要小看他,有了这个暂停,我们能做的事情就太多了。在调用异步代码时,就可以先 yield 停一下,停下来我们就可以等待异步的结果了。
那么如何把 Generator 写到异步中呢?
明天见(ง •_•)ง。
推荐阅读:
前端人因为 Vue3 的 Ref-sugar 提案打起来了!
点点“赞”和“在看”,保护头发,减少bug。