Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
124 views
in Technique[技术] by (71.8m points)

javascript - How can I convert this async callback to a generator?

I have a hard time understanding generators. But I think what I'm trying to do should be possible.

I have an object Topic that has access to Pages. Originally Topic was implemented such that Pages would be retrieved through a callback.

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
} );

Now, let's assume I cannot refactor PageRepository's callback mechanism, but 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 hastle?

I know I can iterate generator values with a for...of statement, like:

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

... so I came up of with something like the following:

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
      }
    } );
  }
}

However, this doesn't work, probably because yield is called from within the callback.

Then, based my (poor) understandings of this article (from "To use a generator ..." onward), I thought this might work:

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
  }
}

But this doesn't work either, unfortunately.

So, is what I'm trying to achieve doable, without too much hassle; meaning: no modules (like co, suspend, etc...) and/or convoluted thunk generators and what have you?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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?

No, not really. You are mixing two concepts here:

  • generators used to create iterators yielding result values, similar to the result iterator
  • generators abused to create an iterator yielding asynchronous actions, that is driven asynchronously from the outside to allow synchronous control structures on the inside (see also here for deeper explanation, or continue reading that davidwalsh article you linked)

There are ideas to merge the two concepts once the second has gotten its own keywords (async/await), but that's not applicable yet (the async iteration proposal is in stage 3). You are stuck with a "nested" structure, that array-like iterator inside an asynchronous callback.

So you can use a generator for either, but not one for both. And it's questionable whether that's worth it. Notice that neither will make your code synchronous, you will always have to use async callbacks in some regard.


Call back with a generator instance, not an array:

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
  }
} );

Don't call back a function, but resume a generator (like in the simplest async example):

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();

Yes, you can use both approaches at once (passing the generator-created iterator into the next method of the async generator), but that's probably quite confusing. And they'll stay separate generators.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...