Javascript 使用yield等待异步代码完成

Javascript 使用yield等待异步代码完成,javascript,node.js,yield,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.c

我试图学习如何使用发电机和产量,所以我尝试了下面的方法,但似乎不起作用

我正在使用以下函数,其中包含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
这样的数字。我做错了什么?

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

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

for (var i of gen()) {
  console.log(i);
}
函数*gen(){
var i=0;
而(i<10){
产量i++;
}
}
for(gen()的变量i){
控制台日志(i);
}
仅仅用星号(生成器函数)调用函数只会创建生成器对象(这就是为什么您在控制台中看到
{}
),可以使用
next
函数与之交互

也就是说,您可以使用生成器函数作为异步函数的模拟,但您需要一个特殊的运行程序,如。

TL;博士 简而言之,您正在寻找像co这样的助手

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
这可能会让人困惑,但当你屈服时,它就像一个回报声明;左侧尚未指定值。。。更重要的是,如果你把
vary=(收益率x)+1在表达式的其余部分之前解析括号。。。因此您返回,+1被保留,直到返回一个值。
然后,当它到达时(通过
.next()
传入),表达式的其余部分将被计算(然后分配到左侧)

每次调用返回的对象有两个属性
{value:…,done:false}

value
是您返回/产生的内容,
done
是它是否在函数末尾命中实际返回语句(包括隐式返回)

这是可以用来实现异步魔法的部分

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)
等把它推回去,直到我完成为止


我们能做得更好吗? 我们现在知道,我们只是在等待承诺得到解决,然后在迭代器上调用
.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包括额外的基数(确保所有收益都像承诺一样处理,这样你就不会因为传递一个非承诺值而崩溃……或者允许产生承诺数组,这将成为一个承诺,它将返回该收益的结果数组……或者尝试/捕获承诺处理,将错误扔回迭代器……是的,try/catch有效通过迭代器上的
.throw(err)
方法,可以很好地处理yield语句

这些东西并不难实现,但它们使示例比需要的更为模糊

这就是为什么co或其他一些“协同程序”或“生成”方法非常适合这种东西的原因

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


但是等等,还有更多! 从ES7开始,规范很可能会为这个确切的用例添加语言

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

doAsyncProcess(123).then(values => doStuff(values));
async
await
关键字一起使用,以实现与协同程序生成器相同的功能,而无需所有外部样板文件(并最终进行引擎级优化)

如果您使用的是像BabelJS这样的transpiler,您今天就可以试试这个

我希望这有帮助

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
生成器函数的迭代器。您可以通过
total.next().value
检索其
yield
值。但是,mongodb库是基于回调的,因此其函数不返回值,因此
yield
将返回null

您提到您在其他地方使用了Promission;我建议您看看,尤其是它的功能。Promisification反转回调模型,以便回调的参数现在用于解析promisified函数。更好的是,它将转换整个基于回调的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)

您可能想研究如何使异步调用有组织且简单…但这并不能帮助我了解它是如何工作的…我在另一种方法中使用了
Promise
,这与此相同吗?不。生成承诺是您需要在迭代器内部执行的操作,从生成器的实例化返回。
co
接受这些产生的承诺,并将其值返回到下一轮迭代器中。这里的第一个看起来像是使用mongo API的一个非常简洁的示例。您如何重写它,使DB连接不在函数中?即如何等待一件事,然后再做下一件事?