Non-recursive method to iterate over Promise itera

2019-04-08 05:31发布

问题:

I've developed a client library that exposes a method called iterator(). This method returns a Promise instance created using require('promise') library, which is completed with an iterator object.

This object contains a method called next() which returns a Promise which is completed with a complex object like this: {done: [true|false], key: _, value: _}

Although iterator() might pre-fetch some elements, next() needs to return a Promise in case it results in a remote call.

Now, say a user wants to iterate over all elements until the Promise returned by next() returns an object containing done: true.

I've managed to achieve this using the following recursive method (I originally found this solution in this answer):

var iterate = client.iterator();

iterateTeams.then(function(it) {

  function loop(promise, fn) {
    // Simple recursive loop over iterator's next() call
    return promise.then(fn).then(function (entry) {
      return !entry.done ? loop(it.next(), fn) : entry;
    });
  }

  return loop(it.next(), function (entry) {
    console.log('entry is: ' + entry);
    return entry;
  });

});

The question is, would it be possible, using require('promise') library, to build a non-recursive solution? The reason I'm interested in a non-recursive method is to avoid blowing up if the number of entries to iterate over is too big.

Cheers, Galder

回答1:

The reason I'm interested in a non-recursive method is to avoid blowing up if the number of entries to iterate over is too big

Don't fear. Asynchronous "recursion" (sometimes dubbed pseudo-recursion) does not grow the call stack, it's much like tail recursion. You won't ever get a stackoverflow exception.

And if the promise library is implemented reasonably, this should not even grow the memory - see Building a promise chain recursively in javascript - memory considerations for details.



回答2:

Without new syntax or a library - generally no.

Well, if you're using babel, you can use ES2018 (:P) async iteration:

for await (const team of iterateTeams) {
   // do something with team
}

read more about it here

Otherwise, you can use generators with ES2016 async/await syntax:

for(var it = iterateTeams(); !done; ({done, value}) = await it.next()) {
    // work with value
}     

Or with available today ES2015 generator syntax and a pump through bluebird:

// inside a Promise.corutine  
for(var it = iterateTeams(); !done; ({done, value}) = yield it.next()) {
   // work with value
}