我开始阅读《您不知道JS:异步和性能》,并在delegating recursion example上绊倒了:我在心中仔细地检查了代码,并得到了正确的结果,但是无法理解本书中间步骤的描述。

尝试将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);
    });
}

本书提供了以下步骤:
  • run(bar)启动*bar()生成器。
  • foo(3)*foo(..)创建一个迭代器,并将3传递为其val参数。
  • 因为是3 > 1,所以foo(2)创建了另一个迭代器,并将2传递为其val参数。
  • 因为2 > 1,所以foo(1)创建了另一个迭代器,并将1传递为其val参数。
  • 1 > 1false,因此我们接下来使用request(..)值调用1,并为第一个Ajax调用获得 promise 。
  • 该 promise 已被yield修改,这又回到了*foo(2)生成器实例。
  • promise 返回给yield *生成器的*foo(3)传递
    实例。另一个yield *将 promise 传递给*bar()生成器实例。再一次,另一个yield *通过了 promise
    run(..)实用程序,它将等待该 promise (对于
    首次Ajax请求)继续。
  • 当 promise 解决时,其履行消息将发送以恢复*bar(),它通过yield *传递到*foo(3)实例,然后实例通过yield *传递到*foo(2)生成器
    实例,然后通过yield *传递到普通的yield*foo(3)生成器实例中正在等待。
  • 现在,第一个调用的Ajax响应立即从return生成器实例,该实例将该值作为*foo(3)实例中yield *表达式的结果发送回去,并且
    分配给其本地*foo(2变量。
  • val内部,使用*foo(2)发出第二个Ajax请求,
    将其 promise 被request(..)放回yield实例,然后
    *foo(1)一直传播到yield *(再次执行步骤7)。什么时候
    promise 解决,第二个Ajax响应传播所有
    返回run(..)生成器实例,并分配给
    其本地*foo(2)变量。
  • 最后,第三个Ajax请求是使用val发出的,
    promise 传给request(..),然后它的分辨率值来
    一路返回,然后进行run(..) ed,以便返回return中等待的yield *表达式。

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



    为什么要等待*bar()而不是foo(3)?我认为在Promise实现后,其值(foo(2))会传递给1行,而不是return yield request( "http://some.url/?v=" + val );,因此我们在yield的末尾有return 1。然后,再次将foo(1)传递到1行,以代替val = yield *foo( val - 1 );,因此我们在yield调用中包含了val = 1。之后,创建第二个foo(2),并且request()成为yield的 promise 。
    然后foo(3) foo(3) promise 为yield,然后bar() bar() promise 为yieldrun()与第二个Promise一样等待第二个Promise,依此类推。

    JSFiddle

    我忽略了什么?

    最佳答案



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

    10-01 04:08
    查看更多