异步(二):Generator深入理解

原创 amandakelake 教程 JavaScript 130阅读 2018-03-16 22:57:08 举报

一、Generator基础认知

最基础的原则就是见到yield就暂停,next()就继续到下一个yield……以此知道函数执行完毕。

先不上理论,直接看一段代码,这里的step()是一个辅助函数,用来控制迭代器,替代手动next()

yield和next()调用有一个不匹配,就是说,想要完整跑完一个生成器函数,next()调用总是比yield的数量多一次

为什么会有这个不匹配?
因为第一个 next(..) 总是启动一个生成器,并运行到第一个 yield 处。不过,是第二个 next(..) 调用完成第一个被暂停的 yield 表达式,第三个 next(..) 调用完成第二个 yield, 以此类推。

所以上面的代码中,foo有两个yield,bar有三个yield
所以接下来要跑三次s1(),四次s2()
我们在控制台看每一步的输出,一步一步来分析

分析到这里,对generator的基础工作原理应该就有了大概的认知了。

如果想加深一点理解(皮一下),可以随意调换一下s1和s2的执行顺序,总之就是三个s1和四个s2,对于理解多个生成器如何在共享的作用域上并 发运行也有指导意义。

二、异步迭代生成器

这一段,我们来理解一下生成器与异步编程之间的问题,最直接的就是网络请求了

这段代码,大家都知道不能正常工作吧,data是underfined
ajax是一个异步操作,它并没有停下来等到拿到数据之后再赋值给data
而是在发出请求之后,直接就执行了下一句console

既然知道了问题核心在于“没有停下来”
那刚好生成器又有“yield”停下来这个操作,那么二者是不是刚好合拍了呢

看一段代码

这段代码使用了生成器,其实跟上一段代码干的是一样的事情,虽然更长更复杂,但实际上更好用,具体原因慢慢分析

两段代码的核心区别在于生成器中使用了yield

在yield foo()的时候,调用了foo(),没有返回值(underfined),所以发出了一个ajax请求,虽然依然是yield underfined,但是没关系,因为这段代码不依赖yield的值来做什么事情,大不了就打印underfined嘛对不对

这里并不是在消息传递的意义上使用 yield,而只是将其用于流程控制实现暂停 / 阻塞。实 际上,它还是会有消息传递,但只是生成器恢复运行之后的单向消息传递。

所以,生成器在 yield 处暂停,本质上是在提出一个问题:“我应该返回什么值来赋给变量 data ?”谁来回答这个问题呢?

看foo,如果ajax请求成功,调用

会用响应数据恢复生成器,意味着我们暂停的 yield 表达式直接接收到了这个值。然后 随着生成器代码继续运行,这个值被赋给局部变量 data

在生成器内部有了看似完全同步的代码
(除了 yield 关键字本身),但隐藏在背后的是,在 foo(..) 内的运行可以完全异步

这一部分对于理解生成器与异步编程之间扎下了最核心的内容,万望深刻理解为什么

三、Generator+Promise处理并发流程与优化

接下来来点高级货吧,总不能一直停留在理论上

request是假设封装好的基于Promise的实现方法
run也是假设封装好的能实现重复迭代的驱动Promise链的方法

这段代码里,r3是依赖于r1和r2的,同时r1和r2是串行的,但这两个请求是相对独立的,那是不是应该考虑并发执行呢?
但yield 只是代码中一个单独 的暂停点,并不可能同时在两个点上暂停

这样试一下

看一下yield的位置,p1和p2是并发同时执行的用于 Ajax 请求的 promise,哪一个先完成都无所谓,因为 promise 会按照需要 在决议状态保持任意长时间

然后使用接下来的两个 yield 语句等待并取得 promise 的决议(分别写入 r1 和 r2)。
如果p1先决议,那么yield p1就会先恢复执行,然后等待yield p2恢复。
如果p2先决 议,它就会耐心保持其决议值等待请求,但是 yield p1 将会先等待,直到 p1 决议。
不管哪种情况,p1 和 p2 都会并发执行,无论完成顺序如何,两者都要全部完成,然后才 会发出 r3 = yield request..Ajax 请求。

这种流程控制模型和Promise.all([ .. ]) 工具实现的 gate 模式相同

四、抽象异步Promise流,简化生成器

到目前位置,Promise都是直接暴露在生成器内部的,但生成器实现异步的要点在于:创建简单、顺序、看似同步的代码,将异步的 细节尽可能隐藏起来。

能不能考虑一下把多余的信息都藏起来,特别是看起来比较复杂的Promise代码呢?

把Promise的实现细节都封装在bar里面,对bar的要求就是给我们一下rs结果而已,我们也不需要关系底层是用什么来实现的

异步,实际上是把Promise,作为一个实现细节看待。

具体到实际生产中,一系列的异步流程控制有可能就是下面的实现方式

这些代码可能非常复杂,如果把实现直接放到生成器内部的话,那几乎就失去了使用生成器的理由了
好好记一下这句话:创建简单、顺序、看似同步的代码,将异步的 细节尽可能隐藏起来。

我在学习过程中喜欢做记录,分享的是自己在前端之路上的一些积累和思考,希望能跟大家一起交流与进步。 这是我的github博客,欢迎star

评论 ( 0 )
最新评论
暂无评论

赶紧努力消灭 0 回复