This content originally appeared on DEV Community and was authored by randhavevaibhav
callback based –
In callback based function we provide a callback function as an argument to a function like
below –
callbackBasedFn("Hello", (err, data) => {
if (err) {
throw new Error(err);
} else {
console.log(`data : ${data}😉`);
}
});
Inside the callback we are getting err or data depending on the result of callbackBasedFn()
the callbackBasedFn
can have internal implementation like below –
function callbackBasedFn(data, cb) {
setTimeout(() => {
if (data === "error") {
cb("Something went wrong 😵💫", null);
}
cb(null, data);
}, 1000);
}
In this function we are doing some async operation that will result in data or err.
So if we want to process the output of callbackBasedFn()
we have to do it in callback fun provided to callbackBasedFn()
itself like below –
callbackBasedFn("Hello", (err, data) => {
if (err) {
throw new Error(err);
} else {
console.log(`data : ${data}😉`);
const operation = data + " add some lines";
// ⬆ some operations
}
});
It is ok for one function but what if we have bunch of function which are depending on output of the previous function calls and all of them are async ?
Then code will get quite messy and un-readable like below –
callbackBasedFn("Hello", (err, data) => {
if (err) {
throw new Error(err);
} else {
console.log(`data : ${data}😉`);
const op1 = data + " add some lines";
// ⬆ some operations
callbackBasedFn2(op1,(err,data)=>{
if (err) {
throw new Error(err);
} else {
console.log(`data : ${data}😉 2`);
const op2 = data + " add some more lines";
callbackBasedFn3(op2,(err,data)=>{
if (err) {
throw new Error(err);
} else {
console.log(`data : ${data}😉 3`);
const op3 = data + " add some more lines";
})
})
}
});
Due to this heavy nesting of function it also got fancy name The Callback hell 👿
.
To solve this promises
where introduce.
Promise based –
A Promise
is a JS class which returns a Promise
object when called with new
keyword.
while calling a Promise constructor with new
keyword we pass a constructor function
which receives 2 arguments resolve
and reject
. we can call our callback based function callbackBasedFn()
here and resolve
it with data if everything went well or reject
it with err
if any error occurred.
like below –
const newPromiseObj = new Promise((resolve,reject)=>{
callbackFunc((err,data)=>{
if(err)
{
reject(err)
}else{
resolve(data)
}
})
})
Then how will we catch errors and get data?
Ans – with .then()
and .catch()
methods.
This newPromiseObj
comes with .then()
and .catch()
methods which accepts a callback for data and error resp. we will get our data and error like below –
newPromiseObj.then((data)=>{
console.log(data); // ⬆ Process data
}).catch((err)=>{
console.error(err) // ⬅ Process error
})
We can promisify
previous callbackBasedFn()
function like below –
const promisify = (callbackBasedFn) => {
return new Promise((resolve, reject) => {
callbackBasedFn("Hello 1", (err, data) => {
if (err) {
reject(err);
} else {
resolve(`data : ${data}😉`);
}
});
});
};
and consume it like below –
promisify(callbackBasedFn)
.then((res) => {
console.log(res);
}).catch((err) => console.error(err));
OUTPUT - data : Hello 1😉
But again if we have multiple async functions were input of one function is depends on output of other function then we have to return new Promise
from .then()
block of previous promise.
and attached a new .then()
block and then process the data .
promisify(callbackBasedFn)
.then((res) => {
console.log(res);
return new Promise((resolve, reject) => {
callbackBasedFn(`${res} Hello 2`, (err, data) => {
if (err) {
reject(err);
} else {
resolve(`${data} 😉`);
}
});
});
})
.then((res) => {
console.log(res);
return new Promise((resolve, reject) => {
callbackBasedFn(`${res} Hello 3`, (err, data) => {
if (err) {
reject(err);
} else {
resolve(`data : ${data} 😉`);
}
});
});
})
.then((res)=> console.log(res))
.catch((err) => console.error(err));
OUTPUT -
Hello 1😉
Hello 1😉 Hello 2 😉
data : Hello 1😉 Hello 2 😉 Hello 3 😉
Well it avoids nesting of function calls but still we end up with another structure called then() ladder 🪜
. and also it is hard to follow what’s going on.
Then what to use ?
with await
with await we can literally wait for the promise to finish executing and then operate on the data like below –
const p1 = await promisify(callbackBasedFn);
console.log(p1);
const p2 = await new Promise((resolve,reject)=>{
callbackBasedFn(`${p1} Hello 2`, (err, data) => {
if (err) {
reject(err);
} else {
resolve(`${data}😉`);
}
});
});
console.log(p2)
const p3 = await new Promise((resolve,reject)=>{
callbackBasedFn(`${p2} Hello 3`, (err, data) => {
if (err) {
reject(err);
} else {
resolve(`data: ${data}😉`);
}
});
});
console.log(p3)
OUTPUT -
Hello 1😉
Hello 1😉 Hello 2😉
data: Hello 1😉 Hello 2😉 Hello 3😉
But what about error’s?
Async functions –
Now we have to use async functions –
const p1AsyncFunc = async () => {
try {
return await promisify(callbackBasedFn);
} catch (error) {
console.error(error);
}
};
const p2AsyncFunc = async (p1) => {
try {
return await new Promise((resolve, reject) => {
callbackBasedFn(`${p1} Hello 2`, (err, data) => {
if (err) {
reject(err);
} else {
resolve(`${data}😉`);
}
});
});
} catch (error) {
console.error(error);
}
};
const p3AsyncFunc = async (p2) => {
try {
return await new Promise((resolve, reject) => {
callbackBasedFn(`${p2} Hello 3`, (err, data) => {
if (err) {
reject(err);
} else {
resolve(`data: ${data}😉`);
}
});
});
} catch (error) {
console.error(error);
}
};
const p1 = await p1AsyncFunc();
console.log(p1)
const p2 = await p2AsyncFunc(p1);
console.log(p2)
const p3 = await p3AsyncFunc(p2);
console.log(p3)
OUTPUT -
Hello 1😉
Hello 1😉 Hello 2😉
data: Hello 1😉 Hello 2😉 Hello 3😉
Notice how each async function call is done one after another like sync function and we are also able to catch error for each function call.
This content originally appeared on DEV Community and was authored by randhavevaibhav