DEV Community

Cover image for Callback 📞 vs Promise 🤞 based functions
randhavevaibhav
randhavevaibhav

Posted on

Callback 📞 vs Promise 🤞 based functions

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}😉`);
  }
});
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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
  }
});
Enter fullscreen mode Exit fullscreen mode

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";  
        })
    })
    
  }
});
Enter fullscreen mode Exit fullscreen mode

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)
        }
    })
})
Enter fullscreen mode Exit fullscreen mode

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
})
Enter fullscreen mode Exit fullscreen mode

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}😉`);
      }
    });
  });
};
Enter fullscreen mode Exit fullscreen mode

and consume it like below -

promisify(callbackBasedFn)
  .then((res) => {
    console.log(res);
    }).catch((err) => console.error(err));
Enter fullscreen mode Exit fullscreen mode
OUTPUT - data : Hello 1😉
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode
OUTPUT - 
Hello 1😉
Hello 1😉 Hello 2 😉
data : Hello 1😉 Hello 2 😉 Hello 3 😉
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
OUTPUT -
Hello 1😉
Hello 1😉 Hello 2😉
data: Hello 1😉 Hello 2😉 Hello 3😉
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode
OUTPUT - 
Hello 1😉
Hello 1😉 Hello 2😉
data: Hello 1😉 Hello 2😉 Hello 3😉
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)