这个来自 YDKJS 的委托递归示例究竟是如何工作的?



我开始阅读《你不知道JS:异步和性能》,并在委派递归示例时绊倒了:我在心理上浏览了代码并得到了正确的结果,但无法理解书中中间步骤的描述。

尝试将console.log()插入函数的主体,尝试调试器检查调用堆栈,但仍然无法使我的代码心理模型与书中的代码模型保持一致。

function run()将生成器函数作为参数,创建其实例并将其运行到最后,将每个先前yield的 ed 值传递给next()调用。

function run(gen) {
var args = [].slice.call( arguments, 1), it;
// initialize the generator in the current context
it = gen.apply( this, args );
// return a promise for the generator completing
return Promise.resolve()
.then( function handleNext(value){
// run to the next yielded value
var next = it.next( value );
return (function handleResult(next){
// generator has completed running?
if (next.done) {
return next.value;
}
// otherwise keep going
else {
return Promise.resolve( next.value )
.then(
// resume the async loop on
// success, sending the resolved
// value back into the generator
handleNext,
// if `value` is a rejected
// promise, propagate error back
// into the generator for its own
// error handling
function handleErr(err) {
return Promise.resolve(
it.throw( err )
)
.then( handleResult );
}
);
}
})(next);
} );
}

示例代码:

function *foo(val) {
if (val > 1) {
// generator recursion
val = yield *foo( val - 1 );
}
return yield request( "http://some.url/?v=" + val );
}
function *bar() {
var r1 = yield *foo( 3 );
console.log( r1 );
}
run( bar );

为了方便起见,我们可以实现这样的function request()

function request(url) {
return new Promise(function(resolve){
setTimeout(function(){
resolve( url.match(/v=(d+)$/)[1] );
},1000);
});
}

本书提供了以下步骤:

  1. run(bar)启动*bar()生成器。
  2. foo(3)*foo(..)创建一个迭代器,并将3作为其val参数传递。
  3. 因为3 > 1foo(2)会创建另一个迭代器,并传入2作为其val参数。
  4. 因为2 > 1foo(1)会创建另一个迭代器,并传入1作为其val参数。
  5. 1 > 1false,所以我们接下来用1值调用request(..),并得到第一个Ajax调用的承诺。
  6. 这个承诺被yield出来了,这又回到了*foo(2)生成器实例。
  7. yield *将承诺传递回*foo(3)生成器 实例。另一个yield *将承诺传递给*bar()生成器实例。又一次yield *传递了承诺 输出到run(..)实用程序,它将等待该承诺(对于 第一个 Ajax 请求)继续。
  8. 当承诺解决时,将发送其履行消息以恢复*bar(),它通过yield *进入*foo(3)实例,然后通过yield *传递到*foo(2)生成器 实例,然后通过yield *传递到正常yield这正在*foo(3)生成器实例中等待。
  9. 第一个调用的 Ajax 响应现在return立即从*foo(3)生成器实例,该实例将该值作为*foo(2) 实例中的yield *表达式的结果发送回,以及 分配给其局部val变量。
  10. *foo(2)内部,第二个 Ajax 请求是用request(..)发出的, 其承诺被yield*foo(1)实例,然后yield *一直传播到run(..)(再次执行步骤 7)。什么时候 承诺解析,第二个 Ajax 响应传播所有 回到*foo(2)生成器实例,并被分配给 它的局部val变量。
  11. 最后,第三个 Ajax 请求是用request(..)发出的,它的 承诺出去run(..),然后它的解析值来了 一直回来,然后returnEd,以便它回到*bar()中的等待yield *表达式。

直到第 8 步,一切都清楚了。

。然后通过yield *到达正常yield这正在*foo(3)生成器实例中等待。

为什么在foo(3)等待,而不是在foo(2)等待?我认为在承诺履行之后,它的值(1)被传递给return yield request( "http://some.url/?v=" + val );行,代替yield,所以我们在foo(1)结束时return 1。然后1再次传递到val = yield *foo( val - 1 );行中,代替yield,所以我们在调用foo(2)内部val = 1。之后,进行第二次request()yieldfoo(3)的承诺。 然后foo(3)yield是对bar()的承诺,然后bar()yield是对run()的承诺。run()等待第二个应许,就像等待第一个应许一样,依此类推。

JSFiddle

我忽略了什么?

我忽略了什么?

无。在步骤 8 和 9 中,他们应该引用的生成器是由foo(1)创建的生成器,而不是由foo(3)创建的生成器。

最新更新