0

I'm trying to familiarise myself with Promises and how they work. While it's a new concept to me I'm relatively sure I can understand most of it. In particular I've been looking at the BlueBird library and working my way through the examples. However there's one code snippet on the page that I can't quite wrap my head around.

Promise.promisifyAll(needle);
var options = {};

var current = Promise.resolve();
Promise.map(URLs, function(URL) {
    current = current.then(function () {
        return needle.getAsync(URL, options);
    });
    return current;
}).map(function(responseAndBody){
    return JSON.parse(responseAndBody[1]);
}).then(function (results) {
    return processAndSaveAllInDB(results);
}).then(function(){
    console.log('All Needle requests saved');
}).catch(function (e) {
    console.log(e);
});

In this code I understand that the needle library is being promisified. I think I'd be right in saying that current is being set to an empty promise.

My question is around the

current = current.then(function () {
    return needle.getAsync(URL, options);
});
return current;

code. If needle has been promisified, what's the purpose of nesting it within another promise?

  • 1
    That's a *horrible* way of chaining the asynchronous actions. It [queues the `needle.getAsync` calls over the `URLs` array](http://stackoverflow.com/a/23650478/1048572). – Bergi Jan 29 '15 at 17:50
  • This mix of a `map` and a `reduce` should be used by its name: `scan`. – Bergi Jan 29 '15 at 17:50
  • I certainly had my doubts that it was nice code. Thanks for clearing it up. If you want to put this as an answer I can accept it. –  Jan 29 '15 at 18:44
  • 1
    Meh, actually it's not that horrible. Rather "tricky" and "non-obvious" :) – Bergi Jan 29 '15 at 19:51

2 Answers2

0

That's a dubious way of queuing the asynchronous calls and at the same time yielding them to the map function so that a promise for an array of all the results is produced.

So what does it do? The queuing alone is typically done using reduce, that loop with the current accumulator does the same. It starts with the empty promise, and repeatedly chains a callback for every url in the array; like this code does:

var current = Promise.resolve().then(function() {
    return needle.getAsync(URL[0], options);
}).then(function() {
    return needle.getAsync(URL[1], options);
}).then(function() {
    return needle.getAsync(URL[2], options);
}) …

However, being used in a map loop it actually generates an array of single promises, like

var promises = [];
promises[0] = Promise.resolve().then(function() {
    return needle.getAsync(URL[0], options);
});
promises[1] = promises[0].then(function() {
    return needle.getAsync(URL[1], options);
});
promises[2] = promises[1].then(function() {
    return needle.getAsync(URL[2], options);
});
…

I would either use the reduce way, starting with Promise.resolve([]) and adding to that result array step-by-step, or write a dedicated scan (or mapAccum, whatever you want to name it) function and use that in combination with Promise.all.

Or much better, just use Bluebird's built-in {concurrency: 1} option for Promise.map!

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I thought it would have to be doing something like that. Thanks so much for clearing it up! –  Jan 30 '15 at 07:54
0

Bergi wrote a good answer which you should read. Here is how I'd write that code using .each:

var get = Promise.promisify(needle.get);

Promise.each(URLs, get). // make the requests
       map(function(x){ return JSON.parse(x[1])} ). // parse response as json
       then(processAndSaveAllInDB).
       then(function(){ console.log("All Needle requests saved"); }).

Note you don't need the catch since Bluebird will find out about the unhandled rejection and report it for you in this case.

Usually when people do what the code above does they care about order although it was not guaranteed in the original code (map changed behaviour in bluebird 2 to be much faster but not guarantee queueing order).

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • Very interesting implementation of it. Thanks for the input! –  Jan 30 '15 at 07:54
  • How does this actually work? In the docs for `each`, I read "*Resolves to the original array unmodified, this method is meant to be used for side effects.*" So the results of the `get` calls aren't passed to those parser functions? – Bergi Jan 30 '15 at 08:12
  • @Bergi oh woops, better get that changed in bluebird 3.0 – Benjamin Gruenbaum Jan 30 '15 at 08:16
  • @Bergi yes it will get changed in bluebird 3.0.. which is soon I just need to finish doing the website – Esailija Jan 30 '15 at 21:57
  • @Esailija: But `each` for side effects sounds fine… I'd rather call it `mapSeries` or so if it returns the results for each element. – Bergi Jan 31 '15 at 10:13
  • 1
    @Bergi simply changing the return value from useless to useful doesn't warrant a new method name, you can simply ignore the return value (like you always probably did) and it will function exactly the same – Esailija Jan 31 '15 at 11:14