Header Ads Widget

Understanding JavaScript Promises


Introduction to Promises

Dealing with asynchronous code? Promises is the way to deal with it without writing multiple callbacks. Promises were standardized and introduced in ES2015, but in ES2017 they have been superseded by async function. Understanding of Promises is the foundation of learning Async functions.

Way Promises work

Once a promise has been called, it always start with Pending state. At this point, caller function waits for the promise function to finish it's job and then return either in Resolved or Rejected state. But as we know Javascript executes in asynchronous mode, so caller function will still continue doing it's job in parallel with promise doing it's job.

Creating Promises

The Promise API exposes a constructor which you can use to create Promise -

const printSomething = (isValid) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (isValid) {
        console.log('Yes I am valid');
        resolve();
      } else {
        console.log('Sorry I am invalid');
      }
    }, 1000);
  });
}

Here promise is checking a the passed Boolean value isValid. If the isValid is true, then promise is returning resolved promise , other returning rejected promise.

Promise provides resolve and reject to return values to the calling function in case of success or error respectively.

Consuming Promises

Let's now consume the promise with below code -

const printSomething = (isValid) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (isValid) {
        console.log('Yes I am valid');
        resolve();
      } else {
        console.log('Sorry I am invalid');
      }
    }, 1000);
  });
}

printSomething(true)
  .then(() => printSomething(true))
  .then(() => printSomething(false));

Here I am calling printSomething function which is returning Promise either in Resolve or Reject state based on the function parameter isValid.

Here is the outcome -
Yes I am valid
Yes I am valid
Sorry I am invalid

You can execute the code here.


Chaining Promises

You can chain promises by returning result from one promise and then using the same in the next promise.

const printSomething = (name, textToAdd) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      var retValue = '';
      if (name != null) {
        retValue = name + ' ' + textToAdd;
      } else {
        retValue = textToAdd;
      }
      console.log(retValue);
      resolve(retValue);
    }, 1000);
  });
}

printSomething(null, 'Hello')
  .then((result) => printSomething(result, 'Sudipta'))
  .then((result) => printSomething(result, 'Deb'));

As you can see in the above example, I am using the result from the first promise which is "Hello" and the using the same in the second promise to append "Sudipta" with "Hello", thus returning "Hello Sudipta". Finally using this in the third promise to append "Deb", this returning "Hello Sudipta Deb"

Here is the result
Hello
Hello Sudipta
Hello Sudipta Deb

You can execute the code here.


Handling Errors

Handling error can be done by either returning Promise is reject mode or throwing an error.
Let's understand the below example with Reject returning an error.

const printSomething = (name, textToAdd) => {
  return new Promise((resolve, reject) => {
    try {
      setTimeout(() => {
        if (textToAdd == null) {
          reject(new Error('textToAdd can not be null'));
        } else {
          var retValue = '';
          if (name != null) {
            retValue = name + ' ' + textToAdd;
          } else {
            retValue = textToAdd;
          }
          console.log(retValue);
          resolve(retValue);
        }
      }, 1000);
    } catch (e) {
      reject(e);
    }
  });
}

printSomething(null, 'Hello')
  .then((result) => printSomething(result, 'Sudipta'))
  .then((result) => printSomething(result, 'Deb'))
  .then((result) => printSomething(result, null))
  .then((result) => printSomething(result, 'From Canada'))
  .catch(err => console.log('Error occured: ' + err.message));

In the above example, I am returning Reject promise with new Error. Then using the same with catch statement while consuming the Promise. Here is the output -

Hello
Hello Sudipta
Hello Sudipta Deb
Error occured: textToAdd can not be null

You can execute the code here.
Now let's modify the same example, where instead of returning Reject promise, I will throw the error. This is helpful to handle runtime error.
const printSomething = (name, textToAdd) => {
  return new Promise((resolve, reject) => {
    try {
      setTimeout(() => {
        if (textToAdd == null) {
          throw "textToAdd can not be null";
        } else {
          var retValue = '';
          if (name != null) {
            retValue = name + ' ' + textToAdd;
          } else {
            retValue = textToAdd;
          }
          console.log(retValue);
          resolve(retValue);
        }
      }, 1000);
    } catch (e) {
      reject(e);
    }
  });
}

printSomething(null, 'Hello')
  .then((result) => printSomething(result, 'Sudipta'))
  .then((result) => printSomething(result, 'Deb'))
  .then((result) => printSomething(result, null))
  .then((result) => printSomething(result, 'From Canada'))
  .catch(err => console.log('Error occured: ' + err.message));

One important fact is that reject DOES NOT terminate control flow like a return statement does. In contrast throw does terminate control flow.

Let's have the below code with reject

const printSomething = (name, textToAdd) => {
  return new Promise((resolve, reject) => {
    try {
      setTimeout(() => {
        if (textToAdd == null) {
          reject(new Error('textToAdd can not be null'));
        } else {
          var retValue = '';
          if (name != null) {
            retValue = name + ' ' + textToAdd;
          } else {
            retValue = textToAdd;
          }
          console.log(retValue);
          resolve(retValue);
        }
      }, 1000);
    } catch (e) {
      reject(e);
    }
  });
}

printSomething(null, 'Hello')
  .then((result) => printSomething(result, 'Sudipta'))
  .then((result) => printSomething(result, 'Deb'))
  .then((result) => printSomething(result, null))
  .then((result) => printSomething(result, 'From Canada'))
  .catch(err => console.log('Error occured: ' + err.message))
  .then(() => console.log('I will still execute even after the error'));

Output:
"Hello"
"Hello Sudipta"
"Hello Sudipta Deb"
"Error occured: textToAdd can not be null"
"I will still execute even after the error"

You can execute the code here. Here you can see that catch and then statement are getting executed even after reject promise return.

Now let's have the same code with throw statement

const printSomething = (name, textToAdd) => {
  return new Promise((resolve, reject) => {
    try {
      setTimeout(() => {
        if (textToAdd == null) {
          throw "textToAdd can not be null";
        } else {
          var retValue = '';
          if (name != null) {
            retValue = name + ' ' + textToAdd;
          } else {
            retValue = textToAdd;
          }
          console.log(retValue);
          resolve(retValue);
        }
      }, 1000);
    } catch (e) {
      reject(e);
    }
  });
}

printSomething(null, 'Hello')
  .then((result) => printSomething(result, 'Sudipta'))
  .then((result) => printSomething(result, 'Deb'))
  .then((result) => printSomething(result, null))
  .then((result) => printSomething(result, 'From Canada'))
  .catch(err => console.log('Error occured: ' + err.message))
  .then(() => console.log('I will still execute even after the error'));

Output:

"Hello"
"Hello Sudipta"
"Hello Sudipta Deb"
"Uncaught textToAdd can not be null (line 6)"

You can execute the code here. You see this time the catch and then statements are not getting executed. The reason is that throw statement terminate the control flow.


Promise.all()

Promise.all() will return a promise when all the promises are resolved. If one of the promise is failed, it will reject immediately with the error message from the failed promise irrespective of whether other promises are resolved or not.

Let's have this below example: 

var returnDouble = (value) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (value > 0) {
        resolve(value * 2);
      } else {
        reject(resolve);
      }
    }, 1000);
  });
};

var promisesToMake = [returnDouble(20), returnDouble(10), returnDouble(5)];
var finalPromise = Promise.all(promisesToMake);

finalPromise.then(function (results) {
  console.log('Final Result: ' + results);
});

Output:
"Final Result: 40,20,10"
You can execute the code here. With promise.all(), instead of three different returns, it will return one single promise once all the promises are resolved.

Now let's forcefully reject one promise in the below example -

var returnDouble = (value) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (value > 0) {
        resolve(value * 2);
      } else {
        reject(resolve);
      }
    }, 1000);
  });
};

var promisesToMake = [returnDouble(20), returnDouble(0), returnDouble(5)];
var finalPromise = Promise.all(promisesToMake);

finalPromise
  .then((results) => console.log('Final Result: ' + results))
  .catch(err => console.log('Error occured'));

Output:
"Error occured"
You can execute the code here. In this example, the second call is failed, even though the first and third call is successful. But still the final result is failure.


Promise.race()

Promise.race() runs as soon as one of the promises you pass to it resolves, and it runs the attached callback just once with the result of the first promise resolved.
Here is the example 

var returnDouble = (value,time) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (value > 0) {
        resolve(value * 2);
      } else {
        reject(resolve);
      }
    }, time);
  });
};

var promisesToMake = [returnDouble(20,100), returnDouble(200,50), returnDouble(5,500)];
var finalPromise = Promise.race(promisesToMake);

finalPromise
  .then((results) => console.log('Final Result: ' + results))
  .catch(err => console.log('Error occured'));

Output:
"Final Result: 400"
You can execute the code here.

Further Study




Post a Comment

3 Comments