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

How can I convert this async callback to a generator?

本文关键字:转换 回调 异步      更新时间:2023-09-26

我很难理解生成器。但我认为我想做的应该是可能的。

我有一个对象Topic,它可以访问Pages。最初实现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是从回调中调用的。

然后,基于我对这篇文章的(糟糕的)理解(从"使用生成器…"开始),我认为这可能会奏效:

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生成器,你有什么?

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

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

  • 用于创建产生结果值的迭代器的生成器,类似于result迭代器
  • 生成器被滥用来创建一个产生异步操作的迭代器,该迭代器从外部异步驱动,以允许内部的同步控制结构(请参阅此处以获得更深入的解释,或继续阅读您链接的davidwalsh文章)

有一些想法是,一旦第二个获得了自己的关键字(async/await),就可以合并这两个概念,但这还不适用(异步迭代方案处于第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方法中),但这可能非常令人困惑。它们将保持独立的发电机。