Как именно работает этот пример делегирования рекурсии из YDKJS? - PullRequest
3 голосов
/ 12 июня 2019

Я начал читать You Not Know JS: Async and Performance и остановился на Пример делегирования рекурсии : Я мысленно прошел код и получил правильный результат, но не могу понять описание промежуточные шаги в кн.

Попытка вставить 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 > 1, foo(2) создает другой итератор и передает 2 в качестве параметра val.
  4. Поскольку 2 > 1, foo(1) создает еще один итератор и передает 1 в качестве параметра val.
  5. 1 > 1 - это false, поэтому мы затем вызываем request(..) со значением 1 и получаем обещание для этого первого вызова 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) экземпляр генератора, который отправляет это значение обратно как результат выражения yield * в экземпляре *foo(2), и присваивается локальной переменной val.
  10. Внутри *foo(2), второй запрос Ajax выполняется с request(..), чье обещание yield возвращено к экземпляру *foo(1), а затем yield * распространяется до run(..) (шаг 7 снова). когда обещание разрешается, второй ответ Ajax распространяет все обратно в экземпляр генератора *foo(2), и назначен его локальная val переменная.
  11. Наконец, третий Ajax-запрос выполняется с request(..), его Обещание выходит на run(..), а затем его значение разрешения приходит весь путь назад, который затем return редактируется так, чтобы он вернулся к ожидание yield * выражение в *bar().

Все ясно до 8-го шага.

... который затем проходит через yield * к нормальному yield это ожидает в экземпляре генератора *foo(3).

Зачем ждать в foo(3), а не в 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 Обещание foo(3). Затем foo(3) yield s Обещание bar(), затем bar() yield s Обещание run(). run() ждет второго Обещания, так же, как с первым обещанием и т. Д.

JSFiddle

Что я упустил из виду?

1 Ответ

1 голос
/ 12 июня 2019

Что я упустил из виду?

Ничего. В шагах 8 и 9 генератор, на который они должны ссылаться, - это генератор, созданный foo(1), а не foo(3).

...