javascript - 使用 yield 等待异步代码完成

标签 javascript node.js yield

我正在尝试学习如何使用生成器和 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。我做错了什么?

最佳答案

长话短说

对于简短的回答,您正在寻找像 co 这样的 helper 。

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

这可能会令人困惑,但当你 yield 时,它就像一个 return 语句;左侧尚未分配值... ...更重要的是,如果您将 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);
});

没有魔法。
我没有返回一个值,而是返回一个 promise 。
当 promise 完成时,我使用 .next( result ) 将结果推回,这让我得到了另一个 promise 。

当该 promise 解决时,我使用 .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 涵盖额外的基础(确保所有 yield 都像 promise 一样对待,所以你不会通过传递非 promise 值而爆炸......或允许产生 promise 数组,这成为一个 promise ,它将返回该 yield 的结果数组 ...方式,感谢迭代器上的 .throw(err) 方法)。

这些东西并不难实现,但它们使示例比需要的更加困惑。

这正是为什么 co 或其他一些“协程”或“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));

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

如果您正在使用像 BabelJS 这样的转译器,您今天就可以试试这个。

希望对您有所帮助。

关于javascript - 使用 yield 等待异步代码完成,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29827443/

相关文章:

rust - Rust 中是否有一个特性允许在没有协程等实验性功能的情况下按组进行折叠?

javascript - 在 Rails 应用程序中更改页面时,Google 图表消失

javascript - Angular 中的自定义自动滚动顶部指令

javascript - 将 gulp-file 分成几个文件

javascript - 使用 Promises 执行流程,将一个函数的输出作为下一个函数的输入

node.js - 如何从express.js中的另一个函数调用一个函数?

javascript - <a> 来自签名模板网页表单字段的 href 信息未按预期工作

javascript - 连接 JQuery 变量

javascript - 当 promise 在 javascript 中产生时会发生什么?

生成器的 Python 循环行为