Async Actions
If you have written JavaScript before, you are most likely aware of asynchronous programming. Since JavaScript can only perform one command at a time (it is a single-threaded language), operations that are long, such as retrieving data from a server, can block the main thread. This is bad because to the user, your program may appear unresponsive.
An example of this would be making a fetch request to an API and waiting for the response. While waiting for the response from the API, we want our program to be able to execute other processes. Asynchronous programming allows JavaScript to continue execution of other processes while waiting for another process to resolve. Here is a good blog that goes more in depth on asynchronous JavaScript and the event loop.
Promises
A promise is a JavaScript object that, "represents the eventual completion (or failure) of an asynchronous operation, and its resulting value." Promises were first introduced to JavaScript in ES6. With this new feature, functions can return a promise and continue executing other processes while waiting for it to resolve or settle. Once the promise is resolved, the program can continue with the use of that data it was waiting on.
Before promises were introduced, callbacks and events were used for asynchronous programming, but this presented some challenges. Some people have referred to this as callback hell, as the code can be extremely hard to understand. Here is an example from this Stranger Things themed blog post:
fightTheDemogorgon(function(result) {
rollForDamage(result, function(seasonsLeft) {
closeTheGate(seasonsLeft, function(finalResult) {
console.log('Hawkins is safe for ' + finalResult + ' more seasons.');
}, failureCallback);
}, failureCallback);
}, failureCallback);
A Metaphor
I like to think of promises like a ticket you would get from a food truck after you order. The ticket has a number, so it can be associated with your order and you can perform other actions while you are waiting for your food. Meanwhile, the food truck is hard at work getting the order they "promised" ready for you.
States
Promises have three possible states: fulfilled, rejected, and pending.
The initial state of the promise is pending. This is when you first get the ticket from the food truck and are waiting for your order. Maybe during this time you are doing another action you need to complete, like calling your mom. We all know we need to do that more.
A promise is fulfilled if the operation was completed successfully. This would be when the food truck has successfully given you your food. A promise is rejected if it failed. If rejected, there will be an error associated with the promise. This would be the case if the food truck ran out of an ingredient and couldn't make your order. Maybe the error would be a message from the employee telling you what went wrong.
Then / Catch Methods
Two methods that are frequently used with promises are .then()
and .catch()
. These methods will execute when a promise is "settled," meaning it is no longer pending. Both .then()
and .catch()
return promises themselves and can be chained onto the original async function.
Then
The .then()
method takes up to two arguments. The arguments should be callback functions to execute when the previous async action settled. The first callback will execute in the case of the promise being fulfilled and the second will execute in the case of the promise being rejected. These callback functions are named onFulfilled()
and onRejected()
in the MDN documentation, but most programmers use arrow functions for their callbacks like so:
return fetch(`${baseUrl}/api/v1/concert/${concertId}/users`, configurationObject)
.then(r => r.json())
.then(users => {
if (users.error) {
alert(users.error)
} else {
dispatch(setUsersOfConcert(users.data))
}
})
.catch(error => console.log(error))
The onFulfilled()
function takes one argument, the fulfillment value. In this case, I called the value r
for response. The onRejected()
callback takes one argument, the reason for the rejection. In this case, I didn't use the second argument for .then()
, but this was handled with .catch()
, which you will read about below. The promise returned by .then()
will resolve to the return value of the passed in callback, or to its originally settled value if the passed in argument was not a callback function. For example, if .then(console.log("hi"))
was chained onto an async function, the promise will resolve to undefined.
As you can see, I chained the .then()
method on with one argument, to execute in the case of the promise being fulfilled. In this case, I take the response body from the API and parse it to json. The .json()
method returns a promise as well, hence the additional .then()
chaining.
Catch
The .catch()
method is basically the same as .then()
, however it deals with the case of the promise being rejected. From the docs, this method actually calls .then()
under the hood, with the first argument as undefined
. As mentioned earlier, .catch()
returns a promise as well. From the docs, this promise is resolved unless the onRejected()
callback function returns an error, or returns a promise that is rejected. The promise returned by .catch()
will resolve to the return value of the passed in callback.
MDN has a pretty good diagram to explain the flow of chaining:
In most cases that I have seen, developers use .catch()
to log errors. Above, in my code snippet, you can see that in the case of an error, I just log it to my console.
Conclusion
There is much more I could write about when it comes to promises. There are more methods associated with them and more use cases. You can even construct your own promise objects using the new
keyword. Check out the youtube video I linked in the resources section to see this in action.
I wanted to keep this blog fairly short, but I might go more in depth on the subject in the future. Let me know how you have used promises in your code. Hope you all are safe and happy!
Top comments (0)