JavaScript is synchronous by default, meaning that it executes code in the order it is written and blocks the execution until a function call returns a value. This can lead to issues with long-running operations or blocking the UI thread, which can negatively impact the user experience.
To address this issue, JavaScript provides mechanisms for asynchronous programming, such as callbacks, Promises, and async/await. These mechanisms allow you to execute code asynchronously, meaning that the execution continues without blocking while an operation is in progress. Once the operation is complete, the result is passed back to the program, typically through a callback or a Promise.
Synchronous | Asynchronous |
BLOCKING | NON-BLOCKING |
In this article, we will discuss each of these patterns to deal with asynchronous in Javascript.
1. Callbacks
A callback is a function that is passed as an argument to another function and is executed when a certain event or action occurs. In other words, a callback is a function that we are going to call when the result of an asynchronous operation is ready.
function getProduct(id, callback) {
setTimeout(() => {
console.log("Reading a product from a database");
callback({id: id, productName: "CR-V2023"});
}, 2000);
}
getProduct(1, (product) => {
console.log("product", product);
});
In this example, the callback function is defined as an anonymous function that takes a ‘product’ parameter and logs it to the console. The getProduct function creates a 2-second delay using the setTimeout method, and then logs a message to the console. Once the getProduct function has completed reading the user from the database, it calls the callback function with the product object as its argument. The callback function then logs the product object to the console.
Now let move to a better way to deal with asynchronous code: promises
2. Promises
Promises provide a cleaner and more intuitive way to work with asynchronous code than traditional callback functions.
A promise is an object that represents the eventual completion (or failure) of an asynchronous operation and allows you to handle the result of the operation asynchronously. In other words, a promise is an object that holds the eventual result of an asynchronous operation. When an asynchronous operation completes, it can either result in a value or an error.
A Promise has three states:
- Pending: The initial state, before the Promise is fulfilled or rejected.
- Fulfilled: The Promise has completed successfully and resolved to a value.
- Rejected: The Promise has failed and rejected with an error.
Here’s an example of how to create and use a Promise:
const myPromise = new Promise((resolve, reject) => {
// Asynchronous operation, e.g. fetching data from an API
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => resolve(data))
.catch(error => reject(error));
});
myPromise
.then(data => console.log(data))
.catch(error => console.error(error));
In this example, we create a Promise that fetches data from an API using the fetch
method. The Promise is either resolved with the data, or rejected with an error, depending on the outcome of the API call.
Once the Promise is created, we can use the .then()
method to handle the successful completion of the Promise, and the .catch()
method to handle any errors. The .then()
method takes a callback function that will be called with the resolved value, and the .catch()
method takes a callback function that will be called with the error object.
Promises can be chained together using the .then()
method, which returns a new Promise object that can be further chained. This allows you to write clean and concise asynchronous code that is easy to read and understand.
3. Async/Await
Async/await is a feature in JavaScript that allows for asynchronous programming with the use of promises.
The async keyword is used to define a function that will return a promise. This function can contain one or more await statements, which pause the execution of the function until a promise is resolved. Once the promise is resolved, the function continues executing.
We use try…catch block to catch error in async/await
By using async/await with a try…catch block, you can handle errors in a more readable and concise way compared to using .then() and .catch() with promises.
Here’s an example of an async function that fetches data from an API:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching data:', error);
throw error; // rethrow the error to be handled by the caller of the function
}
}
Overall, async/await provides a cleaner and more concise way of writing asynchronous code in JavaScript, making it easier to reason about and maintain.