Я начал читать 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);
});
}
В книге предусмотрены следующие шаги:
run(bar)
запускает генератор *bar()
.
foo(3)
создает итератор для *foo(..)
и передает 3
в качестве параметра val
.
- Поскольку
3 > 1
, foo(2)
создает другой итератор и передает 2
в качестве параметра val
.
- Поскольку
2 > 1
, foo(1)
создает еще один итератор и передает 1
в качестве параметра val
.
1 > 1
- это false
, поэтому мы затем вызываем request(..)
со значением 1
и получаем обещание для этого первого вызова Ajax.
- Это обещание
yield
исключено, которое возвращается к *foo(2)
экземпляр генератора.
-
yield *
передает это обещание обратно генератору *foo(3)
пример. Другой yield *
передает обещание *bar()
экземпляр генератора. И все же еще один yield *
проходит обещание
в утилиту run(..)
, которая будет ждать этого обещания (для
первый запрос Ajax), чтобы продолжить.
- Когда обещание разрешается, его выполнение отправляется для возобновления.
*bar()
, который проходит через yield *
в экземпляр *foo(3)
, который затем проходит через yield *
к генератору *foo(2)
экземпляр, который затем проходит через yield *
к нормальному yield
это ожидает в экземпляре генератора *foo(3)
.
- Ответ первого вызова Ajax теперь сразу
return
из
*foo(3)
экземпляр генератора, который отправляет это значение обратно как результат выражения yield *
в экземпляре *foo(2
), и
присваивается локальной переменной val
.
- Внутри
*foo(2)
, второй запрос Ajax выполняется с request(..)
,
чье обещание yield
возвращено к экземпляру *foo(1)
, а затем
yield *
распространяется до run(..)
(шаг 7 снова). когда
обещание разрешается, второй ответ Ajax распространяет все
обратно в экземпляр генератора *foo(2)
, и назначен
его локальная val
переменная.
- Наконец, третий 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
Что я упустил из виду?