JavaScript Promise

JavaScript Promise and Callbacks

💡What is the meaning of asynchronous?

“Asynchronous,” often known as “async,” simply means “takes some time” or “occurs in the future, not now.”

💡What are callbacks?

There isn’t a special thing called a ‘callback’ in the JavaScript language, it’s just a convention.
Instead of immediately returning some result like most functions, Callback-based functions take some time to produce a result.

** Normal function **
Print the sum of two numbers after 1 second.

const sum = function(a, b) {
  console.log('return sum of two numbers, ' a + b);
}

sum(a, b);

** Callback function **
Requirement – Sum of two numbers after some seconds and return that sum to the Callee function.

//* Try with direct return sum value

// setTimeout is used to show ASYNC call. This may be a API Call as well.

const sum2 = function (a, b) {
    setTimeout(() => {
        return a + b;
    }, 1000);
}
console.log('sum 2 results ', sum2(2, 3)); // sum 2 results  undefined

//* Let's perform with callback based functions

const sum3 = function (a, b, cb) {
    setTimeout(() => {
        cb(a + b);
    }, 1000);
}
sum3(10, 20, function (result) {
    console.log('Sum is done', result); // Sum is done 30
});


💡Now first understand Parallel tasks and Serial tasks.

 * Parallel tasks -> When several tasks are independent, you can do those tasks simultaneously.

 * Serial tasks -> When some tasks are dependent, you can not do those tasks simultaneously. We will perform such tasks in serial, one after another.


** Example of Serial tasks **
Requirement: The output of the previous task is required for the next tasks.

    const sum = function (a, b, cb) {
        setTimeout(() => {
            cb(a + b);
        }, 1000);
    }
    const mul = function (sumResult, c, cb) {
        setTimeout(() => {
            cb(sumResult * c);
        }, 1000);
    }
    const minus = function (mulResult, d, cb) {
        setTimeout(() => {
            cb(mulResult - d);
        }, 1000);
    }

    function call(a, b, c, d, cb) {
        sum(a, b, function (res) {
            console.log('sum is', res);
            mul(res, c, function (res) {
                console.log('multiply is ', res);
                minus(res, d, function (res) {
                    console.log('Minus is ', res);
                    cb(res);
                })
            })
        })
    }

    call(10, 10, 5, 90, function(res) {
        console.log('final output of calculator, ', res)
    });

JavaScript Promise and Callbacks


👀 Observations in the above code:

  • function ‘call’ has nested callbacks. Here we have only 4 levels of callbacks but there may be n number of callbacks as well.
  • Nested callbacks inside of each other because they all depend on the previous callback result.
  • The code structure looks like a pyramid style and that is called the pyramid of doom.
  • Such a style of code is also called a 'Christmas tree'.

👉 This structure of callback is known as Callback Hell.


💡 How to solve callback hell?

Callback hell is not a software problem or JS problem, it’s a problem of bad code writing. Unstructured Code, Nested Code, and non-modular code create this issue and that can easily be solved using good code practice.

JavaScript Promise

a overcome of callback problems.

JavaScript Promise is used to handle asynchronous operations.
Promise does not return output immediately, instead, it returns a promise object (you can also call this a promise token), and once Promise completes its operation then returns output.

The promise is also a way to solve the callback hell problem. By using Promise Object we can remove nested callbacks. But we already learned above that by adopting the good practice, we can also solve nested callback problems. Promise is just a way.


💡 What is the actual use of Promise? Why do they come into the picture?

In Callback Based Asynchronous code we pass our callback function to the library function or Async function and they execute it. But the problem is we are giving our function control to a third party so they can use it any number of times and anywhere and it creates TRUST issues.
Inappropriate callback function use might negatively impact output. So it also breaks the Inversion of the Control principle.

Promise took measures to address the callback access problem to any location. We don’t need to pass the callback function to Promise. Once Promise is completed, it returns to us and then we can call our function on our side only. So here we have only full control of callback operations.


💻 ** Misuse of callbacks – Understand with Code **

// Create a method that returns sum from api (ASYNC call)

function printSumFromAPI(a, b, cb) {
    setTimeout(() => {
        cb(a + b);
        cb();
        cb();
    }, 2000);
}

// callback
const myFun = function (res) {
    console.log('Sum of two numbers returned from API is ', res);
}

// call of async method
printSumFromAPI(10, 20, myFun);

// Here 3 times callback function is called and thats why three different outputs are returned.

output >

Sum of two numbers returned from API is  30
Sum of two numbers returned from API is  undefined
Sum of two numbers returned from API is  undefined


💡How does Promise work?

Let’s look at a real example. When you go to a restaurant and order food on the counter. What do they do? They take your order, get payment, and return a token to you. This token is like a Promise token for your future food once food is ready.

Promise works on the same concept. When you create a Promise, It does not return the final value immediately. Instead of returning the final value, it returns a promise object to you. Once Promise is settled, you can call the required methods (then, catch, and finally) on the Promise object to access data.

new Promise((resolve, reject) => {
   console.log('I will print immediate because I am not in resolve or reject.') 
});

output >

I will print immediate because I am not in resolve or reject.
Promise { <pending> }

The initial state of Promise is pending. When it is neither resolved nor rejected then the state is Pending.
Promise returns a promise object immediately and returns serial operations output that are not async.

✔️ Promise has three main states –

pending: initial state.
fulfilled: the operation was completed successfully.
rejected: operation failed.

// Check promise after some milliseconds (without then or catch method)

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('I will be resolved after 2 seconds')
    }, 2000);
});

console.log(p1); // Promise { <pending> }

// If you check this promise after 2 seconds, till that time it will be resolved or rejected.

setTimeout(() => {
    console.log(p1); // Promise { 'I will be resolved after 2 seconds' }
}, 3000);


💡Promise Methods

✔️ then method

Promise then method takes up to two arguments: callback functions for the fulfilled and rejected cases of the Promise.
The first parameter is used for the fulfilled state and the second parameter is used for the reject state that works the same as the catch method.
Then method also returns a promise & that’s why you can chain other promise methods.

✔️ catch method

Called when the promise is rejected. It is a shortcut for Promise.prototype.then(undefined, onRejected)

✔️ finally

Called when promise settled (either fulfilled or rejected). It does not return any output like success or error.
The finally() method can be useful to do some cleanup once the promise is settled, regardless of its outcome.

//  then and catch methods

let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('Call then method.');
    }, 2000);
});

p2.then((res) => {
    console.log('Output with then method ', res);
}).catch((err) => {
    console.log('Error with catch method ', err);
})

JavaScript Promise and Callbacks


** Let’s try multiple resolve or reject methods inside Promise **

const p4 = new Promise((resolve, reject) => {
    resolve(1);
    resolve(2); // this will not run. resolve only called once at a time.
})
console.log(p4); // Promise { 1 }


const p5 = new Promise((resolve, reject) => {
    reject('error - 1');
    reject('error - 2'); // this will not run. reject only called once at a time.
})
console.log(p5); // Promise { <rejected> 'error - 1' }
p5.catch((error) => { console.log(error) })

const p6 = new Promise((resolve, reject) => {
    reject('error - 1 p6');
    resolve('resolve - 2'); // this will not run. Promise only return one state (first state which read).
})
console.log(p6); //Promise { <rejected> 'error - 1 p6' }
p6.catch((error) => { console.log(error) })

👉 Note: Promise only returns one state at a time which comes first. If a Promise is resolved first then it will marked as fulfilled. If Promise rejects first then marked as rejected.


💡Promise some other important methods

✔️ Promise.all()

The Promise.all() static method takes an iterable of promises and returns a single Promise when all of the input’s promises are fulfilled.
It rejects when any of the input’s promises is rejected, with this first rejection reason.

✔️ Promise.allSettled()

It returns a promise fulfilled when all of the input’s promises settle with an array of objects that describe the outcome of each promise.
Promise.allSettled() is used when tasks are not dependent on each other OR you’d always like to know the result of each promise.
Promise.all() may be more appropriate if the tasks are dependent on each other, or if you’d like to immediately reject any of them.

✔️ Promise.any()

It returns a promise fulfilled when any of the input’s promises are fulfilled, with this first fulfillment value.
It rejects when all of the input’s promises are rejected.

✔️ Promise.race()

The method name itself clarifies that whoever wins the race, will return as output. Here win does not mean to resolve. it may be resolved or rejected.
It returns the promise fulfilled with the first promise settled. The Promise.race() method is one of the promised concurrency methods.
It’s useful when you want the first async task to complete but do not care about its eventual state (i.e. it can either succeed or fail).

Read more here on mdn.

💡Now Convert the Callback-based code to promise-based code.

 What is Promisify?

Promisify is a function that can turn any given function into a promisified version of itself. The objective is to convert a function that uses traditional callback-based asynchronous programming into a function that returns a promise.

callback-based asynchronous programming

const myCallback = function (cb) {
    setTimeout(() => {
        console.log('I am running');
        cb(); // callback which you have to execute after async call.
    }, 2000);
}
myCallback(function () {
    console.log('I am running in callback function')
})

Conversion to Promise based

const myCallbackAsPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('I am running');
        resolve();
    }, 2000);
})
myCallbackAsPromise.then(function () {
    console.log('I am running in callback function using Promise')
});

👉Differences between callback-based and promise-based

Callback-based methods take a callback as an argument and run itself after the async process completes.
Promise does not take callback from you, they are resolved once the async process completes.

You can not trust callback-based methods because they can misuse your passed callback function.
You can trust Promise because then can only emit resolve or reject a single time.

In callback-type methods, you don’t have control over executing your function.
In the Promise type, you have control to execute your function.

JavaScript Promise & Callbacks

Leave a Reply

Your email address will not be published.