使用yield等待异步代码完成

时间:2015-04-23 15:05:42

标签: javascript node.js yield

我正在尝试学习如何使用发电机和产量,所以我尝试了以下但是它似乎没有起作用。

我正在使用以下函数,其中包含2个异步调用:

var client = require('mongodb').MongoClient;

$db = function*(collection, obj){
    var documents;
    yield client.connect('mongodb://localhost/test', function*(err, db){
        var c = db.collection(collection);
        yield c.find(obj).toArray(function(err, docs){
            documents = docs;
            db.close();
        });
    });
    return documents.length;
};

然后拨打电话原始电话,我这样做:

var qs = require("querystring");

var query = qs.parse("keywords[]=abc&keywords[]=123");
var total = $db("ads", {"details.keywords": {$in: query["keywords[]"]}});
console.log(total);

当我在控制台中恢复输出时,我明白了:

{}

我期待像200这样的数字。我做错了什么?

3 个答案:

答案 0 :(得分:8)

TL; DR

对于简短的回答,您正在寻找像 co <​​/ strong>这样的帮手。

var co = require("co");
co(myGen( )).then(function (result) { });

但是为什么?

ES6迭代器或定义它们的生成器没有任何本质上的异步。

function * allIntegers ( ) {
    var i = 1;
    while (true) {
      yield i;
      i += 1;
    }
}

var ints = allIntegers();
ints.next().value; // 1
ints.next().value; // 2
ints.next().value; // 3

.next( )方法实际上允许您将数据中的发送回迭代器。

function * exampleGen ( ) {
  var a = yield undefined;
  var b = yield a + 1;
  return b;
}

var exampleIter = exampleGen();
exampleIter.next().value; // undefined
exampleIter.next(12).value; // 13 (I passed 12 back in, which is assigned to a)
exampleIter.next("Hi").value; // "Hi" is assigned to b, and then returned

考虑可能会令人困惑,但是当你屈服它时,它就像一个回报声明;左侧尚未分配值... ...更重要的是,如果您已将var y = (yield x) + 1;括号解析为 之前 < / strong>表达式的其余部分......所以你返回,并且+1被置于保持状态,直到值返回为止 然后当它到达时(通过.next( )传递),表达式的其余部分将被评估(然后分配到左侧)。

每次通话返回的对象都有两个属性{ value: ..., done: false }
value是你返回/产生的,done是它是否在函数结束时命中了实际的return语句(包括隐式返回)。

这是可以用来使这种异步魔法发生的部分。

function * asyncGen ( id ) {
  var key = yield getKeyPromise( id );
  var values = yield getValuesPromise( key );

  return values;
}

var asyncProcess = asyncGen( 123 );
var getKey = asyncProcess.next( ).value;

getKey.then(function (key) {
  return asyncProcess.next( key ).value;
}).then(function (values) {
  doStuff(values);
});

没有魔力 我没有回复价值,而是回复了承诺 当承诺完成后,我使用.next( result )将结果推回来,这让我得到了另一个承诺。

当这个承诺解决之后,我会使用.next( newResult )等来推回,直到我完成。

我们可以做得更好吗?

我们现在知道我们只是等待promises解析,然后在迭代器上用结果调用.next

我们必须提前了解迭代器的样子,知道我们何时完成?

不是。

function coroutine (iterator) {
  return new Promise(function (resolve, reject) {
    function turnIterator (value) {
      var result = iterator.next( value );
      if (result.done) {
        resolve(result.value);
      } else {
        result.value.then(turnIterator);
      }
    }

    turnIterator();
  };
}


coroutine( myGen ).then(function (result) { });

这不完整和完美。 co <​​/ strong>涵盖了额外的基础(确保所有收益率都像承诺一样对待,所以你不要通过传递非承诺价值而放弃......或者允许数组承诺yielding,这将成为一个promise,它将返回该yield的结果数组...或者尝试/捕获promise处理,将错误抛回到迭代器中...是的,try / catch与yield语句完美配合,这样做,多亏了迭代器上的.throw(err)方法。

这些事情并不难实现,但它们使得这个例子比实际情况更加模糊。

这正是 co <​​/ strong>或其他一些&#34; coroutine&#34;或者&#34;产生&#34;方法对于这些东西来说是完美的。

Express服务器背后的人建立了KoaJS,使用Co作为库,而Koa的中间件系统只需要用.use方法生成生成器并做正确的事。

但等等,还有更多!

从ES7开始,规范很可能会为这个确切的用例添加语言。

async function doAsyncProcess (id) {
  var key = await getKeyPromise(id);
  var values = await getValuesPromise(key);
  return values;
}

doAsyncProcess(123).then(values => doStuff(values));

asyncawait关键字一起使用,以实现与协程包装的promise-Yielding生成器相同的功能,而不需要所有外部样板(并且最终具有引擎级优化) )。

如果您使用 BabelJS 之类的转录程序,今天就可以试试。

我希望这会有所帮助。

答案 1 :(得分:1)

Yield和generator与异步无关,它们的主要目的是生成可迭代的值序列,如下所示:

function * gen() {
  var i = 0;
  while (i < 10) {
    yield i++;
  }
}

for (var i of gen()) {
  console.log(i);
}

只使用星号(生成器函数)调用函数只会创建生成器对象(这就是您在控制台中看到{}的原因),可以使用next函数进行交互。

也就是说,你可以使用生成器函数作为异步函数的模拟,但是你需要一个特殊的运算符,比如co

答案 2 :(得分:1)

var client = require('mongodb').MongoClient;

$db = function*(collection, obj){
    var documents;
    yield client.connect('mongodb://localhost/test', function*(err, db){
        var c = db.collection(collection);
        yield c.find(obj).toArray(function(err, docs){
            documents = docs;
            db.close();
        });
    });
    return documents.length;
};    
var qs = require("querystring");

var query = qs.parse("keywords[]=abc&keywords[]=123");
var total = $db("ads", {"details.keywords": {$in: query["keywords[]"]}});
console.log(total);

原样,total$db生成器函数的迭代器。您可以通过yield检索其total.next().value值。但是,mongodb库是基于回调的,因此,它的函数不返回值,因此yield将返回null。

你提到你在其他地方使用Promises;我建议您查看bluebird,特别是其promisify功能。 Promisification反转回调模型,以便回调的参数现在用于解析promisified函数。更好的是,promisifyAll将转换整个基于回调的API。

最后,bluebird也提供了协程功能;但它的协同程序必须返回承诺。因此,您的代码可能会被重写如下:

var mongo = require('mongodb');
var Promise = require('bluebird');

//here we convert the mongodb callback based API to a promised based API
Promise.promisifyAll(mongo);

$db = Promise.coroutine(function*(collection, obj){
//existing functions are converted to promised based versions which have
//the same name with 'Async' appended to them
    return yield mongo.MongoClient.connectAsync('mongodb://localhost/test')
                .then(function(db){
                  return db.collectionAsync(collection);})
                .then(function(collection) {
                  return collection.countAsync();});
});

var qs = require("querystring");
var query = qs.parse("keywords[]=abc&keywords[]=123");
$db('ads',{"details.keywords": {$in: query["keywords[]"]}})
.then(console.log)