Code Endeavor's Blog

Javascript Simplified Part IV
Promises Promises

Introduction

This is part four of the series javascript simplified. This entry discusses the concept of Promises, which was added to the javascript language as part of ES6 / ECMAScript 2015. Promises allow for cleaner code when dealing with callbacks and will lay the foundation for our understanding of the next article on async/await.

Review of Callbacks

Our second article, Callbacks, Anonymous Functions, and Fat Arrows, introduced us to a callback, which is an important concept to know when dealing with a language like javascript. I say this because javascript is a language that can only do one thing at a time, otherwise referred to as single-threaded. So if we have some logic to perform that will take a long time, we do not want to lock up our program while we wait for an answer. Typically, waiting for a response from an AJAX request or opening a file (in node) are scenarios where we need to wait without locking.

Calculate Answer

For this example we will use a setTimeout to simulate some work that will take a while to complete.

function calculateAnswer(callback)
{
  console.log('Calculating, please wait...');
  setTimeout(() => callback(42), 1000);  //wait 1 second then invoke our callback function passing in our response
}

calculateAnswer(answer => console.log('the answer is ' + answer));
console.log('Do you have my answer?');  //called right after we call calculate answer
Calculating, please wait...
Do you have my answer?
the answer is 3

Take note in the order of the output. When we call calculateAnswer

  • first writes the Calculating, please wait...
  • initiates the wait for a second giving a reference to the callback function
  • instantly the code moves back to the caller and outputs Do you have my answer?
  • Finally, when the timeout hits, the callback is run, returning the answer to the log.

Handling Errors Are Problematic

This is a common pattern in javascript. Unfortunately, when dealing with errors, it is difficult to provide a nice way to handle errors. Consider the following.

function calculateAnswer(callback)
{
  console.log('Calculating, please wait...');
  setTimeout(() => {
    var isError = new Date().getMilliseconds() % 2 == 0;     //get current clock's milliseconds, if even number we will generate an error.
    if (isError)
      throw 'We have an error';
    callback('the answer is 3');
  }, 1000); 
}

try {
  calculateAnswer(answer => console.log(answer));
} catch (error) {                       //error is not caught since code is already outside the try/catch due to it be async
  console.log(error);
}

console.log('Do you have my answer?');  //called right after we call calculate answer

Promises To the Rescue

This is one reason a new concept called promises were added to ES6. What is a Promise you ask?

A Promise is an object representing the eventual completion or failure of an asynchronous operation.

We write a promise by wrapping our asynchronous code in the new Pomise object. Its constructer expects a function that has two arguments: resolve and reject. These arguments are called when the asynchronous code either is successful (resolve) or fails (reject).

function calculateAnswer()
{
  return new Promise((resolve, reject) => {
    console.log('Calculating, please wait...');
    setTimeout(() => {
        var isError = new Date().getMilliseconds() % 2 == 0; 
        if (isError)
          reject('We have an error');   //call the reject parameter passing in the message
        else
          resolve('the answer is 3');   //call the successful (resolve) parameter passing in the result.
      }, 1000); 
    }
  );
}

calculateAnswer()
  .then(answer => console.log(answer))  //we now chain our method with .then which will only be called when resolve 
  .catch(error => console.log(error));  //if we had an error we will have called reject, and this code is called

console.log('Do you have my answer?');  //called right after we call calculate answer

We no longer need a try catch block to handle the errors as the promise catch will be called when we wish to report an error.

Avoiding the Callback Pyramid

While error handling is nice it is not the only reason Promises were introduced. Consider the following code.

function calculateAnswer(input, callback)
{
  var answer = input + 1;
  console.log('Calculating, please wait...');
  setTimeout(() => callback(answer), 1000); 
}

calculateAnswer(1, answer1 => {
  console.log(answer1);                     //output 2
  calculateAnswer(answer1, answer2 => {     //use answer to calculate next answer
    console.log(answer2);                   //output 3
    calculateAnswer(answer2, answer3 => {   //use answer to calculate next answer
      console.log(answer3);                 //output 4
    });
  });
});

Notice all the indentations for each callback, requiring a new argument each time. The indentation of the code is commonly referred to a callback pyramid (or callback hell)

By using a promise, our code can look much more friendly, through the use of chaining our promises.

function calculateAnswer(input)
{
  return new Promise(resolve => {
    var answer = input + 1;
    console.log('Calculating, please wait...');
    setTimeout(() => resolve(answer), 1000);  
  });
}

calculateAnswer(1)
  .then(answer => { 
    console.log(answer);            //output 2
    return calculateAnswer(answer); //use answer to calculate next answer
  })
  .then(answer => {
    console.log(answer);            //output 3
    return calculateAnswer(answer); //use answer to calculate next answer
  })
  .then(answer => {
    console.log(answer);            //output 4
  })
  .catch(error => console.log(error));  //bonus: catch error 

This gives you a good introduction to promises and a few reasons for using them. These reasons, however, are not the best part about using promises. The next blog entries will introduce us to the internals of javascript and the wonders of async await. Stay tuned! 😉