Promises in JavaScript

Promises are one of the coolest concepts that exist in Js. This concept become so famous that you will find different technologies trying to opt-in for something similar.

Promises in simple terms are readability enhancers and can help us to solve the problem of Inversion of Control. To understand the working of promises, we need to get a hang of two foundational concepts.

  • How to Create a Promise?

  • How to Consume a Promise?

Now before we start with these two fundamental concepts, we need to understand that promises are native to Js. You can find their discussion in the official docs in Js. And they are officially shipped with Js.

In technical terms, the promise is just another Js object with some special capabilities. Promise objects can be used as a placeholder in situations where the answer will be computed in the future.

A promise object has the following key properties.

  • State:- Every promise object has a state value that can be either pending or rejected or fulfilled.

    • Pending:- Initially the state of promise is pending when the work is in progress. It is also the default value.

    • Fulfilled:- If the future task is completed successfully then the state becomes fulfilled.

    • Rejected:- If the future task is not completed successfully then the state becomes rejected.

  • Value:- Every promise object has a value property associated with it. When we initially create the promise, the state of the promise is pending. So till the time state of the promise remains pending the value property remains undefined. When the state changes to either rejected or fulfilled the value can become something else.

  • On-Fulfilled:- This is an array that stores a bunch of callback functions that will be triggered (not run). Once the state of the promise object changes to fulfilled. All of these callback functions inside the array take one argument which is the value property of the same promise.

  • On-Rejected:- This is same to same as the on-fulfilled array. It's just that the callback functions stored inside this array are executed. Once the state of the promise changes to rejected. All of these callback functions inside the array take one argument which is the value property of the same promise.

Note:- Once you change the state of the promise from pending to rejected or fulfilled then you cannot change them again.

How to create a promise object?

To create a promise object we can use the promise class constructor. We can do new Promise it will create a brand new Promise Object for us.

Now this promise constructor requires a mandatory argument which is a callback function also referred to as executor callback. This executor callback takes two arguments resolve and reject. Which are mainly some functions.

let p = new Promise (function executor (resolve, reject){
});

Initially, the state of promise is pending and the value is undefined.

Js will not give any special permission to the promise object as it is a native feature. So the execution of code inside the executor callback is always synchronous until (asynchronous operations powered by runtime come up.)

let p = new Promise (function executor (resolve, reject){
    for(let i = 0; i < 100000000; i++){
        console.log(i);
     }
})

In the above piece of code, we have a blocking for loop. So the completion of the executor function is also halted and hence you will not immediately get the promise object. You need to wait for it.

let p = new promise(function executor(resolve, reject){
    let a = 10;
    setTimeout(function (){
        console.log("Timer done")
    }, 5000)
})

In the above piece of code, as there is no blocking piece of code, every line is just a constant time operation and even if we have a timer. This will be run by the runtime in the background. Js will trigger the timer and come back.

Now you can write your future task in executor callback. The future task could be some time-consuming task or downloading some heavy files. Once the future task is done you can do one of the following.

By making a signal success

To go to a fulfilled state we just need to call the resolve function with an argument. Whatever argument you pass in the resolve function will get allocated to the value property of the promise and the promise will move to the fulfilled state.

By making a signal error

To go to a rejected state we just need to call the reject function with an argument. Whatever argument you pass in the resolve function will get allocated to the value property of the promise and the promise will move to the rejected state.

let p = new Promise (function exec (resolve, reject){
    let a = 30;
    setTimeout(function f(){
        a += 10;
        resolve(a);
    }, 5000)
    a += 5;
});

In the above piece of code, we call the promise constructor with an exec callback. The moment we start the exec function execution. Then it creates a variable and then goes on to start a timer in the runtime. It just triggers the timer and comes back as it will not wait for it.

Once the triggering of the callback is done. Js comes and add 5 into a variable, and now no other line of code is left. So the exec function is done. which means we got a new promise object (and the timer is still running behind the scene).

Now the promise we got is going to be pending. After 45 when the timer will be completed. It will execute the setTimeout callback f. Where we call the resolve function with the value 45 and hence the state of the promise changes and the value property is updated to 45.

Same as it is for the reject function. In this case, the state will be rejected.

Note:- As I said the earlier state of promise change only once, So if you try to call remove or reject multiple times then it will take effect only once as if you just wrote one of them one time. (Value never changes again and again)

What happens when the state of promise changes?

When a state of promise changes from pending to fulfilled whatever value we call the resolve the function gets attached to the value property of the promise object and it cannot be changed. Apart from that, all the callback functions which were waiting in the on-fulfilled array were transferred to the Microtask queue.

So we already know about the Macrotask queue/ Callback queue.

But in promise when a promise is fulfilled, then all the functions which are meant to be executed after promise fulfillment (which are stored in the on-fulfillment array) go into the microtask queue.

You might be thinking, why do these callbacks have to go to the microtask queue? Why they cannot be immediately executed?

Because promise fulfillment will happen sometime in the future due to some async task. If we have some code running on our main thread and in between the promise fulfills then Js will never hamper the flaw of the main thread and will not execute these callback functions immediately. Hence they have to wait in the microtask queue.

Now You might be thinking when we have macrotask queue then why promise based callbacks go into the microtask queue?

Because Js wants to set the priority for the callback executions. The priority of the callbacks waiting in the microtask queue is always and always higher than callback waiting in the macrotask queue.

So if at any point of time, there is a callback waiting in the macrotask queue and another callback is waiting in the microtask queue, then the one in the microtask queue will be executed first.

Does the execution of microtask queue is also maintained by the event loop? Just like how it does for macrotask queue.

Yes, The evet loop keeps on checking that is the call stack is empty or not if the global code is completely executed or not. Once both are done. Then it first checks if there is a callback inside the microtask queue or not. If there is a callback inside the microtask queue then it will be executed first as the micro task queue has higher priority.

Once the microtask queue is empty then the microtask queue is looked into. In case the promise gets rejected then all the above theory is applied to the callback of the on-rejected array.

How to consume a promise:-

Read my other blog How to Consume a promise.