【问题标题】:Generators in KOAKOA 中的生成器
【发布时间】:2015-05-12 17:53:32
【问题描述】:

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: [Function], done: false } }

这是我所期望的:

{ '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)});

现在控制台日志显示我的结果很好,但控制台日志外部显示我只是空对象。

【问题讨论】:

    标签: javascript node.js koa


    【解决方案1】:

    生成器是组织异步代码的强大工具,但它们不会神奇地等待异步代码运行。

    发生了什么

    让我们浏览一下您的代码,以便您了解发生了什么:

    getRelationsList 是一个生成器函数,调用时会返回一个新的生成器。此时,您的生成器函数中没有任何代码被调用(尽管如果您传递参数,它们将被设置)。然后在生成器上调用.next 以开始执行生成器函数。它将一直执行,直到遇到第一个 yield 语句并返回一个带有产生值和生成器完成状态的对象。

    到目前为止,您似乎了解了大部分内容,但是生成器并没有神奇地转换产生的值。当您输出db.relations.find({}) 时,您将获得find 函数的返回值,我假设它是Promise 或某种类型的thenable:

    所以你的“外部”值是{ value:Promise, done:false }

    您的内部console.log 从未运行过的原因是,您实际上每次调用getRelationsList() 时都在创建一个新生成器,所以当您在外部console.log 之后再次调用getRelationsList().next() 时,您正在创建一个新的生成器generator 并调用 next,所以它只执行到第一个 yield,就像上一行的调用一样。

    为了完成执行,您必须在生成器的同一个实例上调用 next 两次:一次执行到 yield,一次继续执行到函数结束。

    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 的小库,它基本上会提取出 promise 并等待它们完成,然后再将解析的值传递回生成器函数(使用 .next() 函数),以便您可以编写同步风格的异步代码。

    co 将返回一个 promise,这将要求您调用 .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)
    })
    

    【讨论】:

      【解决方案2】:

      您的问题是您多次调用 getRelationsList() 函数,这是不正确的。

      将您的代码更改为以下

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

      【讨论】:

        【解决方案3】:

        生成器必须由外部代码执行。 在 koa 底层使用 co 库来“运行”生成器。

        以下是您如何在 koa 之外实现您想要的:

        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
        

        【讨论】:

        • 好的。现在我有: { 'inside: ': {object_with_results} 但仍然: { 'outside ': {} }
        猜你喜欢
        • 2015-10-11
        • 2017-08-16
        • 1970-01-01
        • 2014-05-31
        • 2016-04-29
        • 2017-03-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多