Skip to main content

The great async workout

The best way to fix the concepts seen so far in this chapter is to rewrite the roll function from its sync version to its awaitable one. Let's roll!

The sync version

Where it all started:

function roll() {
  return Math.floor(Math.random() * 5);
}

console.log('before');
console.log(roll());
console.log('after');
Toggle Console Output

It goes like before, result, after. Remember that the timeout we introduced later is artificial and not actually needed for the calculation. But if you needed to add some suspense to a program it would make perfectly sense, so let's move on.

The continuation-passing style (CPS) version

function roll(callback) {
  setTimeout(() => {
    callback(Math.floor(Math.random() * 5));
  }, 500);
}

console.log('before');
roll((result) => {
  console.log(result);
});
console.log('after');
Toggle Console Output

before, after then the number. This reveal the non-blocking nature of JavaScript.

By the way we haven't seen it in the first lesson of the chapter, but callbacks are by no mean asynchronous by nature - it's setTimeout that defers the code execution, see the logs without the timeout:

function roll(callback) {
  callback(Math.floor(Math.random() * 5));
}

console.log('before');
roll((result) => {
  console.log(result);
});
console.log('after');
Toggle Console Output

The point is that CPS for non async functions is...a bit weird don't you think? return works and works good, and you want to use it!

The Promise based version

CPS needs an explicit callback function parameter. Promises save us some work:

function roll(callback) {
  return new Promise(resolve => {
    setTimeout(() => {
        resolve(Math.floor(Math.random() * 5));
    }, 500);
  })
}

console.log('before');
roll().then((result) => {
  console.log(result);
});
console.log('after');
Toggle Console Output

Great. This allows us to use roll() in Promise.all for instance:

function roll(callback) {
  return new Promise(resolve => {
    setTimeout(() => {
        resolve(Math.floor(Math.random() * 5));
    }, 500);
  })
}

Promise.all([
  roll(),
  roll(),
  roll(),
]).then(results => console.log(results));
Toggle Console Output

Using async / await

Since roll returns a Promise now, we can await for it:

function roll(callback) {
  return new Promise(resolve => {
    setTimeout(() => {
        resolve(Math.floor(Math.random() * 5));
    }, 500);
  })
}

async function rollTwice() {
  const first = await roll();
  const second = await roll();
  console.log(`You rolled a ${first} and a ${second}`);
}

rollTwice();
Toggle Console Output

Wonderful. Practice telling the evolution of the roll function as a story by putting strategically placed log statements, then do the same with the divide function taking error handling into account.

Conclusion

In this series of lesson we've been exposed to asynchronous code in various flavours. They are the evolution of each other and every is relevant to some extent so you have to master all of them. We are now ready to learn something more exciting than timeouts.