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

How can I convert this async callback to a generator?

我很难理解生成器。但我认为我正在尝试做的事情应该是可能的。

我有一个可以访问 Page 的对象 Topic。最初 Topic 的实现方式是通过回调检索 Pages。

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(从 "To use a generator ..." 开始)的(糟糕的)理解,我认为这可能有效:

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 等...)and/or 令人费解的 thunk 生成器,你有什么?

I do want to refactor Topic such that I can access it's pages through a generator, in stead of through a callback. Is that doable, without too much hassle?

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

  • 生成器用于创建产生结果值的迭代器,类似于result迭代器
  • generators abuses to create an iterator yielding asynchronous action,从外部异步驱动以允许内部同步控制结构(另请参阅 here for deeper explanation,或继续阅读您链接的那篇 davidwalsh 文章)

有想法 merge the two concepts once the second has gotten its own keywords (async/await), but that's not applicable yet (the async iteration proposal 在第 3 阶段)。您被 "nested" 结构所困,异步回调中的类似数组的迭代器。

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


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

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 方法),但这可能会让人感到困惑。他们将保持独立的发电机。