Skip to content

Asynchronous JavaScript with Promises

asynchronous javascript with promises

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

Mozzila documentation

Promises can be considered the new cool kid in the block (although the concept of promises and execution synchronization is almost 40 years old, you can read more on that here). In JavaScript, they were natively introduced and standardized in ES6 (and they could be run in ES5, because libraries such as jQuery and Angular had their own custom implementation). What promises are good for is the improvement on callbacks when it comes to control flow, and especially nesting more callbacks, which leads to unclear and unmaintainable code.

But wait, what are promises for the uninitiated? Imagine you promise your long lost friend you haven’t seen since high school you will hang out and grab a couple of beers this weekend (well, we all know how this one goes, but lets take it as an example for the sake of it). After you shake on it, the deal is pending — neither fullfilled or rejected, out there in the limbo and it can either be successfull, meaning you really go out with your friend and listen to his marital problems, or failed, which basically translated to a night of staying home and watching Netflix. Promises works similarly, they don’t know right away if the result of the operation will be successfull or not, but it is ready to handle both of those values, thus allowing you to work with data in an asynchronious manner.

The most basic syntax in Javascript for writing promises looks something like this:

let promise = new Promise(function(resolve, reject) {
 // the promise is executed here 
});

And as you see, writing a Promise in regular JavaScript is nothing spectacular, in the Promise constructor a callback function is inserted, consisting of two parameters, either a resolve function or a reject function, and the code itself decides what function to call depending on how everything goes. If everything goes well, the resolve method is called, otherwise the reject method fires. Whenever the resolve fires a .then() callback is resolved, which can do a couple of things but usually it returns the object the promise has been called on. Reject resolves itself with the .catch() callback. If .then() fires the .catch() won’t and vice versa. When it comes to asynchronous code, the main thing to note is that the promise object will be returned before the logic inside the promise actually evaluates either to resolve or to reject. The code won’t be finished running, but a nice object is avaliable to attach to the .then() or .catch() functions.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

Promises and callbacks

Well, promises were introduced to battle a serious issue when it comes to JavaScript programming — the neverending pit of Helheim, nested callbacks (callbacks are esentially functions that are supposed to be run after another function has finished its execution). And this is especially true for any code written in Node.js (more on that later). I’ve seen code written in Node that has more than seven or eight levels of nesting, which makes it a painful task to read and understand.

function task1() {
    console.log("Task1 called");
}
function task2() {
    console.log("Task2 called");
}
function secondBreak() {
    console.log("One second break!");
}
setTimeout(function() {
    console.log("Working on task1");
    task1();
    setTimeout(function(){
        secondBreak();
        setTimeout(function() {
            console.log("Working on task2");
            task2();
            setTimeout(function() {
                secondBreak();
            }, 1000);
        }, 2000);
    }, 1000);
}, 2000);

The code above just calls an imaginary task every two seconds, with a one second break in between, but even with the snippet being fairly simple when it comes to functionality, it isn’t exactly clear right from the start what this could be, and especially if the task1() and task2() functions would have been more complex (or even had some callbacks of their own).

Promises provide a more elegant way to solve similar problems. Another important characteristic of promises is that they are chainable. No more messy code, promise chaining allows multiple .then() functions to be appended to the promise, and the good thing is that the callbacks, that is the next .then() function always waits for the previous one to be done, but also the .then() function is calling the callback even after the success or the failure of the asynchronous operation. This is an amazingly cool feature — the ability to combine asynchronous tasks into promises chained one after another.

Now, the issue above can be solved like this using promises:

function task1() {
    console.log("Task1 called");
}
function task2() {
    console.log("Task2 called");
}
function secondBreak() {
    console.log("One second break!");
}
function runTasks(callback, time) {
    var promise = new Promise(function(resolve, reject){
        setTimeout(function() {
            var resolveCallback = callback();
            resolve(resolveCallback);
        }, time)
    })
    return promise;
}
runTasks(task1, 2000)
    .then(function(){ return runTasks(secondBreak, 1000) })
    .then(function(){ return runTasks(task2, 2000) })
    .then(function(){ return runTasks(secondBreak, 1000) })

What is happening here? The runTasks() function takes a callback function and the time as parameters. Inside the function a promise has been created and it handles a setTimeout() function, while resolving the callback given as a parameter inside the setTimeout(), thus enabling us to send our functions task1(), task2() and secondBreak() to setTimeout().

Then the function runTasks() is run with the parameters, startint with the first task, and the two second delay. Afterwards it is chained with the .then() method and returning the result of the runTasks() with the one second break function and one seconds (and that is promise, which is again chainable, and this could go to infinity), thus achieving a natural and elegant look for the code.

Hands on: Promises in Node.js

Currently I’m working on a side project in Node, and boy, if you’re not careful you could get into a world of pain! Basically, JavaScript being a single-threaded language, enables callbacks as a form of proceeding past a long-running process in order not to block the execution, and Node essentially is JavaScript in its core shares the same pitfalls as that language. There are a lot of uncertainities and wild behavior if the developer is not skilled or careful enough.

The concept is mostly the same in Node as in JavaScript though, and the snippet below shows some code I used in my hobby application that is written in Node.js.

router.get('/:car_id', (req, res) => {
    const errors = {};
Car.findOne({ id: req.params.car_id})
      .then(car=> {
         res.json(car);
      }).catch(err => res.status(404).json(err)) 
  })

What is that router thing? The router object (from Express.js) is used as middleware to handle routes and utilize a complex routing system in our application, in order to help us navigate URI’s. Basically thanks to the router, we created the ‘:/car_id’ route (the full one being something like http://localhost:8000/api/cars/41, where 41 is the :car_id, and the router will send us to that location).

Request or req is an object that contains information about the HTTP request that raised the event, it contains information such as the req.url, req.method, req.headers , req.cookies and req.params. Response or res represents the response the application sends when it recieves a request. You can look at them as information that you send, and information that you which is based on the information sent.

Now, we are looking in our Car models for one car that has the id equal to the id set in the request parameters, and then — then() :). If the request has been successfull, then() fires and returns the car as a json object, otherwise it picks up an error object and sends that error to the user. It is a simple excerpt but I hope that you are starting to understand how promises work and how they are utilized.

Conclusion

The topic of promises can be painful to grasp for many developers, but in the end it isn’t that complex and is extremely helpful and useful.

Hope you learned something new, below you have some links that you can read in order to improve your knowledge of Promises even more!
Promise and Using promises