异步无处不在:终极解决方案(四)

324 阅读4分钟

异步无处不在:同步模式和异步模式(一)

异步无处不在:回调函数(二)

异步无处不在:Promise 破除“回调地狱”(三)

前三篇内容,让我们认识了 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 写到异步中呢?

明天见(ง •_•)ง。