The Challenge

I needed to handle synchronous and asynchronous control flows in my Hapi API Boilerplate (a work in progress) for various different bits of functionality. There were the standard Q, Bluebird and Async libraries, but I was already using babel and I figured I should be able to do this natively, without the help of more libraries/modules.

The Options

As best as I could figure, I had the following options available to choose from:

I have experience with promises and I knew I could easily use those for both sync and async control flows, but using promises for sync flows still left me with Christmas tree code.

1
2
3
4
5
6
7
MyFunc1().then((res1) => {
  MyFunc2().then((res2) => {
    MyFunc3().then((res3) => {
      // etc...
    });
  });
});

So while I like promises for async flows (addressed below), I needed a better solution for sync flows… which was great because that’s precisely what generators and async/await functions are for.

Generators

The bottom line with generators is that I hate the syntax. It is not a convention found anywhere else in the specs. The * by the function declaration, the yield keyword… it is all just very weird to me. Additionally, I read a couple of articles that seemed to indicate support for generators making it into the specs is faltering in light of Async/Await.

Async / Await

Async/Await makes sense to me syntactically. You define your block using the async keyword, and you define your synchronous control flow inside the block using the await keyword. So the above example of Christmas tree code can be written much more cleanly like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(async () => {

  try {
    const res1 = await MyFunc1();
  } catch (err) { return cb(err); }

    try {
    const res2 = await MyFunc2();
  } catch (err) { return cb(err); }

    try {
    const res3 = await MyFunc3();
  } catch (err) { return cb(err); }

  // do stuff with res1, res2, res3

})();

This is much cleaner than using promises and it makes for much more readable code. Best of all, I didn’t have to use something like async.waterfall().

Promises for Async

So that solved my sync control flow problems, but I still needed a good solution for native async control flow. Promises are just what the doctor ordered. Promise.all() works in the same way that I would have used async.parellel(). I can declare an array of functions to pass into the .all() method. If any of the functions reject a promise, the whole parent promise will fail over to the .catch() and return the error back up stream. Otherwise, once all passed functions successfully complete, the parent promise will resolve.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const arrOfFuncs = [
  MyFunc1(),
  MyFunc2(),
  MyFunc3()
];

Promise.all(arrOfFuncs).then((results) => {
  const res1 = results[0];
  const res2 = results[1];
  const res3 = results[2];

  // or destructured assignment from es2015
  // const [ res1, res2, res3 ] = results;

  // do stuff with results
}).catch((err) => {
  return cb(err);
});

Conclusion

So that’s pretty much it. If you are using babel, there is really no need to include more libraries into your codebase. Native ES2015 syntax, transposed via babel works just fine!