@vadimek
Полуджун

Как именно работает этот пример делегирования и рекурсии из You Don't Know JS?

Начал читать ­«You Don't Know JS: Async and Performance» и застрял на примере delegating recursion (кстати, как правильно перевести «delegating recursion»?). Прошел мысленно (и не только) по коду и получил верный результат, но не до конца понимаю промежуточные шаги, описанные в книге.

Пробовал расставлять console.log() внутри функций, пробовал дебаггером изучить call stack, но так и не могу привести в соответствие свое представление кода с книжным.

Функция run() принимает функцию-генератор, создает ее экземпляр и проходит его до конца, передавая каждое прошлое значение из yield в вызов 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);

Для удобства можно реализовать функцию request() так:
function request(url) {
    return new Promise(function(resolve){
        setTimeout(function(){
            resolve( url.match(/v=(\d+)$/)[1] );
        },1000);
    });
}


Шаги, приведенные в книге (не перевожу, чтобы что-то не исказить):
  1. run(bar) starts up the *bar() generator.
  2. foo(3) creates an iterator for *foo(..) and passes 3 as its val parameter.
  3. Because 3 > 1, foo(2) creates another iterator and passes in 2 as its val parameter.
  4. Because 2 > 1, foo(1) creates yet another iterator and passes in 1 as its val parameter.
  5. 1 > 1 is false, so we next call request(..) with the 1 value, and get a promise back for that first Ajax call.
  6. That promise is yielded out, which comes back to the *foo(2) generator instance.
  7. The yield * passes that promise back out to the *foo(3) generator instance. Another yield * passes the promise out to the *bar() generator instance. And yet again another yield * passes the promise out to the run(..) utility, which will wait on that promise (for the first Ajax request) to proceed.
  8. When the promise resolves, its fulfillment message is sent to resume *bar(), which passes through the yield * into the *foo(3) instance, which then passes through the yield * to the *foo(2) generator instance, which then passes through the yield * to the normal yield that's waiting in the *foo(3) generator instance.
  9. That first call's Ajax response is now immediately returned from the *foo(3) generator instance, which sends that value back as the result of the yield * expression in the *foo(2) instance, and assigned to its local valvariable.
  10. Inside *foo(2), a second Ajax request is made with request(..), whose promise is yielded back to the *foo(1) instance, and then yield * propagates all the way out to run(..) (step 7 again). When the promise resolves, the second Ajax response propagates all the way back into the *foo(2) generator instance, and is assigned to its local val variable.
  11. Finally, the third Ajax request is made with request(..), its promise goes out to run(..), and then its resolution value comes all the way back, which is then returned so that it comes back to the waiting yield * expression in *bar().

Все понятно до 8-го шага
...which then passes through the yield * to the normal yield that's waiting in the *foo(3) generator instance.

Почему yield, ожидающий в 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предает Промис в bar(), bar() аналогично передает Промис в run(). run() ждет выполнения этого (второго) Промиса, так же как с первым, и то же самое дальше с третьим Промисом, только начиная внутри функции foo(2) со строчки
return yield request( "http://some.url/?v=" + val );




Что я упустил?
  • Вопрос задан
  • 134 просмотра
Пригласить эксперта
Ответы на вопрос 1
@grinat
Так и не понял что тебе не ясно. Да и щас такие делают вот так:
async function myFoo (val) {
    if (val > 1) {
        val = await myFoo(val - 1)
    }
    return request('http://some.url/?v=' + val)
}

async function myBar () {
    let r1 = await myFoo(5)
    console.log( 'r1', r1 )
}

myBar()

Просто вначале появились промисы и генераторы, и потом уже асинк/авейт и приходилось извращаться на генераторах, чтобы воспроизвести. Щас генераторы бесполезны и никто их не использует. Например я делаю что либо на генераторах, только в том случае если в других языках это тоже делается на генераторах и в команде есть фулстеки, чтобы им было проще понять что происходит.

А что касается рекурсии, то еще до промисов последовательные вызовы через аякс делали через рекурсии, полагаю за этим в книге пример рекурсии.

Шо то я эту книгу глянул, зря ты ее читаешь, она устарела сильно.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы