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!`);
});
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!`);
});
});
});
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!`);
});
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();
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();
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
orminutes
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.
Top comments (0)