使用yield等待异步代码完成

Get*_*awn 2 javascript yield node.js

我正在尝试学习如何使用生成器和产量,所以我尝试了以下但它似乎没有工作.

我使用以下函数,其中包含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;
};
Run Code Online (Sandbox Code Playgroud)

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

var qs = require("querystring");

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

当我在控制台中恢复输出时,我得到了这个:

{}
Run Code Online (Sandbox Code Playgroud)

我期待着一些像200.我做错了什么?

Nor*_*ard 8

TL; DR

对于简短的回答,你正在寻找像co这样的帮手.

var co = require("co");
co(myGen( )).then(function (result) { });
Run Code Online (Sandbox Code Playgroud)

但为什么?

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
Run Code Online (Sandbox Code Playgroud)

.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
Run Code Online (Sandbox Code Playgroud)

考虑可能会令人困惑,但当你屈服它就像一个回报声明; 左手边尚未分配的价值还没有......而更重要的是,如果你已经把var y = (yield x) + 1;括号得到解决之前,该表达式的其余部分......这样你就回,将+ 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);
});
Run Code Online (Sandbox Code Playgroud)

没有魔力.
我没有返回一个值,而是回复了一个承诺.
当承诺完成时,我正在推回结果,使用.next( result ),这让我得到了另一个承诺.

当这个承诺结束时,.next( newResult )我会把它推回去,使用等等,直到我完成为止.


我们可以做得更好吗?

我们现在知道我们只是在等待promise的解析,然后.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) { });
Run Code Online (Sandbox Code Playgroud)

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

这些事情并不难实现,但它们使得这个例子变得比它需要的更加模糊.

这正是为什么co或其他"coroutine"或"spawn"方法对于这些东西来说是完美的.

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));
Run Code Online (Sandbox Code Playgroud)

asyncawait关键字一起使用时,以实现相同的功能的协程包裹承诺高产发生器,没有所有的外部样板的(并与发动机级优化,最终).

如果您使用像BabelJS这样的转发器,今天就可以试试这个.

我希望这有帮助.