Asynchronous programming (callbacks, async-await, promises) in JS
Asynchronous programming is a programming paradigm that allows for the execution of multiple tasks or processes at the same time without waiting for each other to complete. In traditional synchronous programming, tasks are executed sequentially, one after the other, and each task must be completed before the next one starts. In contrast, asynchronous programming enables tasks to be initiated and executed independently of the main program flow.
The key idea behind asynchronous programming is to handle tasks that may take some time to complete (such as I/O operations, network requests, or file operations) without blocking the execution of the entire program. Instead of waiting for a task to finish before moving on to the next one, asynchronous programming allows the program to continue its execution and handle the completion of tasks when they are done.
JavaScript, especially in the context of web development, is a language where asynchronous programming is commonly used. This is because many operations, like fetching data from a server or handling user input, are asynchronous. Asynchronous programming in JavaScript is often achieved through the use of callbacks, promises, and the async/await syntax.
Here are some key concepts related to asynchronous programming in JavaScript:
- Callbacks: Functions that are passed as arguments to other functions and are executed after the completion of a particular task.
- Promises: Objects representing the eventual completion or failure of an asynchronous operation. They provide a more structured way to handle asynchronous code compared to callbacks.
- Async/Await: A more recent syntax introduced in ECMAScript 2017 (ES8) that simplifies asynchronous code by allowing you to write it in a more synchronous-looking style. The
async
keyword is used to define a function that returns a promise and theawait
keyword is used to pause the execution of the function until the promise is resolved.
First let’s understand callback, promises, and async/await with a simple code:
let students = [{
name: "Harry potter",
house: "Gryffindor"
}, {
name: "Draco Malfoy",
house: "Slytherin"
}];
let newStudent = {
name: "Cedric Diggory",
house: "Hufflepuff"
};
//TimeOut is added to have some delay
const addCharacter = (character) => {
setTimeout(() => {
students.push(character);
}, 1000);
}
const getListOfSortedStudents = (list) => {
list.forEach((student, index) => {
console.log(`${index + 1}. ${student.name} is added to ${student.house}`);
})
};
// This function will add a new student to the list,
// but the catch here is it will take 1 second
addCharacter(newStudent);
// This will console the list of the sorted student
getListOfSortedStudents(students);
The output will be:
So here in the above code, the addCharacter
function is called with newStudent
, and it adds newStudent
to the students
array using setTimeout
with a delay of 1000 milliseconds (1 second). Meanwhile, the getListOfSortedStudents
function is called with the current state of the students
array(which still contains the initial value where there are only 2 students), and it prints the existing (2)students to the console. After the delay of 1000 milliseconds, the callback inside setTimeout
is executed, and newStudent
is added to the students
array.
Now here asynchronous programming comes into the picture. In an ideal scenario getListOfSortedStudents
should wait for the addCharacter
function to complete its functionality and then getListOfSortedStudents
do the desired tasks. Now let’s first achieve this using callback.
Here is the code the get the desired output using callback:
let students = [{
name: "Harry potter",
house: "Gryffindor"
}, {
name: "Draco Malfoy",
house: "Slytherin"
}];
let newStudent = {
name: "Cedric Diggory",
house: "Hufflepuff"
};
//TimeOut is added to have some delay
const addCharacter = (character, callback) => {
setTimeout(() => {
students.push(character);
callback(students);
}, 1000);
}
const getListOfSortedStudents = (list) => {
list.forEach((student, index) => {
console.log(`${index + 1}. ${student.name} is added to ${student.house}`);
})
};
addCharacter(newStudent, getListOfSortedStudents);
The output will be:
Explanation:
- The
students
array initially contains two objects representing Harry Potter and Draco Malfoy. - The
newStudent
object representing Cedric Diggory is created. - The
addCharacter
function is called withnewStudent
and thegetListOfSortedStudents
callback. It addsnewStudent
to thestudents
array usingsetTimeout
with a delay of 1000 milliseconds (1 second). - Meanwhile, the
getListOfSortedStudents
function is not directly called in the main code; instead, it is passed as a callback toaddCharacter
. - After the delay of 1000 milliseconds, the callback inside
setTimeout
is executed. It addsnewStudent
to thestudents
array and then calls the provided callback (getListOfSortedStudents
) with the updatedstudents
array. - The
getListOfSortedStudents
function is called with the updatedstudents
array, and it prints all three students to the console.
The above example demonstrates the use of a callback to handle the result of an asynchronous operation. The callback is executed once the asynchronous operation (adding a character) is completed.
Now let’s implement the same code using promises.
let students = [{
name: "Harry potter",
house: "Gryffindor"
}, {
name: "Draco Malfoy",
house: "Slytherin"
}];
let newStudent = {
name: "Cedric Diggory",
house: "Hufflepuff"
};
//TimeOut is added to have some delay
const addCharacter = (character) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
students.push(character);
resolve(students);
} catch (e) {
reject(e);
}
}, 1000);
});
}
const getListOfSortedStudents = (list) => {
list.forEach((student, index) => {
console.log(`${index + 1}. ${student.name} is added to ${student.house}`);
})
};
addCharacter(newStudent).then((listOfStudents) => {
getListOfSortedStudents(listOfStudents);
}, (error) => {
console.log("Error", error);
});
Explanation:
- The
addCharacter
function is modified to return a Promise. The asynchronous operation (adding a character to thestudents
array) is wrapped inside thePromise
constructor. If the operation is successful, it resolves with the updatedstudents
array; if an error occurs, it rejects with the error. - The
addCharacter(newStudent)
returns a Promise, and the.then()
method is used to handle the resolved value (success) and rejected value (error). - If the Promise is resolved, the success callback is executed, which calls
getListOfSortedStudents
with the updated list of students. - If the Promise is rejected, the error callback is executed, logging the error message.
This usage of Promises provides a more structured way to handle asynchronous operations and makes the code easier to read and maintain. Promises are especially useful when dealing with multiple asynchronous operations or when chaining asynchronous calls.
Now let’s implement the same code using async/await.
let students = [{
name: "Harry potter",
house: "Gryffindor"
}, {
name: "Draco Malfoy",
house: "Slytherin"
}];
let newStudent = {
name: "Cedric Diggory",
house: "Hufflepuff"
};
//TimeOut is added to have some delay
const addCharacter = (character) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
student.push(character);
resolve(students);
} catch (e) {
reject(e);
}
}, 1000);
});
}
const getListOfSortedStudents = (list) => {
list.forEach((student, index) => {
console.log(`${index + 1}. ${student.name} is added to ${student.house}`);
})
};
(async function() {
try {
await addCharacter(newStudent);
getListOfSortedStudents(students);
} catch (e) {
console.log("Error", e);
}
})();
Explanation:
- A self-invoking function has been called with
async
keyword and under that function, we are callingaddCharacter
function with andgetListOfSortedStudents
in a sequence. - Calls
addCharacter
withnewStudent
and waits for the promise to be resolved usingawait
. - After the promise is resolved, it calls
getListOfSortedStudents
to print the updated list of students.
The
async
andawait
keywords in JavaScript are used to work with asynchronous code and Promises in a more synchronous and readable manner. Theasync
keyword is used to define a function as asynchronous. Theawait
keyword is used inside an async function to wait for a Promise to resolve before proceeding with the execution of the code.
In summary, Asynchronous programming in JavaScript is crucial for handling concurrent tasks efficiently. Callbacks, promises, and async/await provide different approaches to managing asynchronous code, with each having its advantages. Promises and async/await are generally preferred for their readability and error-handling capabilities.
Thanks for Reading!!!