Topic 002: Closure, Callback, Async/Await, Promises
Sure! Let’s go through each concept with some JavaScript code examples.
Closure is a fundamental concept in JavaScript that allows functions to retain access to variables from their containing scope even after the parent function has finished executing. This means that inner functions have access to the variables and parameters of their outer function, even after the outer function has returned.
function outerFunction() {
let outerVar = "I am outer";
function innerFunction() {
console.log(outerVar);
}
return innerFunction;
}
let closureExample = outerFunction();
closureExample(); // Output: I am outer
In this example, innerFunction
retains access to the outerVar
variable even after outerFunction
has finished executing. This is possible because of closure.
Callbacks are functions that are passed as arguments to another function and are executed after the completion of that function. They are commonly used in asynchronous programming to handle asynchronous tasks.
function fetchData(callback) {
setTimeout(() => {
callback("Data fetched");
}, 2000);
}
function displayData(data) {
console.log(data);
}
fetchData(displayData); // Output (after 2 seconds): Data fetched
In this example, fetchData
is an asynchronous function that simulates fetching data after 2 seconds. The displayData
function is passed as a callback to fetchData
, and it gets executed with the fetched data after the asynchronous task completes.
Promises are objects representing the eventual completion or failure of an asynchronous operation. They allow you to handle asynchronous operations more elegantly than callbacks.
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched");
}, 2000);
});
}
fetchData()
.then((data) => console.log(data)) // Output (after 2 seconds): Data fetched
.catch((error) => console.error(error));
In this example, fetchData
returns a Promise that resolves with the fetched data after 2 seconds. We use the then
method to handle the successful resolution of the Promise and the catch
method to handle any errors that might occur.
A Promise in JavaScript represents a future value or an asynchronous operation that will eventually produce a value or result. It can be in one of the three states: pending
, fulfilled
, or rejected
.
Here’s an example of a Promise in JavaScript:
// Creating a promise
const myPromise = new Promise((resolve, reject) => {
// Asynchronous operation, e.g., fetching data
setTimeout(() => {
const data = 42;
if (data) {
resolve(data); // If successful, call resolve with the result
} else {
reject(new Error("Data not found")); // If failed, call reject with an error
}
}, 2000); // Simulating a delay of 2 seconds
});
// Consuming the promise
myPromise
.then((result) => {
console.log("Promise resolved with result:", result);
})
.catch((error) => {
console.error("Promise rejected with error:", error);
});
In this code:
new Promise()
constructor. Inside the constructor, we define an executor function that takes two parameters: resolve
and reject
. These are functions provided by JavaScript to either fulfill or reject the promise.setTimeout
here). When the operation completes successfully, we call resolve
with the result. If the operation fails, we call reject
with an error..then()
method, which takes a callback function to handle the resolved value, and the .catch()
method, which takes a callback function to handle any errors that occur during the promise execution.We can also create custom promises for our specific needs. Here’s an example:
function customPromise(condition) {
return new Promise((resolve, reject) => {
if (condition) {
resolve("Custom promise resolved");
} else {
reject(new Error("Custom promise rejected"));
}
});
}
// Consuming the custom promise
customPromise(true)
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
In this code:
customPromise
that takes a condition
parameter.condition
, we either resolve the promise with a success message or reject it with an error message..then()
and .catch()
to handle the resolution or rejection.Promise.all()
to make concurrent API calls and handle their responses once they’re all complete. Here’s a basic example:const axios = require("axios");
async function fetchData() {
try {
const [response1, response2, response3] = await Promise.all([
axios.get("https://api.endpoint1.com"),
axios.get("https://api.endpoint2.com"),
axios.get("https://api.endpoint3.com"),
]);
// Handle responses here
console.log(response1.data);
console.log(response2.data);
console.log(response3.data);
} catch (error) {
console.error("Error fetching data:", error);
}
}
fetchData();
This code will make three API calls concurrently using Axios (you can use any HTTP client library), and once all three calls are complete, it will handle the responses accordingly.
Async/await is a syntactic sugar built on top of Promises, providing a more readable and synchronous-like way to write asynchronous code.
async function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Data fetched");
}, 2000);
});
}
async function displayData() {
try {
const data = await fetchData();
console.log(data); // Output (after 2 seconds): Data fetched
} catch (error) {
console.error(error);
}
}
displayData();
In this example, fetchData
returns a Promise as before. However, with async
/await
, we can use the await
keyword to pause the execution of displayData
until the Promise returned by fetchData
is resolved or rejected. This results in code that looks more synchronous and is easier to understand.