Normally in JavaScript, you’re going to be working with what is known as synchronous code. Synchronous code is where each line or command is executed one at a time. This works for most scenarios, but sometimes we want to do something that may take some time to complete. For example, if a user clicks a button that makes a request to the server, we don’t want the UI to freeze while that processes. We would prefer to display to the user some indication that data is loading like showing a spinner icon. We can do this asynchronously. Opposed to synchronous code, asynchronous code allows us to run other code while waiting for a long-running task to complete.
There are multiple ways to handle asynchronous code in JavaScript, but promises allow us to do so easily and simply. The Promise object represents the eventual completion or failure of an asynchronous operation. They are similar to promises in the real world. For example, I promise ,”I’m going to make my bed every day.” I will either follow through with that and the promise will be resolved or fulfilled or I won’t and the promise will be failed or rejected.
In this post, I will break down promises, show some code examples and talk about the benefits of using them.
Promises
A promise is a proxy for a value that will be eventually returned from an asynchronous operation.
It has three states:
- pending: initial state, not fulfilled or rejected
- fulfilled: operation was successful, resulting in a value
- rejected: operation failed, resulting in an error object
Code Example
Here is some sample code using promises:
The promise constructor takes a function as an argument to be executed. This function has two callback functions as parameters, resolve and reject. The resolve callback is called when everything worked and sets the state to fulfilled. The reject callback is called when something failed and sets the state to rejected. Additionally, the promise object can be chained with then() and catch() methods that both return promises. In this example, the then() method takes a callback function as an argument and gets executed if the Promise object resolves. Similarly, the catch() method takes a callback as an argument that gets executed when the Promise object is rejected. In this case, the condition is met, so the promise resolves and ‘Success’ will output to the console.
If we changed sky.color to be red (or any color other than blue), then the promise would be rejected and ‘Failure’ would output to the console.
The then Method
Before going further, it is important to fully understand the promise syntax. In the example we used, the then() method took one argument, a callback function. Actually, the method can take up to two arguments: a success callback and an error callback.
So, instead of having a catch in our previous example, we could have done this and got the same result:
We got the same result in this case, but using the error callback does not always behave the same as using then() and catch(). Both uses will handle errors in the promise code, but here is the difference:
From researching online, it seems that for most cases, using then() and catch() is preferred. However, there are situations where you would want to use the error callback. It is not as simple as saying to always use then() and catch().
If you would like to read more into on this, check out this.
Advantages
Promises were introduced to JavaScript with ES6 in 2015. Before this, we used callback functions. Promises were meant to replace callback functions and make our code simpler and easier to maintain. The benefits are is we greatly reduce (and in some cases completely get rid of) callback hell. Additionally, promises make it easier to understand the control flow of our asynchronous logic and they provide automatic error handling.
Replace Callback Functions
Callback functions are useful, but they create issues when they’re relied on heavily in asynchronous programming. Levels of nested callbacks can create what is known as callback hell, where the callbacks form a chain that is hard to understand and maintain. Take a look at this code using callbacks:
Now if we implement this using promises:
The benefit of this is that we don’t have to pass multiple callback functions as parameters. We can simply call the resolve() and reject() methods of the Promise, then() and catch() will execute depending on its state. This code is much easier to manage, especially if we needed more than two callbacks passed into our method.
Better Definition of Control Flow of Asynchronous Logic
With traditional callback functions, it can be difficult to make sense of the control flow of the application. Take a look at this code where we need to call a series of APIs to get all of the running trails in a certain city:
Compare that to this code where we do the same thing but with promises:
Which code looks clean, readable and easy to maintain? Which would make the person maintaining it want to run you over with their car?
Automatic Error Handling
With callback functions, we need layers of manual error handling. This can quickly become complicated and hard to maintain. On the other hand, with promises we can easily achieve automatic error handling. Once our promise has been rejected, the catch method will be called. The catch will handle errors anywhere in the promise chain, and they can all be handled in one place:
Conclusion
Whether working on your own code, using some library or using one of the many APIs, you’re likely to use promises. Additionally, they are increasingly being more integrated into JavaScript. With ES2017, async and await were introduced which are built on promises. They will prevent you from using layers of complex callback functions when working with asynchronous code. They are easy to integrate into your project and can greatly increase the maintainability and readability of your code so why not use them?