其实关于js generator的内容算是一个老话题了,在日常的应用开发中,我们多少通过间接或直接的方式有过接触。 直接的比如,dva.js、koa.js(1.x版本)等等。 间接的包括async,await相关特性的使用。
既然是老腔常谈,我们来看一下官方对Generator的简要说明:
function* gen() { yield 1; yield 2; yield 3; } const g = gen(); console.log(g) // 输出结果 Generator {}在浏览器控制台打印 Generator 实例,通过展开对象后我们可以看见下面内容: 图中我们除了原型链上看到,Generator 的实例方法外。
[[GeneratorState]]: 'suspended'这一行作为 Generator 的私有属性,从字面意思上来看,就是状态的意思,那么我们可以认为 Generator 是一个状态机。 通过验证 Generator 有两个状态 suspended 等待、closed 关闭。Generator 的初始状态为 suspended,每次调用next方法会检测function *() {} 里面的每一行代码,如果遇到yield 就算是完成一次迭代,并返回对应的值,状态保持suspended。如果检测到return同样返回对应的值,并将状态改为closed。之后还有yield 没有执行,即使再次调用next的方法,也不再有效。Generator 的return方法同样有结束迭代的效果。
提示:Generator的状态是不可逆的,一旦状态变为closed就代表既定事实,无法恢复。 虽然我们不能通过获取Generator私有的状态,但是我们可以通next()得到的值的done属性来判断当状态,当done = true的时候就是结束或者关闭状态。
来看一段代码
const fn = function *() { yield new Promise(r => setTimeout(() => r('first, 2000'),2000)) yield new Promise(r => setTimeout(() => r('second, 1000'),1000)) } const g = fn() g.next().value.then(v => console.log(v)) g.next().value.then(v => console.log(v)) // 输出结果 // second, 1000 // first, 2000这是一个简单利用 Generator 来进行的异步调用。 不过从输出的结果来看,并不是按我希望的按照迭代顺序来输出结果的。 来把代码稍作修改:
const g = fn() g.next().value.then(v => { console.log(v) g.next().value.then(v => console.log(v)) }) // 输出结果 // first, 2000 // second, 1000从修改后的代码能够看到,达到了预期的结果。 但是,问题来了。不光代码就传统的异步调用的代码更多,而且并没有有效解决回调地狱的问题,而且一旦异步调用的规模为n不确定时,这种手动嵌套的方式,显然是不可取的。 我们再来看一个Generator的特性:
const fn = function *() { const first = yield 'first' console.log(first) const second = yield 'second' console.log(second) } const g = fn() let tempValue = g.next() tempValue = g.next(tempValue.value) g.next(tempValue.value) // 输出结果 // first // second通过输出的结果来看,结果是从function 中完美输出。 如果只看function 这部分代码是不是有点眼熟。
const fn = async function () { const first = await Promise.resolve('first') console.log(first) const second = await Promise.resolve('second') console.log(second) } fn() // 输出结果 // first // second其实async与await就是*与yield的语法糖。 那么问题来了,该怎么实现上述的这种自然同步效果呢。
继续看代码:
// 这里致敬co.js const Co = generator => { // 逻辑 } // 我们只需要将之前的代码放在里面 Co(function *() { const first = yield new Promise(r => setTimeout(() => r('first, 2000'),2000)) console.log(first) const second = yield new Promise(r => setTimeout(() => r('second, 1000'),1000)) console.log(second) }) // 希望输出的结果 // first // secondCo方法里面主要用来处理 Generator 的迭代逻辑,也就是用来干脏活。 正如标题所诉为了达到前面我们需要的结果,我们需要4行代码。对,就是这么简单!
// 如题,刚好四行,完美! const Co = generator => { const ge = generator() // 这里用了一个递归来解决问题规模未知的情况 const recursion = next => { !next.done && next.value.then(res => recursion(ge.next(res))) } recursion(ge.next()) }Generator 在加入JS大家庭的时候,正直整个前端高速发展的时期,那个时候涌现出了很多有意思的工具。大部分的人都沉侵在工具的选择问题上。随着后来async,await的加入,自然是大浪淘沙。不过在闲暇之余,我们同样可以学习这些尘封在历史中点滴,来提升自己审视与解决问题的能力。