1

I've grown used to promise chaining arrays. It's incredibly easy to read a promise chain when each promise is a line long such as

myArray.map(x => convertX)
  .filter()
  .whatever()
  .etc()

This is incredibly easy to read. However, when I create promise chains with custom functions, it gets much messier.

database.query(first query)
  .then(results => {
    // do stuff
    // do more
    // even more
    return database.query(second query)
  })
  .then(results => {
    // rinse and repeat
  })
  .catch(err => { 
    // error handling 
  })

Now, this can be legible, but when the promise chain extends further, it gets to be a little much. If I make each promise it's own function, then I can streamline the process so the code looks something like this (which imo, is 1000x more legible).

db.query(first query)
  .then(storeFirstQuery)
  .then(secondQueryAndStoreIt)
  .then(thirdQueryAndStoreIt)
  .catch(errHandlingFunction)

This way, I can rearrange the functions without having to manipulate the values that get passed from one promise to the next. If a promise uses a result of another, it only needs to be after the other, but not immediately after. That way I can sneak promises in wherever I need.

However, this requires my promise chain to use variables outside each promise's scope. Is there a tried and true way to do this?

Edit - Seems async/await is the best way to do this, but I'm running Node on Heroku and it's not supported yet :/

Matt
  • 1,392
  • 12
  • 24
  • You're not the first to seem to like the approach you've chosen. I don't, because it's legible English, not legible JavaScript. It's ["callback lite"](http://stackoverflow.com/a/42380006/918910). – jib Mar 14 '17 at 02:22

2 Answers2

1

Well, you can use something like that with promises:

myArray.map(x => convertX)
  .filter()
  .whatever()
  .etc()

if you use my rsp module from npm.

Other than that you can use the async/await features of ES2017 to simplify the promises chains, especially their scope.

Because with code like this:

db.query(first query)
  .then(storeFirstQuery)
  .then(secondQueryAndStoreIt)
  .then(thirdQueryAndStoreIt)
  .catch(errHandlingFunction)

if you need to use the result of first query in the last thirdQueryAndStoreIt() handler, you have a problem with accessing the data that is out of scope. But when you do:

try {
    let a = await db.query(first query);
    let b = await storeFirstQuery();
    let c = await secondQueryAndStoreIt();
    let d = await thirdQueryAndStoreIt(a); // use 'a' here
} catch (e) {
    errHandlingFunction(e);
}

then you don't have the problem with scope, as you can easily access all of the previously assigned variables.

See this for Node versions that support this syntax:

You can use it with Node v7.6+ out of the box or with Node v7.0+ with the --harmony flag.

For older Node versions you can use co or Bluebird.coroutine for a similar syntax using generator functions and yield instead of await.

rsp
  • 107,747
  • 29
  • 201
  • 177
  • Yea, `async`/`await` seems like the best way to do it. Unfortunately, I'm running Node on Heroku and it's not supported yet. – Matt Mar 13 '17 at 14:55
  • You can do exactly the same thing without async/await. That's just eye candy. – Tomalak Mar 13 '17 at 15:27
-1

if you really want, you can limit scope to a single meta-Promise by creating it yourself:

return new Promise((resolve, reject) => {
  const f1 = () => { /* ... */ };
  const f2 = () => { /* ... */ };
  const f3 = () => { /* ... */ };

  return db.query()
    .then(f1)
    .then(f2)
    .then(f3)
    .then(resolve)
    .catch(reject);
});

but the most legible way to do this is to use async/await.

Dan O
  • 6,022
  • 2
  • 32
  • 50
  • I should have added that I'm doing all this on my Node server running on Heroku. They don't support `async`/`await` yet :/ – Matt Mar 13 '17 at 14:53
  • @matt Hm? There is no async/await in this answer. – Tomalak Mar 13 '17 at 15:28
  • 1
    @Dan Don't make a "meta-promise". Just use a regular function (IIFE) for the sake of creating a closure and return the original promise chain from it. – Tomalak Mar 13 '17 at 15:30
  • @Tomalak "but the most legible way to do this is to use async/await" – Matt Mar 13 '17 at 15:32
  • Sure, but the code in the answer is the relevant thing. And in this case it's the equivalent of the async/await version, for environments that don't have it. – Tomalak Mar 13 '17 at 15:39