【问题标题】:Using generator function next() as a callback in node.js在 node.js 中使用生成器函数 next() 作为回调
【发布时间】:2015-05-12 11:40:24
【问题描述】:

我正在编写一些 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()。当传感器读取完成时,它调用回调,控制返回到主代码。我这样做是因为我可能需要根据以前的读数以不同的顺序从各种传感器读取数据。所以,这是有问题的部分:我将this.next.bind(this) 作为回调传递给异步读取函数。启用生成器时代码似乎可以工作(--harmony_generators),但我想知道这里是否存在我遗漏的陷阱。我对 JS 比较陌生,所以不要害怕指出显而易见的:)

【问题讨论】:

  • 我认为你不能像构造函数那样调用 ES6 生成器。也许这是一个v8错误?让我检查一下。
  • @Bergi 我查看了最新的 ES6 草案规范,但无法确定是否支持。我认为应该是;这将是一种处理嵌套回调问题的轻量级、相对干净的方法。当然,即使您不能将生成器构造为对象以使它们具有this,您也可以通过初始main.next(main); 调用将其“自我”发送给它们,并以初始yield 接收。但是,就目前而言,Q.async 库似乎可以满足我的需求。
  • 我现在已经详细研究了 ES6 草案,可以证实我的怀疑。看我的回答:-)

标签: javascript node.js yield


【解决方案1】:

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

我建议修改或包装readSensor 以返回一个承诺,然后使用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 库也可以这样做。

【讨论】:

  • 完全脱离了主要问题,* 在这种情况下的目的是什么?有一个由内存地址引用的函数?我从来没有在 JS 中看到过。
  • @Vadorequest 它表示generator function
  • 谢谢,几个月前我读过 ES6 的特性,不过完全忘记了!它相对较新。
  • 谢谢。我在研究该问题时简要查看了 Q.async,但被有关示例的警告吓跑了。当然,我猜同样的警告也适用于此时使用生成器的任何东西。我可能会采用这种方法。我想作为一名长期的 C(++) 程序员,我只是对如何需要使用库来获得非常基本的控制流感到震惊。话又说回来,从 JS 迁移到 C(++),我可以想象我需要处理信号量、互斥锁等以防止我的代码自毁而感到震惊。
  • @tyapo 在单个线程上使用类似代码的同步语法运行并行异步操作并不是我所说的“非常基本的控制流”。确实,您必须跳过一些障碍才能做一些理所当然的事情,这些事情在使用阻塞代码来做这些事情的语言中是理所当然的,但最终结果要好得多。上面完整复制的async() 函数是您使用生成器函数获得这种神奇的 async/await 语法所需的全部内容,并且您应该能够将其与任何 Promise 库或可能是内置的 Promise 结合起来,一旦它们准备好了。
【解决方案2】:

所以,这是有问题的部分:我将 this.next.bind(this) 作为回调传递给异步读取函数。启用生成器后,代码似乎可以工作

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

如果使用 [[Call]] 调用生成器,this 绑定将已经以正常方式初始化。如果生成器是使用 [[Construct]] 调用的,this 绑定不会被初始化,并且 FunctionBody 中对this 的任何引用都会产生ReferenceError 异常。

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

传感器读取完成后,[…] 控制返回到主代码。

这确实是一个聪明的主意。之前已经探索过了,见Understanding code flow with yield/generatorsthis article。许多库都支持这一点,especially combined with promises

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

【讨论】:

    猜你喜欢
    • 2018-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-20
    • 2013-05-24
    相关资源
    最近更新 更多