Генераторы в КоА

Как работает app.use в KOA? Когда я устанавливаю какой-то генератор внутри app.use, все работает идеально. Как я могу сделать то же самое в другом месте?

Когда я просто выполняю руководство по генератору:

var getRelationsList = function *() {
  var res = yield db.relations.find({});
  console.log({'inside: ': res});
}
console.log({'outside: ': getRelationsList().next()});
getRelationsList().next();

Я получаю только { 'вне: ': {значение: [Функция], сделано: ложь} }

Это то, что я ожидаю:

{ 'outside: ': { value: {object_with_results}, done: false } }
{ 'inside: ': {object_with_results}

ИЗМЕНИТЬ

Я изменил свой код следующим образом:

var getRelationsList = function *() {
  var res = yield db.relations.find({});
  console.log({'inside: ': res});
}
console.log({'outside ': co(getRelationsList)});

Теперь внутренний журнал консоли показывает мне хорошие результаты, но внешний журнал консоли показывает мне просто пустой объект. введите здесь описание изображения


person Oskar Kamiński    schedule 12.05.2015    source источник


Ответы (3)


Генераторы — это мощный инструмент для организации асинхронного кода, но они не ждут, пока асинхронный код запустится, как по волшебству.

Что творится

Давайте пройдемся по вашему коду, чтобы вы могли увидеть, что происходит:

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

Ваша проблема в том, что вы вызываете функцию getRelationsList() несколько раз, что неверно.

Измените свой код на следующий

var g = getRelationsList();
console.log('outside: ', g.next());
g.next(); //You get your console.log('inside: .... here
person Molda    schedule 12.05.2015

На генераторы должен воздействовать внешний код. Под капотом koa используется библиотека co для запуска генератора.

Вот как вы можете достичь того, чего хотите за пределами коа:

var co = require('co');
var getRelationsList = function *() {
  var res = yield db.relations.find({});
  console.log({'inside: ': res});
}

co(getRelationsList).catch(function(err){});

Я сделал короткий скринкаст о генераторах JavaScript, который должен помочь вам понять, что происходит:

http://knowthen.com/episode-2-understanding-javascript-generators/

++ ИЗМЕНИТЬ

Если вы используете генераторы для программирования в более синхронном стиле (исключая обратные вызовы), то вся ваша работа должна выполняться в генераторе, и вы должны использовать библиотеку, такую ​​​​как co, для выполнения генератора.

Вот более подробный пример того, как вы будете взаимодействовать с генератором вручную. Это должно помочь вам понять результаты, которые вы получаете.

function * myGenerator () {
  var a = yield 'some value';
  return a;
}
var iterator = myGenerator();
// above line just instantiates the generator
console.log(iterator);
// empty object returned
// {}

var res1 = iterator.next();
// calling next() start the generator to either the 
// first yield statement or to return.
console.log(res1);
// res1 is an object with 2 attributes 
// { value: 'some value', done: false }
// value is whatever value was to the right of the first 
// yield statment 
// done is an indication that the generator hasn't run
// to completion... ie there is more to do
var toReturn = 'Yield returned: ' + res1.value;
var res2 = iterator.next(toReturn);
// calling next(toReturn) passes the value of 
// the variable toReturn as the return of the yield 
// so it's returned to the variable a in the generator 
console.log(res2);
// res2 is an object with 2 attributes 
// { value: 'Yield returned: some value', done: true }
// no further yield statements so the 'value' is whatever
// is returned by the generator.
// since the generator was run to completion 
// done is returned as true
person James Moore    schedule 12.05.2015
comment
Ok. Теперь у меня есть: { 'внутри: ': {object_with_results}, но все же: { 'снаружи': {} } - person Oskar Kamiński; 12.05.2015