最近在学习JavaScript异步相关的知识,在学到Generator(生成器函数)、Promise 和 async-awiat 等相关对异步优化的内容时遇到了一些一时没有理解的东西。本文仅作为学习过程中的笔记分享,第一次发文章,如果错误请不吝指出。
话不多说,进入正题,先写一下在 Generator 中要用到的函数,随便写几个就行。
function getValue(val, duration){
return new Promise(resolve => {
setTimeout(() => {
resolve(val);
}, duration);
})
}
function delay(duration){
return new Promise(resolve => {//delay函数不resolve出任何东西,或者说resolve出undefined
setTimeout(resolve, duration);
})
}
function getJSON(url){
return get(url).then(JSON.parse);
}
然后是 Generator 函数主体
function *f(){
var data = yield getJSON('data.json')//(1)
yield delay(1000);//(2)
var b = yield getValue(10, 3000);//(3)
}
Generator 函数会在遇到 yield 时生成一个值并暂停执行,如果要继续执行,则要调用next(),yield 的运算结果是生成器 next() 里面的参数。
为了执行 Generator函数 f ,声明一个函数 g 作为函数 f 的调用。
var g = f();
g.next().value.then(value =>{
g.next(value).value.then(value =>
g.next(value).value.then(value =>
g.next(value);
});
});
});
分析一下对 g 调用 next() 的过程。
在声明完函数 g 之后。第一行的第一个 value 是 getJSON 解析好的的 promise(我们希望在 promise 在 resolve 以后恢复执行),然后通过 data 返回,但是我们也不确定什么时候能resolve,但是可以确定的是:resolve 之后恢复生成器的执行。
第二行的第一个 value 是上一个 then 传入的 value(getJSON(‘data.json’) resolve 的结果),第二个 value 是 delay(1000) 的 promise 的 resolve。
接下来是第三行,很明显,next() 调用一次就恢复执行,然后暂停,再调一次就再恢复执行,这里是第三次调用,传入的 value(第二个 value)是第(2)行的返回结果,虽然 delay 并没啥返回(甚至左边没有变量接住)。这次调用将会返回第(3)行生成的 promise 对象,然后等getValue 函数 resolve 以后,再恢复执行。这时这里第三个 value 应该是10并返回给b。
最后一行,即第四行 g.next(value); 这里的 value 为10。
总结一下 f() 函数的执行
运行到第(1)行的 yield 时,在 yield 出 getJSON 的 promise 后,它执行停止了,直到 promise 的 resolve 后才恢复执行,并且恢复之后还将返回 promise 的 resolve 的值,也就是 data 将等于 data.json 解析出的内容。
然后函数运行到第(2)行,又 yield 出了 delay 的 promise,并再次等待 promise 的 resolve后才恢复执行(虽然这里的 delay 本身没求值结果所以也不 resolve 出啥),delay 在1秒后完成,所以第(2)行的 yield 也在1秒后恢复。
运行到第(3)行,又 yield 出一个promise,然后就停住了,在等待这个 promise 的 resolve 后才恢复,要恢复得调用 next(),而 next 的调用在 promise 的 resolve之后,即这时已经有resolve 的值了,把值传给 next() 即可,b 就等于这个 resolve 出的值。全程 next() 调用在那行的 promise 的 resolve之后,而且调用的时候把 promise 的 resovle 的值传给 next() 并成为yield 的返回值。
f() 这样跟同步的写法区别仅在于有没有 yield,虽然是异步的,但是写在了同一行里。(1)行的data 最终将等于 data.json 用 getJSON 请求到的真正的数据,而不是一个 promise 对象。虽然看起来像同步,但实际上代码在运行到这行的时候暂停了一定的时间,时间取决于 promise 的 resolve 的时间,在等待的这段时间,浏览器是完全空闲的。
在以上的举例分析中,我们只考虑了 promise 能正确 resolve 的情况,那么接下来再写一下出现错误的情况(拿 next() 调用 g 的第一行举例)
g.next().value.then(value =>{
g.next(value);//成功就通过yield返回
}, reason =>{
g.throw(reason);//失败就通过yield抛出
});
console.log(1);
如果成功的就调用这个箭头函数(第一个 value 是 promise),then 只是挂上了两个并没有立刻得到结果的函数就结束,然后执行下一句(console.log(1);)了,注意即使在then调用时promise 已经得出结果,下一句 console.log(1); 仍然先于then里的函数执行。因为这个函数是异步调用的,里面的内容必定在 then 所在的调用栈全部清空以后执行,
另外说一下,既然有错误,就应该加上 try-catch 语句。
try{
var data = yield getJSON('data.json')
}catch(e){
console.log(e);
return;
}
//多了yield的好处:能看出代码哪里需要等待
最后总结和补充一下 Generator 和 async-await 对于异步优化的一些知识点
- Generator可以让执行处于暂停状态,Generator 返回的是一个 Iterator 对象。
- 必要构成,1 个*号和 yield 运算符。
- next 属性返回一个对象,里面 value 是当前 yield 后面的值,done 表示当前生成器有没有运行完。yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。yield可以生成一个值,然后恢复的话(也可以不恢复,就不返回值)又可以返回一个值,两个值可以不一样。
- yield: yield 的运算结果是生成器 next() 里面的参数。
- async + 生成器函数 function {await :promise 函数}。 async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await。
- async 函数对 Generator 函数的改进,配合 promise 使用(后面接返回promise的函数?),包装原理如上。并且 function() 返回一个 promise。async-await 本质是生成器函数和yield。