All Articles

Demystify: JavaScript Promise

inner peace

Today I will write on the JavaScript Promises. The baseline of this tutorial is mostly my personal notes on this Youtube video. I highly encourage you to go and check that video also. Huge shout-out to Tony Alicea for creating that video. I hope that this note will help you to quickly callback what you have learned in there. Let’s start then.

JavaScript is a single-threaded language. That means it will do one task at a time. JS as an execution stack. It will do one thing at a time and it executes the context. Ok, maybe a simple example will give more clarity.

console.log("I am going to start from here!!");

function mainFunction() {
  innerFunction();
  console.log("main function executed !!!");
}

function innerFunction(params) {
  console.log("inner function executed!!!");
}

mainFunction();

In here, withing the execution stack, first put the global context to execute. (We can consider as 1st console output). Then it will put mainFunction(). Inside the mainFunction() it is calling to innerFunction(). So then JS engine put that function upon that function.

But when we are working with JS we are working with concepts like timer and other functionalities(like Queue some functions or call to API). There are coming from the JS engine. Basically, these engines wrote from C or C++. So JS engine can borrow these concepts and lent JS.

For the simple explaining prospective, we can select the `setTimeOut` function. This is a functionality that we borrow from JS engine. When we execute a `setTimeOut` function, once time pass JS engine put a message to a Queue that saying “Okay, now you can execute the callback function.” So, then callback function will pass to Execution Stack and execute it. You can say it also like executing timeOutHandler.

If we have a few timeout functionalities inside of each we will end up with a pyramid and that coding structure called a Pyramid of Doom. This is really hard to debug the application.

If we have multiple callback functions, we can’t execute 2nd callback function because there is no specification regarding that. Another issue is once we handle the callback, later we can’t again handle it. There are main issues that we facing with utilizing the callbacks.

To address these issues we have Promise. Promise represents a future value. The definition for the promise as below.

The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

MDN Web Docs

It is really nice how he explains this in the original video. The Promise is standard that all the JS developer follows. It is like JS engine gives some feature and there is a hook to invoke this feature and once the feature is complete it gives featureHandler(callback) to invoke our code. Also feature’s result is pushed to the Queue and it will pass to the featureHandler’s(callback’s) parameter.

To understand the Promise better, Let’s create our own Promise. Basically, these are the concepts that use to create a Promise object. Let’s do this.

According to the definition, Promise will return a value in future. So in a given time, it could be in either pending that still doing its background task, either the task is complete or due to some reason task is failed/rejected.

const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;

Then we are going to create the Promise object. I named this as NewKindaPromise. I will pass the function as a parameter. This is a function that actually doing the task. For example, if I want to call an API I will do that inside that function. The Promise is meant for the waiting to a finish that task and what would do after finish it. We need to understand that Promise is wrapping “the waiting for some task to complete” idea.

In the NewKindaPriomise we define stating state, the value of that we waiting for, arrays of handlers and arrays of errors. In here we have overcome the issue that previously faced that it will only allow executing one handler function when work is done. But in here you will see an array of handlers.

function NewKindaPromise(executor) {
  let state = PENDING;
  et value = null;
  let handlers = [];
  let catches = [];
}

Now we are going to add the resolve function. When this function called, that waiting task is done and the result will receive as the parameter. Later This function will by executor function. Also here we are checking that task is still pending. If not we return immediately. We need to remember that Promises are meant for executing one time and it is done. No more changing the value.

Once in pending state, and once we are calling this means we receive the value. Then what we are going to do is execute all the handlers.

function resolve(result) {
  if (state !== PENDING) return;
  state = FULFILLED;
  value = result;
  handlers.forEach((h) => h(value));
}

Likewise, we can write a reject function when there is an error.

function reject(err) {
  if (state !== PENDING) return;
  state = REJECTED;
  value = err;
  catches.forEach((e) => e(value));
}

Now we are going to resolve another issue that we face. Register multiple callbacks and once we callback executed and after a few other tasks then execute another callback on the result of the Promise. We are using then function here.

We can check that Promise is fulfilled then execute the callback or add to the handlers array. (Obviously, we didn’t add the error handler).

Additionally, we are calling this function as a constructor. So, this refers to the function object.

For the last, when we are creating the Promise object on that moment we start to execute the executor function. This executor function takes 2 parameters in here(resolve and reject functions).

executor(resolve, reject);

Okay, so here is the complete the NewKindaPromise function.

const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;

function NewKindaPromise(executor) {
  let state = PENDING;
  let value = null;
  let handlers = [];
  let catches = [];

  function resolve(result) {
    if (state !== PENDING) return;

    state = FULFILLED;
    value = result;

    handlers.forEach((h) => h(value));
  }

  function reject(err) {
    if (state !== PENDING) return;
    state = REJECTED;
    value = err;
    catches.forEach((e) => e(value));
  }

  this.then = function (callback) {
    if (state === FULFILLED) {
      callback(value);
    } else {
      handlers.push(callback);
    }
  };

  executor(resolve, reject);
}

Now we are going to create a executor function that we are going to pass to Promise.

const doSomeWork = (res, rej) => {
  res("I am done!!!");
};

Any function that takes 2 parameters will do the trick in here. Hold on, you will say that nothing will happen in the future with that function. Like calling to an API or something. I just wanted to point out anything will work with it.

Ok then let’s change it to a setTimeOut function.

const doSomeWork = (res, rej) => {
  setTimeout(() => {
    res("I am done!!!");
  }, 1000);
};

Let’s create a NewKindaPromise

const myNewKindaPromise = new NewKindaPromise(doSomeWork);

When the moment we create it, it will start to execute the doSomeWork function.

Now we have the executor function. (This is the function that executes when a promise created ASAP.) Let’s add handlers to execute when this task is completed.

myNewKindaPromise.then((val) => {
  console.log(`1st value: ${val}`);
});

myNewKindaPromise.then((val) => {
  console.log(`2nd value: ${val}`);
});

So now, this should works. Lets’s run this. After a one-second later you will see this output.

  1st value: I am done!!!
  2nd value: I am done!!!

Even right now we know that our Promise is resolved we can add the handlers. Because we are using then handler and it will check that Promise is fulfilled and it will immediately execute. If not it will be added to handler array.

setTimeout(() => {
  myNewKindaPromise.then((val) => {
    console.log(`3nd value: ${val}`);
  });
}, 3000);

I think I wrote a lot of things and take a min and try to understand it slowly. This is the basic idea behind the Promise.

Here is the full code for the reference.

const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;

function NewKindaPromise(executor) {
  let state = PENDING;
  let value = null;
  let handlers = [];
  let catches = [];

  function resolve(result) {
    if (state !== PENDING) return;

    state = FULFILLED;
    value = result;

    handlers.forEach((h) => h(value));
  }

  function reject(err) {
    if (state !== PENDING) return;
    state = REJECTED;
    value = err;
    catches.forEach((e) => e(value));
  }

  this.then = function (callback) {
    if (state === FULFILLED) {
      callback(value);
    } else {
      handlers.push(callback);
    }
  };

  executor(resolve, reject);
}

const doSomeWork = (res, rej) => {
  setTimeout(() => {
    res("I am done!!!");
  }, 1000);
};

const myNewKindaPromise = new NewKindaPromise(doSomeWork);

myNewKindaPromise.then((val) => {
  console.log(`1st value: ${val}`);
});

myNewKindaPromise.then((val) => {
  console.log(`2nd value: ${val}`);
});

setTimeout(() => {
  myNewKindaPromise.then((val) => {
    console.log(`3nd value: ${val}`);
  });
}, 3000);

If you have anything to ask regarding this please leave a comment there. Also, I wrote this according to my understanding. So if any point is wrong, don’t hesitate to correct me. I really appreciate you.

That’s for today friends. See you soon. Thank you.

Main image credit