7

There are methods such as Q.reduce and Q.all that help flattening a promise chain on the specific case of heterogeneous collections of promises. Mind, though, the generic case:

const F = (x) => x;
const a = F(1);
const b = F(2);
const c = F(a + b);
const d = F(a + c);
const e = F(b + c);
console.log(e); 

That is, a sequence of assignments on which each term depends on arbitrary previously defined terms. Suppose that F is an asynchronous call:

const F = (x) => Q.delay(1000).return(x);

I can think in no way to express that pattern without generating an indentation pyramid:

F(100).then(a =>
  F(200).then(b =>
    F(a+b).then(c =>
      F(a+c).then(d =>
        F(b+c).then(e =>
          F(d+e).then(f =>
            console.log(f)
          )
        )
      )
    )
  )
);

Note that using returned values wouldn't work:

F(100).then(a => F(200))
    .then(b => F(a+b))
    .then(c => F(a+c))
    .then(d => F(b+c))
    .then(e => F(d+e))
    .then(f => console.log(f));

Since, for example, a wouldn't be in scope on the second line. What is the proper way to deal with this situation?

MaiaVictor
  • 51,090
  • 44
  • 144
  • 286
  • 2
    Various methods here: [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). In fact, this question may be a dup of that one. – jfriend00 Jan 16 '17 at 18:34
  • @jfriend00: It's definitely a duplicate of the same one that one's a duplicate of, good find. – T.J. Crowder Jan 16 '17 at 18:38
  • Definitely a duplicate, I see. I think this thread is turning out more instructive overall, though. – MaiaVictor Jan 16 '17 at 18:40

3 Answers3

4

Because of the way that subsequent operations rely on multiple bits of previous operations, your choices are:

  1. Do what you've done

  2. Put the variables outside the chain and assign them as you go

  3. Have the entire thing passing around an object with a, b, and such on it as properties

#1 is what I'd stick with, barring a really good reason to do either of the other two. Fortunately, that kind of accumulation rarely goes as deep as shown in your question.


async/await is the way to do it here some years later (and may have been when the question was posted, the proposal was finished and they could be used with a transpiler), see Sterling's answer for how they could simplify it. Here's a working example:

const F = x => {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(x);
        }, 100);
    });
};
(async () => {
    const a = await F(100);
    const b = await F(200);
    const c = await F(a+b);
    const d = await F(a+c);
    const e = await F(b+c);
    const f = await F(d+e);
    console.log(f);
})()
.catch(error => {
    // ...handle/report error...
});

Live on Babel's REPL for those with outdated environments.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • async/await could help with scope, or you could use Promise.all() iirc to make chaining parameters easier. (I've used the async lib for node and it makes passing params easy) – Sterling Archer Jan 16 '17 at 18:20
  • @SterlingArcher: The `async`/`await` thing is a **very** good point (and if you're transpiling you can use it now). – T.J. Crowder Jan 16 '17 at 18:24
  • Posted a much better answer haha, we can thank Luggage on the JS chat for that idea – Sterling Archer Jan 16 '17 at 18:26
3

Second try. @Luggage in the JS chat suggested using Async/Await to keep the scope of your params.

let a = await F(200);
let b = await F(a + 100);
let c = await F(a + b);
//... etc, all inside an async function

You could also use Promise.all or (this is my experience) I've used the async lib to help waterfall these issues.

async.waterfall([
   (callback) => {
        let a = F(200);
        callback(null, a)
    }, (a, callback) => {
        let b = F(a+b);
        callback(null, b);
    }, //etc down the chain
]);

I think Promise.all would manage it better than an async lib, but async/await is the prettiest method here, though it will require ES2017 support/transpiling.

Sterling Archer
  • 22,070
  • 18
  • 81
  • 118
2

Async/await solve this issue quite nicely I think;

async function runAsync(){
  const F = async (x) => x;
  const a = await F(1);
  const b = await F(2);
  const c = await F(a + b);
  const d = await F(a + c);
  const e = await F(b + c);
  console.log(e);
}

function runSync(){
  const F = (x) => x;
  const a = F(1);
  const b = F(2);
  const c = F(a + b);
  const d = F(a + c);
  const e = F(b + c);
  console.log(e);
}

runSync(); //5
runAsync(); //5

this will run natively with node 7 using node --harmony-async-await example

Unfortunately, you'll likely need to transpile down for general use and the output can get quite large.

Meirion Hughes
  • 24,994
  • 12
  • 71
  • 122