在 node.js 中使用生成器函数 next() 作为回调

Using generator function next() as a callback in node.js

我正在编写一些 node.js 以通过串行端口连接与传感器进行交互。读取传感器的代码自然是异步的。不过,在我的控制代码中,我需要读取一个传感器,根据值做一些事情,再次读取,做其他事情等等。为此,我使用了如下自包含测试的代码:

var main = new Main();
main.next();

function* Main()
{
  var reading = yield readSensor(this.next.bind(this));
  console.log(reading);

  var reading = yield readSensor(this.next.bind(this));
  console.log(reading);
}

function readSensor(callback)
{
  // simulate asynchrounous callback from reading sensor
  setTimeout(function sensorCallback() { callback('foo'); }, 100);
}

所以,我的顺序控制代码在一个生成器中,当它需要读取时,它会生成 readSensor()。传感器读取完成后,它会调用回调,并将 returns 控制到主代码。我这样做是因为我可能需要根据以前的读数以不同的顺序读取各种传感器。所以,这是有问题的部分:我将 this.next.bind(this) 作为回调传递给异步读取函数。该代码似乎在启用生成器时有效(--harmony_generators),但我想知道这里是否存在我遗漏的陷阱。我对 JS 比较陌生,所以不要害怕指出显而易见的地方:)

我没有深入研究 ES6 生成器,但是让生成器将其自己的 .next 作为回调传递给另一个函数并不适合我。如果有的话,它可能会造成 readSensor 失败并且您无法处理失败的情况,最终陷入僵局。

我建议修改或包装 readSensor 为 return 承诺,然后使用 this article 中概述的技术。

这将允许您编写这样的代码(已在 Node v0.12.0 中验证工作):

var Promise = require('q');

var main = async(function* () {
    var reading = yield readSensor();
    console.log(reading);

    reading = yield readSensor();
    console.log(reading);
});

main();

function readSensor() {
    return Promise.delay(2000).thenResolve(Math.random() * 100);
}



/***********************************************************
 * From here down,                                         *
 * boilerplate  async() function from article linked above *
 ***********************************************************/

function async(makeGenerator){
  return function () {
    var generator = makeGenerator.apply(this, arguments);

    function handle(result){
      // result => { done: [Boolean], value: [Object] }
      if (result.done) return Promise.resolve(result.value);

      return Promise.resolve(result.value).then(function (res){
        return handle(generator.next(res));
      }, function (err){
        return handle(generator.throw(err));
      });
    }

    try {
      return handle(generator.next());
    } catch (ex) {
      return Promise.reject(ex);
    }
  }
}

正如下面的 loganfsmyth 所指出的,Q 已经提供了一个 Q.async() 方法来提供此 async() 函数的功能,其他 promise 库也可能这样做。

So, here's the questionable part: I pass this.next.bind(this) as a callback to the asynchronous read function. The code seems to work when generators are enabled

不,那行不通。生成器不能像您一样使用 new 构造。来自 the spec:

If the generator was invoked using [[Call]], the this binding will have already been initialized in the normal manner. If the generator was invoked using [[Construct]], the this bind is not initialized and any references to this within the FunctionBody will produce a ReferenceError exception.

可以使用 new 调用生成器函数(请参阅 §9.2.3,使用 [[ConstructorKind]] 的 derived),但它们不会构造实例。

When the sensor reading is done, […] control returns to the main code.

这确实是个聪明的主意。之前有探讨过,见Understanding code flow with yield/generators or this article. Many libraries support this, .

我建议您使用这些库之一,您当前的代码不是很稳定(如果完全支持 ES6 会中断)并且似乎也缺乏错误处理。