Callback, Promise and Async-await in JavaScript

Bittu Kumar
5 min readSep 11, 2023

--

Callbacks are often used when you have tasks that take time to complete or are asynchronous in nature.

Two important aspects of using callback in JavaScript.

  • Asynchronous operation:
    Any function/task which takes some time to execute, callback ensures that it doesn’t block the main thread and it keeps our page responsive and time taking task runs behind the scene. once it completed, we can use the result.
  • Sequential operation:
    If we have more than one asynchronous operation and we want to execute it sequentially because result of one task determine other task’s result.

Let’s understand with some example.

We will take one problem and will try to solve it by callback, promise and async-await method provided by JavaScript.

Let’s cover first one small example regarding the asynchronous problem of JavaScript then will start sequential operation.

// as setTimeout is asynchronous operation which requires a function to execute as callback. 
// It will not wait for the taken execution time. Once it finishes after 2 second, output will show

function getData(){
console.log("data fetched successfully")
}

console.log("Start of the main program");
setTimeout(getData, 2000);
console.log("End of the main program");

// "Start of the main program"
// "End of the main program"
// "data fetched successfully"

Now let see sequential approach of solving asynchronous task.

So below diagram is our problem.

Id → User Details → Repo Details → Commit Details

This is a list of asynchronous task which is executing in single thread.

// Synchronous operation 

function task1(id){
let userDetails= {id, userGithubName: "bittu"+id}
return userDetails;
}

function task2(userGithubName){
let repoDetails= {userGithubName, repos:['repo1', 'repo2', 'repo3'] }
return repoDetails;
}

function task3(repoName){
let commitDetails= {repoName, commits:['commit1', 'commit2','commit3' ]}
return commitDetails;
}

let task1result= task1(2);
let task2result= task2(task1result.userGithubName);
let task3Result= task3(task2result.repos[0]);

console.log(task3Result.commits)

// ['commit1', 'commit2', 'commit3']

Now each task is taking some time to execute, will put setTimeout for each task and see. (creating asynchronous nature using setTimeout)

// asynchronous task

function task1(id){
setTimeout(()=>{
let userDetails= {id, userGithubName: "bittu"+id}
return userDetails;
}, 1000)
}

function task2(userGithubName){
setTimeout(()=>{
let repoDetails= {userGithubName, repos:['repo1', 'repo2', 'repo3'] }
return repoDetails;
}, 2000)
}

function task3(repoName){
setTimeout(()=>{
let commitDetails= {repoName, commits:['commit1', 'commit2','commit3' ]}
return commitDetails;
}, 3000)
}


let task1result= task1(2);
console.log(task1result)
let task2result= task2(task1result.userGithubName);
let task3Result= task3(task2result.repos[0]);

console.log(task3Result.commits)

So here error you are seeing is due to:
It is trying to run synchronously and running in single thread as JavaScript is synchronous in nature.

task1 execution will take 1 second time so it will move to next line of code for execution.
now it is going to execute task2, but it need result coming from task1. but it is not available because it will take 1 second time. So that’s why this undefined error you can see.

Here we come with the solution of callback.

// Sequential operation, task of one result determine result of other task.
// callback and callback hell
function task1(id, callback) {
setTimeout(() => {
const userDetails = { id, userGithubName: "bittu" + id };
callback(userDetails);
}, 1000);
}

function task2(userGithubName, callback) {
setTimeout(() => {
const repoDetails = { userGithubName, repos: ['repo1', 'repo2', 'repo3'] };
callback(repoDetails);
}, 2000);
}

function task3(repoName, callback) {
setTimeout(() => {
const commitDetails = { repoName, commits: ['commit1', 'commit2', 'commit3'] };
callback(commitDetails);
}, 3000);
}

task1(2, (user) => {
task2(user.userGithubName, (repos) => {
task3(repos.repos[0], (commits) => {
console.log("Result: ", commits.commits);
});
});
});


// "Result:", ["commit1", "commit2", "commit3"]

Here, task1, task2, task3 will execute in sequential manner, irrespective of any of three task takes how much times.

task1 will execute then,
task2 will execute then,
task3 will execute

So you saw example of getting commits with callback where each task is dependent on previous task. and while solving this problem we came across nested callback and that is called callback hell,

Which is having some problem of code maintain and code readability.

So Promise comes into picture to resolve this issues.

// Solution through promise

function task1(id){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
const userDetails = { id:id, userGithubName: "bittu" + id };
resolve(userDetails)
}, 1000)
})
}

function task2(githubUserName){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
const repoDetails= {githubUserName:githubUserName, repos:['repo1', 'repo2', 'repo3'] };
resolve(repoDetails);
}, 2000)
})
}

function task3(repoName){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
let commitDetails= {repoName:repoName, commits:['commit1', 'commit2','commit3' ]};
resolve(commitDetails)
}, 1000)
})
}


task1(2)
.then((user)=>{
return task2(user.userGithubName);
})
.then((repoDetails)=>{
return task3(repoDetails.repos[0])
})
.then((commits)=>{
console.log(commits.commits)
})
.catch((error) => {
console.error("Error:", error);
});


// ["commit1", "commit2", "commit3"]

So we get the same result through promise as well. We get bit better readability but again if you see there is some problem of maintaining code due to nested then. This is called promise chain.

It is better than callback and it can be used.

But again we get one more solution for more optimization is

Async-await

This is most simplified way to solving our problem provided by Javascript.

Here we have no change, everything will be same as promise is having but way to handle last part where promise chain comes — is bit easy and readable.

// Async-await


function task1(id){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
const userDetails = { id:id, userGithubName: "bittu" + id };
resolve(userDetails)
}, 1000)
})
}

function task2(githubUserName){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
const repoDetails= {githubUserName:githubUserName, repos:['repo1', 'repo2', 'repo3'] };
resolve(repoDetails);
}, 2000)
})
}

function task3(repoName){
return new Promise((resolve, reject)=>{
setTimeout(()=>{
let commitDetails= {repoName:repoName, commits:['commit1', 'commit2','commit3' ]};
resolve(commitDetails)
}, 1000)
})
}

async function main(){
const user= await task1(2);
const repoDetails= await task2(user.userGithubName);
const commits= await task3(repoDetails.repos[0]);
console.log(commits.commits)
}

main();

//["commit1", "commit2", "commit3"]

There is a difference only in last part,

So here we have to make a async function and where ever we want sequential operation, put await keyword before that. only condition is where we are putting await, that function should return promise …….

Now you have comparative example of callback, promise and async-await.

We took one problem and solve it through all three ways.

Thanks for reading. Happy learning.

See you soon in next article.

--

--

Bittu Kumar
Bittu Kumar

Written by Bittu Kumar

Exploring Angular and Frontend development.

Responses (1)