1

I am failing to use promises properly in JS from what it seems like.

I have 3 async inter-dependent functions, like so:

func1, func2 and func 3.

  • func1 returns a single results that func2 uses.
  • func2 returns a single result as well
  • func3 uses results from func1 and func2.

So func3 has to wait for both func1 and 2, while func2 has to wait for only func1.

Here is the JS fiddle that I was able to compose and it works, but reading the mess where the 3 are used together is just a nighmare. What would be a proper way to execute such a chain operation?

function func1() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve(10);
    }, 1000);
  });
}

function func2(return1) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve(return1 + 20);
    }, 1000);
  });
}

function func3(val1, val2) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve(val1 + val2);
    }, 1000);
  });
}

func1().then(function(result) {
  func2(result).then(function(result2) {
    func3(result, result2).then(function(finalResult) {
      console.log(finalResult);
    }, function(err) {
      console.log(err);
    });
  });
}).catch(function(err) {
  console.log(err);
});
Barmar
  • 741,623
  • 53
  • 500
  • 612
Alexus
  • 942
  • 7
  • 20
  • 2
    I'm voting to close this question as off-topic because requests for advice on how to improve/redesign working code belongs on CodeReview.stackexchange.com. – Barmar Feb 24 '17 at 00:11
  • I'm not sure there's much room for improvement. There's no redundant code that I can see, it directly expresses the design requirement. Do you need to generalize this for N functions, where each function needs to get the results of all the previous functions? – Barmar Feb 24 '17 at 00:14
  • You may be interested in this [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). This covers the various options for accessing multiple prior results while chaining promises. – jfriend00 Feb 24 '17 at 00:31
  • @Barmar the chaining code looks incredibly hard to read to me because of the same scoping as you would get with callbacks. I was suspecting I amusing promises incorrectly. – Alexus Feb 24 '17 at 00:36
  • @jfriend00 Thank you, will review it! – Alexus Feb 24 '17 at 00:36
  • Possible duplicate of [How do I access previous promise results in a .then() chain?](http://stackoverflow.com/questions/28250680/how-do-i-access-previous-promise-results-in-a-then-chain) – Roamer-1888 Feb 24 '17 at 04:03
  • 1
    Don't forget to `return` your promises! You should have `return` before the calls to `func2()` and `func3()`. Otherwise, the `catch` at the bottom can only catch errors that occurred in `func1()`. – JLRishe Feb 24 '17 at 08:16

2 Answers2

4

Using just promises, you can either use the closure scope and nest your promises (which is what you're doing), or you could pass multiple results as an object like this:

func1()
  .then((result) => {
    return func2(result).then((result2) => ({result, result2}));
  })
  .then(({result, result2}) => {
    return func3(result, result2);
  });

Or you could store results in the scope outside all of the promises:

let result;

func1()
  .then((_result) => {
    result = _result;
    return func2(result);
  })
  .then((result2) => {
    return func3(result, result2);
  });

If your environment supports async/await functions, you could rewrite it like this:

async function fn() {
  const result = await func1();
  const result2 = await func2(result);
  const result3 = await func3(result, result2);

  return result3;
}

fn().then((result3) => console.log(result3));

If your environment support generators, you could use the co library to create co-routines:

const fn = co.wrap(function*() {
  const result = yield func1();
  const result2 = yield func2(result);
  const result3 = yield func3(result, result2);

  return result3;
});

fn().then((result3) => console.log(result3));
SimpleJ
  • 13,812
  • 13
  • 53
  • 93
  • I like async await, that's much cleaner. Passing results as object notation however seems like a bad design to me, because that would indicate the func2 will only be ever used by a func3 because it essentially passes a value from func1 to the further chained func, but it's not supposed to. – Alexus Feb 24 '17 at 00:32
0

EDIT: I misread the requirements. The answer by SimpleJ here is the way to go.

A value returned within a then callback is itself thennable. In other words, you can work through your promises sequentially by returning the next function from the then callback of the previous.

Instead of

func1().then(function(result) {
  func2(result).then(function(result2) {
    func3(result, result2).then(function(finalResult) {
      console.log(finalResult);
    }, function(err) {
      console.log(err);
    });
  });
}).catch(function(err) {
  console.log(err);
});

Try this:

func1()
  .then(result => func2(result))
  .then(result => func3(result))
  .then(result => console.log('Final result', result))
  .catch(err => console.error(err))

I converted your functions to arrow functions to clean up a bit more, but functionally it works the same as yours.

Barmar
  • 741,623
  • 53
  • 500
  • 612
Fissure King
  • 1,250
  • 7
  • 17