如何将此异步回调转换为生成器?

时间:2015-03-26 13:49:58

标签: javascript asynchronous callback generator ecmascript-6

我很难理解发电机。但我认为我应该做的事情应该是可能的。

我有一个可以访问Topic的对象Page。最初实现了Topic,以便通过回调检索Page

var Topic = function( id ) {

  var repository = new PageRepository();

  this.id = id;
  this.getAllPages = function( callback ) {
    repository.getAllPagesByTopicId( this.id, function( result ) {
      var pages = [];
      while( result.hasNext() ) {
        pages.push( result.next() );
      }
      callback( pages );
    } );
  }
}

var topic = new Topic( 1 );
topic.getAllPages( function( pages ) {
  console.log( pages ) // received Page instances
} );

现在,让我们假设我无法重构PageRepository的回调机制,但我想要重构Topic以便我可以访问它通过发电机的页面,而不是通过回调。这是可行的,没有太多的麻烦吗?

我知道我可以使用for...of语句迭代生成器值,例如:

var topic = new Topic( 1 );
for( let page of topic.pages() ) {  // create the generator
  console.log( page ); // received next Page
}

...所以我提出了以下内容:

var Topic = function( id ) {

  ...

  this.pages = function*() { // refactored getAllPages () to a generator function pages()
    repository.getAllPagesByTopicId( this.id, function( result ) {
      while( result.hasNext() ) {
        yield result.next(); // yield the next Page
      }
    } );
  }
}

但是,这不起作用,可能是因为在回调中调用yield

然后,根据我对this article(来自"使用生成器......"以后)的(差)理解,我认为这可能有效:

var Topic = function( id ) {

  ...

  this.pages = function*() {

    let gen = function*() {}(); // create an inner generator

    // not actually sure why the following wrapper function is needed
    // but something similar is used in mentioned article
    yield function() {
      repository.getAllPagesByTopicId( this.id, function( result ) {
        while( result.hasNext() ) {
          gen.next( result.next() ); // call next() on inner generator
        }
      } );
    }(); // immediately create this mysterious wrapper function
  }
}

但不幸的是,这也不起作用。

所以,正是我努力实现的目标,没有太多的麻烦;意思是:没有模块(比如co,suspend等等)和/或复杂的thunk生成器以及你有什么?

1 个答案:

答案 0 :(得分:3)

  

我确实想重构Topic,以便我可以通过生成器访问它的页面,而不是通过回调。这是可行的,没有太多的麻烦吗?

不,不是真的。你在这里混合了两个概念:

  • 用于创建迭代器的生成器,产生结果值,类似于result迭代器
  • 生成器被滥用来创建一个迭代器,产生异步操作,从外部异步驱动以允许内部的同步控制结构(另请参阅here for deeper explanation,或继续阅读您链接的davidwalsh文章)

一旦第二个获得了自己的关键字async / await),就有merge the two concepts的想法,但这还不适用(async iteration proposal在阶段3)。你被一个"嵌套"结构,异步回调内的类似数组的迭代器。

因此,您可以使用生成器,但不能同时使用两者。值得怀疑的是,这是值得怀疑的。请注意,两者都不会使您的代码同步,在某些方面您将始终必须使用异步回调。


使用生成器实例回调,而不是数组:

function Topic( id ) {
  var repository = new PageRepository();
  this.id = id;
  this.getAllPages = function( callback ) {
    repository.getAllPagesByTopicId( this.id, function( result ) {
      callback( function* () {
        while( result.hasNext() ) {
          yield result.next();
        }
      }() );
    } );
  }
}

var topic = new Topic( 1 );
topic.getAllPages( function( pageiterator ) {
  for( let page of pageiterator ) {
    console.log( page ); // received next Page
  }
} );

请勿回调函数,但要恢复生成器(例如最简单的异步示例):

function Topic( id ) {
  var repository = new PageRepository();
  this.id = id;
  this.getAllPages = function( it ) {
    repository.getAllPagesByTopicId( this.id, function( result ) {
      for (var pages = [], result.hasNext(); ) pages.push( result.next() );
      it.next( pages );
    } );
  }
}

var it = (function *() {
  var topic = new Topic( 1 );
  var pages = yield topic.getAllPages( it );
  console.log( pages ) // received Page instances
}());
it.next();

是的,您可以一次使用这两种方法(将生成器创建的迭代器传递给异步生成器的next方法),但这可能非常令人困惑。而且他们会留下独立的发电机。