Генераторы — это мощный инструмент для организации асинхронного кода, но они не ждут, пока асинхронный код запустится, как по волшебству.
Что творится
Давайте пройдемся по вашему коду, чтобы вы могли увидеть, что происходит:
getRelationsList
— это функция-генератор, которая при вызове возвращает новый генератор. На данный момент никакой код в вашей функции-генераторе не вызывался (хотя, если бы вы передавали параметры, они были бы установлены). Затем вы вызываете .next
на своем генераторе, чтобы начать выполнение функции генератора. Он будет выполняться до тех пор, пока не встретит первый оператор yield и не вернет объект с полученным значением и статусом завершения генератора.
Кажется, вы уже поняли большую часть этого, но генераторы не волшебным образом преобразуют полученные значения. Когда вы выдадите db.relations.find({})
, вы получите возвращаемое значение функции find
, которое, как я предполагаю, является обещанием или каким-то типом:
поэтому ваше «внешнее» значение равно { value:Promise, done:false }
Причина, по которой ваш внутренний console.log
никогда не запускался, заключается в том, что вы фактически создаете новый генератор каждый раз, когда вы вызываете getRelationsList()
, поэтому, когда вы снова вызываете getRelationsList().next()
после внешнего console.log
, вы создаете новый генератор и вызываете следующий, поэтому он выполняется только до первый yield, как и вызов в предыдущей строке.
Чтобы завершить выполнение, вы должны дважды вызвать next для одного и того же экземпляра вашего генератора: один раз, чтобы выполнить до выхода, и один раз, чтобы продолжить выполнение до конца функции.
var gen = getRelationsList()
gen.next() // { value:Promise, done:false }
gen.next() // { value:undefined, done:true } (will also console.log inside)
Однако вы заметите, что если вы запустите это, то внутри console.log
будет undefined
. Это связано с тем, что значение оператора yield
равно значению, переданному следующему вызову .next()
.
Например:
var gen2 = getRelationsList()
gen2.next() // { value:Promise, done:false }
gen2.next(100) // { value:undefined, done:true }
Выходы
{ inside:100 }
Потому что мы передали 100 во второй вызов .next()
, и это стало значением оператора yield db.relations.find({})
, которое затем было присвоено res
.
Вот ссылка, демонстрирующая все это: http://jsfiddle.net/qj1aszub/2/
Решение
Создатели koa используют небольшую библиотеку под названием co, которая, по сути, берет переданные промисы и ожидает их завершения, прежде чем передача разрешенного значения обратно в функцию-генератор (используя функцию .next()
), чтобы вы могли написать свой асинхронный код в синхронном стиле.
co вернет обещание, которое потребует от вас вызова метода .then
, чтобы получить значение, возвращаемое функцией-генератором.
var co = require('co');
var getRelationsList = function *() {
var res = yield db.relations.find({});
console.log({'inside: ': res});
return res
}
co(getRelationsList).then(function(res) {
console.log({'outside: ': res })
}).catch(function(err){
console.log('something went wrong')
});
co также позволяет вам выдавать другие функции генератора и ждать их завершения, поэтому вам не нужно оборачивать вещи co на каждом уровне и иметь дело с обещаниями, пока вы не окажетесь на каком-то «верхнем уровне»:
co(function *() {
var list = yield getRelationsList()
, processed = yield processRelations(list)
, response = yield request.post('/some/api', { data:processed })
return reponse.data
}).then(function(data) {
console.log('got:', data)
})
person
mlrawlings
schedule
12.05.2015