3

This is my first shot at nested promises. I'm using the bluebird library but I think the idea is the same for all of the promise libraries.

At a high level, this is what I'm trying to do:

myService.getSomeData(url)
 .then((data) => {
   myOtherService.getMoreData(data.uniqueId)
   .then((thisDataIsAnArray) => {
      //loop over the data above and do something
   });
 });

getMoreData() is supposed to make X service calls and store the results in an array X elements long. This is where I start getting lost, in that I'm not sure how to craft this method and what I should be returning from it. I've taken a few stabs at bluebird's Promise.all and Promise.map but am floundering and thought I'd solicit suggestions.

jkj2000
  • 1,563
  • 4
  • 19
  • 26
  • Trying to understand your goal. So, you want to execute a set of promises then execute another set of promises given the resolutions from the first set of promises, then execute a third set of logic based on the resolutions from the second set of promises? One of the the goals of promises is to remove "callback hell." As a result, it could be nice for you to chain promises rather than nest them. – jpodwys May 06 '16 at 21:15
  • 3
    You might want to start with [flattening your chain](http://stackoverflow.com/a/22000931/1048572). – Bergi May 06 '16 at 21:31
  • 1
    Every asynchronous method should *`return` a promise* for the result of the asynchronous actions inside it. Every single one. – Bergi May 06 '16 at 21:32
  • @jpodwys the main idea is to execute that first promise, take the data resulting from it, then execute X more promises (where the number depends on the original result) and return their results in an array we can work with. – jkj2000 May 06 '16 at 23:13
  • @Bergi thanks for the link and the advice on returning promises from every async method, I'm flattening things out now and putting your other suggestion to use, and it's helping. – jkj2000 May 06 '16 at 23:14
  • 1
    Not sure exactly what you're asking, but this might help: [How to chain and share prior results with Promises](http://stackoverflow.com/questions/28714298/how-to-chain-and-share-prior-results-with-promises/28714863#28714863). – jfriend00 May 07 '16 at 02:46

2 Answers2

5

Return all the promises!

Promises are just return values you attach callbacks to, instead of passing callbacks into functions. Unless you return all of them, there's no way for the callbacks to chain, or catch all their errors.

Also, return from all the .then's the instant you have another promise. This flattens things.

jib
  • 40,579
  • 17
  • 100
  • 158
2

Promise iteration totally warped my brain the first time I tried it as well. I think Bluebird's documentation does a fairly poor job distinguishing the common use cases but I'll not go on about it because (a) I love Bluebird, and (b) I don't have the time to update the docs.

I feel like Promise.map is the right thing for your scenario.

myService.getSomeData(url)
    .then((data) => 
    {
        return myOtherService.getMoreData(data.uniqueId)
    })
    .map((item) =>
    {
        return doSomethingWithData(item);
    })
    .then((results) =>
    {
        // do something with the result array. 
    });

Depending on what you want to do with the results, where I've used .map you can also use .reduce, or .each. Note that .each does not modify the return value from the promise to which it's chained, hence the "use only for side-effects" comment in the Bluebird docs.

The difference between the instance and static methods is, of course, that with static you must supply the array, e.g. Promise.map(array, (item) => {}).

Also, as @jib said - always return a value inside your callbacks. This will save you much pain down the line.

  • Thanks for the example. It seems that `doSomethingWithData()` needs to return a promise in the example-- what if it doesn't, and just returns the number 2 for instance? Should I wrap the call to it in `Promise.promisify` or something along those lines? – jkj2000 May 10 '16 at 22:38
  • @jkj2000 if `doSomethingWithData(item)` is async you'll want to return a promise. If the signature is really `doSomethingWithData(item, callback)` then `Promise.promisify` is a good idea, or if it implemented yet then just consider a promise-based implementation from the start. In general, no you don't have to return a promise, any value is okay, even mixtures of promise and non-promise values. – Paul Midgen May 11 '16 at 21:14