Callbacks, Promises, Async, Await
Table of Contents
In JavaScript, you oftentimes need to perform tasks that will take an undetermined amount of time. Because of this, you want to allow your code to continue running instead of waiting until the task completes. This is called asynchronous programming.
Callback Functions
One of the most popular ways to employ asynchronous programming is through the use of callback functions. As the name implies, a callback function is a function that is called after another function has completed.
Let's look at a simple example:
JAVASCRIPTfunction doChore(chore, callback) {
alert("Started " + chore + ".");
callback();
}
function finished() {
alert("Finished my chore!");
}
doChore("cleaning", finished);
Here you have a function doChore
, that takes two parameters, the name of the chore you are doing, and then a function that it will call after the chore is done. We are passing a function that simply alerts the user that the chore is complete, the finished()
function. As you would expect, this is the end result:
JAVASCRIPTalert("Started cleaning.");
alert("Finished my chore!");
In reality, anything can happen inside doChore
before the callback function is called. Maybe you need to make a request to another server or read a file from disk. The point to get out of this is that the callback function is only called after the rest of function is finished.
We can see this better illustrated when we use the setTimeout
function which can add a delay to our code:
JAVASCRIPTfunction doChore(chore, callback) {
alert("Started " + chore + ".");
setTimeout(callback, 3000);
}
function finished() {
alert("Finished my chore after 3 seconds!");
}
doChore("cleaning", finished);
JAVASCRIPTalert("Started cleaning.");
alert("Finished my chore after 3 seconds!");
Promises
Promises are used to handle the results of an asynchronous operation, for example like making an API call or reading from disk. We want to continue to allow the code to continue to run and promises are a structured way to handle asynchronous operations.
Creating a Promise
Creating a promise is easy, simply create it like you would any object. It takes a callback function with two parameters, a resolve
function and a reject
function.
JAVASCRIPTconst promise = new Promise((resolve, reject) => {
// implementation here
});
Promises only have three possible states they can be in:
- Pending: A pending Promise hasn't begun its operations yet.
- Fulfilled: When a promise resolves after its operations finished.
- Rejected: When a promise fails to complete its operations successfully.
Let's look at an example promise mocking an API call:
JAVASCRIPTconst success = true;
const data = new Promise((resolve, reject) => {
if (success) {
const results = {
message: "This was a success!"
};
resolve(results);
} else {
const results = {
message: "This failed!"
};
reject(results);
}
});
In this bare-bones example, we are controlling whether or not this mock API data call succeeded with a boolean. Depending on that value, it will determine whether the promise resolves or is rejected.
Using Promises
Now that we have defined our promise, let's try actually using it:
JAVASCRIPTconst success = true;
const data = new Promise((resolve, reject) => {
if (success) {
const results = {
message: "This was a success!"
};
resolve(results);
} else {
const results = {
message: "This failed!"
};
reject(results);
}
});
data.then(success => {
console.log(success.message);
}).catch(error => {
console.log(error.message);
})
HTMLThis was a success!
When you use the then
function, it receives the value passed in to the resolve
function. Likewise, the catch
function receives the value passed into the reject
function. That is why we are able to immediately access the message
value that we passed in earlier.
Async Keyword
Async is keyword in JavaScript that allow you to write promises in an easier and more visually appealing manner. Let's start by declaring a normal function as asynchronous.
JAVASCRIPTasync function apiSuccess() {
const results = {
message: "This was a success!"
};
return results;
}
The above code is the equivalent to this:
JAVASCRIPTfunction apiSuccess() {
const results = {
message: "This was a success!"
};
return Promise.resolve(results);
}
Both of the above functions will resolve to the same exact thing. On the other hand, you can do the same in the reverse:
JAVASCRIPTasync function apiFailure() {
const results = {
message: "This failed!"
};
throw new Error(results);
}
That is equivalent to doing this:
JAVASCRIPTfunction apiFailure() {
const results = {
message: "This failed!"
};
return Promise.reject(results);
}
Await Keyword
The await keyword is used with asynchronous functions to ensure that all the promises are completed and synchronized. This can help remove the need to use callbacks via .then()
and .catch()
. Here's an example:
JAVASCRIPTasync function getAPIData(url) {
try {
const response = await fetch(url);
const data = await response.json();
console.log(data);
} catch(error) {
// catches errors in all async functions
console.log(error);
}
}
const url = "https://jsonplaceholder.typicode.com/todos/1";
getAPIData(url);
JAVASCRIPT{
userId: 1,
id: 1,
title: "delectus aut autem",
completed: false
}
Our asynchronous API call was successful and we got our data! Alternatively, if you simply wanted to return the data instead of print it out, you can write it like this:
JAVASCRIPTasync function getAPIData(url) {
try {
const response = await fetch(url);
const data = await response.json();
return data;
} catch(error) {
return error;
}
}
const url = "https://jsonplaceholder.typicode.com/todos/1";
const data = await getAPIData(url);
console.log(data);
JAVASCRIPT{
userId: 1,
id: 1,
title: "delectus aut autem",
completed: false
}
Finally, if you want to call an async function from a non-async function, you can use the then
method. Here's an example:
JAVASCRIPTasync function getAPIData(url) {
try {
const response = await fetch(url);
const data = await response.json();
return data;
} catch(error) {
return error;
}
}
function printAPIData(url) {
getAPIData(url).then(result => {
console.log(result);
});
}
const url = "https://jsonplaceholder.typicode.com/todos/1";
printAPIData(url);
JAVASCRIPT{
userId: 1,
id: 1,
title: "delectus aut autem",
completed: false
}
Conclusion
Hopefully you have seen how useful JavaScripts asynchronous features can be. They allow you to rewrite code that previously were constant callbacks into something much more readable and semantic. They also allow you to write code that is easier to test and maintain.
Resources
- Getting Started with Solid
- Getting Started with Express
- How to Serve Static Files with Nginx and Docker
- How to Set Up Cron Jobs in Linux
- How to build a Discord bot using TypeScript
- How to deploy a MySQL Server using Docker
- How to deploy a Node app using Docker
- Getting Started with Moment.js
- Using Push.js to Display Web Browser Notifications
- Building a Real-Time Note-Taking App with Vue and Firebase
- Setting Up Stylus CSS Preprocessor
- Using Axios to Pull Data from a REST API