Callback Functions, Promises, Async/Await and Fetching API's
Callback Functions
A callback function is a function that is passed as an argument to another function and is executed after some operation has been completed. Callbacks are commonly used for asynchronous operations in JavaScript, such as fetching data from an API, reading files, or handling events.
function greet(name, callback) {
console.log(`Hello, ${name}!`);
callback();
}
function sayGoodbye() {
console.log('Goodbye!');
}
greet("Aquib", sayGoodbye);
// Output:
// Hello, Aquib!
// Goodbye!
In this example, sayGoodbye
is passed as a callback to the greet
function. After greet
logs the greeting, it calls sayGoodbye
.
Asynchronous Callbacks
Callbacks are especially useful in asynchronous operations, such as handling API requests or reading files, where you need to perform actions after an operation completes without blocking the execution of the code.
function delayedGreeting(name, callback) {
setTimeout(() => {
console.log(`Hello, ${name}!`);
callback();
}, 2000); // 2 seconds delay
}
function notify() {
console.log('Greeting completed!');
}
delayedGreeting("Aquib", notify);
// Output (after 2 seconds delay):
// Hello, Aquib!
// Greeting completed!
In this example, the greeting is delayed by 2 seconds using setTimeout
, and after the greeting, the notify
function is called.
Using Anonymous Functions as Callbacks
Anonymous functions are often used as callbacks, especially for simple operations.
function processArray(arr, callback) {
for (let i = 0; i < arr.length; i++) {
arr[i] = callback(arr[i]);
}
return arr;
}
const numbers = [1, 2, 3, 4];
const doubled = processArray(numbers, function(num) {
return num * 2;
});
console.log(doubled); // Output: [2, 4, 6, 8]
In this example, an anonymous function is passed as a callback to processArray
to double each number in the array.
Callback Hell
Callback hell, also known as the "Pyramid of Doom," refers to a situation where callbacks are nested within other callbacks multiple levels deep, making the code hard to read and maintain.
doSomething(function(result1) {
doSomethingElse(result1, function(result2) {
doAnotherThing(result2, function(result3) {
doFinalThing(result3, function(result4) {
console.log("Done!");
});
});
});
});
In ES6 JavaScript introduced Promise()
, this is a replacement of callbacks because as you saw in the last example "Callback Hell", now we can handle the callbacks with even better and easier way.
Promises
Promises are a modern way to handle asynchronous operations in JavaScript. They provide a cleaner, more readable, and more manageable alternative to traditional callback-based approaches. A promise represents a value that may be available now, or in the future, or never.
Promise Structure
Pending: The initial state, neither fulfilled nor rejected.
Fulfilled: The operation completed successfully.
Rejected: The operation failed.
A promise is created using the Promise
constructor, which takes a function (executor) with two parameters: resolve
and reject
.
const myPromise = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("Operation was successful!");
} else {
reject("Operation failed!");
}
});
Using Promises
Promises are consumed using the then
, catch
, and finally
methods.
then
Method
The then
method is used to handle a fulfilled promise.
myPromise.then((message) => {
console.log(message); // Output: Operation was successful!
});
catch
Method
The catch
method is used to handle a rejected promise.
const myPromise = new Promise((resolve, reject) => {
const success = false;
if (success) {
resolve("Operation was successful!");
} else {
reject("Operation failed!");
}
});
myPromise.catch((error) => {
console.error(error); // Output: Operation failed!
});
finally
Method
The finally
method is executed regardless of the promise's outcome, whether it is fulfilled or rejected.
myPromise.finally(() => {
console.log("Promise has been settled."); // Output: Promise has been settled.
});
Chaining Promises
Promises can be chained to handle a sequence of asynchronous operations.
const fetchData = new Promise((resolve, reject) => {
setTimeout(() => resolve("Data fetched"), 1000);
});
fetchData
.then((data) => {
console.log(data); // Output: Data fetched
return "Processing data";
})
.then((processMessage) => {
console.log(processMessage); // Output: Processing data
return "Data processed";
})
.then((finalMessage) => {
console.log(finalMessage); // Output: Data processed
})
.catch((error) => {
console.error(error);
})
.finally(() => {
console.log("All operations completed"); // Output: All operations completed
});
Let's understand this with a small demonstration, we will order a pizza:
function orderPizza () {
return new Promise(function(res, rej) {
setTimeout(() => {
const pizza = "pizza";
res(pizza);
rej("An error occured while ordering pizza.")
}, 2000);
});
}
const pizzaPromise = orderPizza();
pizzaPromise.then((pizza) =>{
console.log(`my ${pizza} has been ordered.`)
}).catch(error => {
console.log(error)
}) // Output: My pizza has been ordered.
what would happen if we were not able to place the order? let's see:
function orderPizza () {
return new Promise(function(res, rej) {
setTimeout(() => {
const pizza = "pizza";
//res(pizza);
rej("An error occured while ordering pizza.")
}, 2000);
});
}
const pizzaPromise = orderPizza();
pizzaPromise.then((pizza) =>{
console.log(`my ${pizza} has been ordered.`)
}).catch(error => {
console.log(error)
}) // Output: An error occured while ordering pizza.
- Here there was no response
res
so it went for rejectrej
and threw an error inside.catch
.
Async/Await
Async/await is a modern syntax in JavaScript that allows you to write asynchronous code in a more synchronous and readable manner. It is built on top of Promises and provides a way to handle asynchronous operations more gracefully without deeply nested callbacks or complex promise chains.
The async
Keyword
The async
keyword is used to define an asynchronous function. An async
function always returns a Promise. If the function returns a value, the Promise will be resolved with that value. If the function throws an error, the Promise will be rejected with that error.
Syntax
async function functionName() {
// function body
}
async function greet() {
return "Hello, World!";
}
greet().then((message) => console.log(message)); // Output: Hello, World!
The await
Keyword
The await
keyword is used to pause the execution of an async
function until a Promise is resolved or rejected. It can only be used inside async
functions. await
makes the code wait for the Promise to resolve and returns the resolved value. If the Promise is rejected, await
throws the rejected value.
Syntax
let result = await promise;
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function sayHello() {
console.log('Start');
await delay(2000); // Waits for 2 seconds
console.log('Hello!');
console.log('End');
}
sayHello();
// Output:
// Start
// (2 seconds delay)
// Hello!
// End
Combining async
and await
Let's combine async
and await
to perform asynchronous operations in a more readable way. We will again be creating an order pizza function:
function getCheese() {
return new Promise((res, rej) => {
setTimeout(() => {
res("๐ง");
}, 1000);
});
}
function getDough(cheese) {
return new Promise((res, rej) => {
setTimeout(() => {
res(`${cheese} ๐ซ`);
}, 2000);
});
}
function getPizza(dough) {
return new Promise((res, rej) => {
setTimeout(() => {
res(`${dough} ๐`);
}, 2000);
});
}
async function orderPizza() {
const cheese = await getCheese();
const dough = await getDough(cheese);
const pizza = await getPizza(dough);
return `Here is your ${pizza}`;
}
orderPizza().then((pizza) => {
console.log(pizza);
}) // Output: (after 5 seconds) Here is your ๐ง ๐ซ ๐
Async/await makes asynchronous code easier to read and maintain by allowing you to write it in a synchronous style.
fetch
API
The Fetch
API is a modern, promise-based method for making HTTP requests in JavaScript. It provides a more powerful and flexible feature set than the older XMLHttpRequest
and is much easier to use.
Usage
The fetch
function is used to make a request to a specified URL. It returns a promise that resolves to the response of the request.
Syntax
fetch(url, options)
.then(response => {
// handle the response
})
.catch(error => {
// handle errors
});
We will only understand the GET request for now, Here's a real life example:
async function fetchData() {
try { // Random API from the internet.
const response = await fetch("https://dummyjson.com/products/2");
console.log(response);
// Converting to JSON format.
const data = await response.json();
console.log(data);
//loadData function will be created on next JavaScript example.
loadData(data);
} catch (err) {
console.log(err)
}
}
fetchData(); // Output: A big object filled with many data's.
Now let's take an example that we want to use this data dynamically inside of our webpage:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- Very basic code -->
<h2 id="title">dummy text</h2>
<img id="img" src="" alt="">
<script src="fetch.js"></script>
</body>
</html>
In the last JavaScript example we called our function by fetchData()
, after that line we will do a simple DOM manipulation:
function loadData(data) {
const title = document.getElementById("title");
const img = document.getElementById("img")
title.innerHTML = data.brand
img.src = data.thumbnail
}
- Now the data will be dynamically added to the webpage without writing anything, you can add more fields by observing whats inside of the data object printed on your console.
On the creation of a dynamic website it is essential to understand how to fetch the data from the given API, not just GET request but POST request as well.
Subscribe to my newsletter
Read articles from Syed Aquib Ali directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Syed Aquib Ali
Syed Aquib Ali
I am a MERN stack developer who has learnt everything yet trying to polish his skills ๐ฅ, I love backend more than frontend.