DEV Community

loading...
Cover image for Beautify your delays

Beautify your delays

aminnairi profile image Amin ・5 min read

In this post, I'll show you a simple trick to make your scripts more readable when attempting to add delays to them.

ECMAScript 2015: Promises

In this post, I'll talk about the async and await keywords, but first we have to know what a promise is in JavaScript before getting further.

According to the documentation:

The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.

In human terms, this means that we can create an object (remember, everything is an object in JavaScript?) that can either resolve (meaning returning a value, as any other objects) or throwing an error, but everything without blocking the main thread of our script. A simple example would be to add some delay via the setTimeout before issueing a console.log.

How was it done before

Before promise, we would often use functions that would take some parameters and a callback. That callback, in our case, would have been triggered after the setTimeout call would have resolved. Here is an example:

"use strict";

function sleep(seconds, callback) {
  setTimeout(callback, seconds * 1000);
}

const SECONDS = 5;

sleep(SECONDS, function() {
  console.log(`After ${SECONDS} seconds!`);
});

Try it online

See? Not that hard. It was already possible to do that kind of thing without Promises, so why bother learning it then? Let's imagine now that our script got bigger and we now have to wait three times, each times separated by three seconds, and do some logging. We would often come up with something like that:

"use strict";

function sleep(seconds, callback) {
  setTimeout(callback, seconds * 1000);
}

const SECONDS = 3;

sleep(SECONDS, function() {
  console.log(`First call after ${SECONDS} seconds!`);

  sleep(SECONDS, function() {
    console.log(`Second call after ${SECONDS} seconds!`);

    sleep(SECONDS, function() {
      console.log(`Third call after ${SECONDS} seconds!`);
    });
  });
});

Try it online

Now imagine you have to do this ten times. And now imagine that instead of logging things on the console you'll have to do some specific operations on all the three calls? It is called a callback hell and it was really popular in JavaScript, especially in the Node.js world until the ECMAScript standard released the Promise object. And now we can start to breath again and write something like that:

"use strict";

// No more callback needed!
function sleep(seconds) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve();
    }, seconds * 1000);
  });
}

const SECONDS = 3;

sleep(SECONDS).then(function() {
  console.log(`First call after ${SECONDS} seconds!`);

  return sleep(SECONDS);
}).then(function() {
  console.log(`Second call after ${SECONDS} seconds!`);

  return sleep(SECONDS);
}).then(function() {
  console.log(`Third call after ${SECONDS} seconds!`);
});

Try it online

So what happend is:

  • We got rid of the callback hell.
  • We returned a Promise object that will handle the resolution (or the failure) of the function.
  • We use the then operator and provided a callback that will describe what to do when three seconds have passed.
  • We logged and... Wait a minute...

Callback? Again?

Yes. We still need to use a callback, but our code is now more maintainable and more readable than before. And I believe that we should all thrive to use Promise for that kind of job, especially if you are a Node.js developper and still use them in the core modules. Did you know that since a certain version of Node.js, you can now use this:

const { promises: fs } = require("fs");

fs.readFile("...").then(function(data) { ... });

Insted of:

const fs = require("fs");

fs.readFile("...", function(error, data) { ... });

Which is awesome! It is still in its experimental days at the time of this writing but I hope that Node.js will catch up and use Promises for all its callbacks!

And this is the end of this topic. Thanks for your attent... Wait, no! We can do more.

But await, there is more...

Since ECMAScript 2017, we now have the right to use the all mighty async & await keywords, which simplify even more the writing of asynchronous instructions in JavaScript. So taking back our example from before, we can now use this syntax:

"use strict";

// No more callback needed!
function sleep(seconds) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve();
    }, seconds * 1000);
  });
}

const SECONDS = 3;

async function main() {
  await sleep(SECONDS);

  console.log(`First call after ${SECONDS} seconds!`);

  await sleep(SECONDS);

  console.log(`Second call after ${SECONDS} seconds!`);

  await sleep(SECONDS);

  console.log(`Third call after ${SECONDS} seconds!`);
}

main();

Try it online

What happend?:

  • We go rid of all the callbacks. All of them.
  • We can now write our code as if it was synchronous (but it is not!).
  • It is much more simpler to work with.
  • We added a special await keyword to wait for the promise to resolve.

The one caveat is that we had to wrap all our asynchronous code in an asynchronous function using the async keyword. But I think that this is a small price to pay to have a script written like that. I would want to work with it rather than with our first example.

Good to know: there is now a new runtime for server-side JavaScript execution that is called Deno and that has been written by one of the original creator of the Node.js platform. It supports TypeScript and JavaScript out of the box and is said to provide a top-level asynchronous support, meaning we would be able to get rid of that main function. Isn't it cool?

Can we go even further?

I found, in the course of my researches, an elegant way to describe delays in my JavaScript code that leverage the getter functionnality of objects (remember again, everything is an object, even a class!) and that can be used like so:

"use strict";

const ONE_SECOND_IN_MILISECONDS = 1000;
const ONE_MINUTE_IN_SECONDS = 60;

class After {
  constructor(delay) {
    this.delay = delay;
  }

  get seconds() {
    return new Promise((resolve) => {
      setTimeout(resolve, this.delay * ONE_SECOND_IN_MILISECONDS);
    });
  }

  get minutes() {
    return new Promise(resolve => {
      setTimeout(resolve, this.delay * ONE_MINUTE_IN_SECONDS * ONE_SECOND_IN_MILISECONDS);
    });
  }
}

function after(delay) {
  return new After(delay);
}

async function main() {
  await after(3).seconds;

  console.log(`After 3 seconds!`);

  await after(0.1).minutes; // 6 seconds

  console.log(`After 6 seconds!`);
}

main();

Try it online

So what the heck happend in here:

  • I now use a class to store the delay to wait.
  • I return a promise, just like before.
  • But instead of resolving after some seconds, I can now decide whether I want to wait for a number of seconds, minutes, etc...
  • Using the getter for seconds or minutes will not return a simple value but a promise we can work with.
  • It is like talking in plain english.

And...

This is how I handle my delays in JavaScript. I'm sure there could be some better, clever ways to do this. JavaScript is an amazing language and very versatile, with a plethora of tools and paradigm to write with. So keep getting curious and keep practicing!

Oh and if by any means you want to improve this article, be my guest! Typos, mistakes, enhancement, let's discuss about it in the comment section.

Discussion (0)

pic
Editor guide