8 recipes to master Promises in ECMAScript 2018

0
1165
17 min read

What are Promises in ECMAScript?

In earlier versions of JavaScript, the callback pattern was the most common way to organize asynchronous code. It got the job done, but it didn’t scale well. With callbacks, as more asynchronous functions are added, the code becomes more deeply nested, and it becomes more difficult to add to, refactor, and understand the code. This situation is commonly known as callback hell.

Promises were introduced to improve on this situation. Promises allow the relationships of asynchronous operations to be rearranged and organized with more freedom and flexibility. In this context, today we will learn about Promises and how to use it to create and organize asynchronous functions. We will also explore how to handle error conditions.

Creating and waiting for Promises

Promises provide a way to compose and combine asynchronous functions in an organized and easier to read way. This recipe demonstrates a very basic usage of promises.

This recipe assumes that you already have a workspace that allows you to create and run ES modules in your browser for all the recipes given below:


How to do it…

  1. Open your command-line application and navigate to your workspace.
  2. Create a new folder named 03-01-creating-and-waiting-for-promises.
  3. Copy or create an index.html that loads and runs a main function from main.js.
  4. Create a main.js file that creates a promise and logs messages before and after the promise is created, as well as while the promise is executing and after it has been resolved:
// main.js 
export function main () { 
 
  console.log('Before promise created'); 
 
  new Promise(function (resolve) { 
    console.log('Executing promise'); 
    resolve(); 
  }).then(function () { 
    console.log('Finished promise'); 
  }); 
 
  console.log('After promise created'); 
}
  1. Start your Python web server and open the following link in your browser:
    http://localhost:8000/.
  2. You will see the following output:
Executing promises

How it works…

By looking at the order of the log messages, you can clearly see the order of operations. First, the initial log is executed. Next, the promise is created with an executor method. The executor method takes resolve as an argument. The resolve function fulfills the promise.

Promises adhere to an interface named thenable. This means that we can chain then callbacks. The callback we attached with this method is executed after the resolve function is called. This function executes asynchronously (not immediately after the Promise has been resolved).

Finally, there is a log after the promise has been created.

The order the logs messages appear reveals the asynchronous nature of the code. All of the logs are seen in the order they appear in the code, except the Finished promise message. That function is executed asynchronously after the main function has exited!

Resolving Promise results

In the previous recipe, we saw how to use promises to execute asynchronous code. However, this code is pretty basic. It just logs a message and then calls resolve. Often, we want to use asynchronous code to perform some long-running operation, then return that value.

This recipe demonstrates how to use resolve in order to return the result of a long-running operation.

How to do it…

  1. Open your command-line application and navigate to your workspace.
  2.  Create a new folder named 3-02-resolving-promise-results.
  3. Copy or create an index.html that loads and runs a main function from main.js.
  4. Create a main.js file that creates a promise and logs messages before and after the promise is created:
// main.js 
export function main () { 
 
  console.log('Before promise created'); 
 
  new Promise(function (resolve) { 
  }); 
 
  console.log('After promise created'); 
}
  1. Within the promise, resolve a random number after a 5-second timeout:
    new Promise(function (resolve) { 
      setTimeout(function () {
        resolve(Math.random());
      }, 5000); 
    })
  1. Chain a then call off the promise. Pass a function that logs out the value of its only argument:
   new Promise(function (resolve) { 
      setTimeout(function () { 
        resolve(Math.random()); 
      }, 5000); 
    }).then(function (result) {
      console.log('Long running job returned: %s', result);
    });
  1. Start your Python web server and open the following link in your browser:
    http://localhost:8000/.
  2. You should see the following output:
Open console

How it works…

Just as in the previous recipe, the promise was not fulfilled until resolve was executed (this time after 5 seconds). This time however, we passed the called  resolve immediately with a random number for an argument. When this happens, the argument is provided to the callback for the subsequent then function. We’ll see in future recipes how this can be continued to create promise chains.

Rejecting Promise errors

In the previous recipe, we saw how to use resolve to provide a result from a successfully fulfilled promise. Unfortunately, the code doesn’t always run as expected. Network connections can be down, data can be corrupted, and uncountable other errors can occur. We need to be able to handle those situations as well.

This recipe demonstrates how to use reject when errors arise.

How to do it…

  1. Open your command-line application and navigate to your workspace.
  2. Create a new folder named 3-03-rejecting-promise-errors.
  3. Copy or create an index.html that loads and runs a main function from main.js.
  4. Create a main.js file that creates a promise, and logs messages before and after the promise is created and when the promise is fulfilled:
   new Promise(function (resolve) { 
     resolve(); 
      }).then(function (result) { 
     console.log('Promise Completed'); 
   });
  1. Add a second argument to the promise callback named reject, and call reject with a new error:
    new Promise(function (resolve, reject) { 
      reject(new Error('Something went wrong'); 
    }).then(function (result) { 
    console.log('Promise Completed'); 
   });
  1. Chain a catch call off the promise. Pass a function that logs out its only argument:
    new Promise(function (resolve, reject) { 
      reject(new Error('Something went wrong'); 
     }).then(function (result) { 
     console.log('Promise Completed'); 
    }).catch(function (error) {      console.error(error);    });
  1. Start your Python web server and open the following link in your browser:
    http://localhost:8000/.
  2. You should see the following output:
Promises in ECMAScript

How it works…

Previously we saw how to use resolve to return a value in the case of a successful fulfillment of a promise. In this case, we called reject before resolve. This means that the Promise finished with an error before it could resolve.

When the Promise completes in an error state, the then callbacks are not executed. Instead we have to use catch in order to receive the error that the Promise rejects. You’ll also notice that the catch callback is only executed after the main function has returned. Like successful fulfillment, listeners to unsuccessful ones execute asynchronously.

See also

  • Handle errors with Promise.catch
  • Simulating finally with Promise.then

Chaining Promises

So far in this article, we’ve seen how to use promises to run single asynchronous tasks. This is helpful but doesn’t provide a significant improvement over the callback pattern. The real advantage that promises offer comes when they are composed.

In this recipe, we’ll use promises to combine asynchronous functions in series.

How to do it…

  1. Open your command-line application and navigate to your workspace.
  2. Create a new folder named 3-04-chaining-promises.
  3. Copy or create an index.html that loads and runs a main function from main.js.
  4. Create a main.js file that creates a promise. Resolve a random number from the promise:
   new Promise(function (resolve) { 
     resolve(Math.random()); 
   }); 
);
  1. Chain a then call off of the promise. Return true from the callback if the random value is greater than or equal to 0.5:
    new Promise(function (resolve, reject) { 
         resolve(Math.random()); 
   }).then(function(value) {
     return value >= 0.5;
   });
  1. Chain a final then call after the previous one. Log out a different message if the argument is true or false:
  new Promise(function (resolve, reject) { 
    resolve(Math.random()); 
  }).then(function (value) { 
    return value >= 0.5; 
  }).then(function (isReadyForLaunch) {
    if (isReadyForLaunch) {
      console.log('Start the countdown! ');
    } else {
      console.log('Abort the mission. ');
    }
});
  1. Start your Python web server and open the following link in your browser:
    http://localhost:8000/.
  2. If you are lucky, you’ll see the following output:

Open console

  1. If you are unlucky, we’ll see the following output:

Open console

How it works…

We’ve already seen how to use then to wait for the result of a promise. Here, we are doing the same thing multiple times in a row. This is called a promise chain. After the promise chain is started with the new promise, all of the subsequent links in the promise chain return promises as well. That is, the callback of each then function is resolve like another promise.

See also

  • Using Promise.all to resolve multiple Promises
  • Handle errors with Promise.catch
  • Simulating finally with a final Promise.then call

Starting a Promise chain with Promise.resolve

In this article’s preceding recipes, we’ve been creating new promise objects with the constructor. This gets the jobs done, but it creates a problem. The first callback in the promise chain has a different shape than the subsequent callbacks.

In the first callback, the arguments are the resolve and reject functions that trigger the subsequent then or catch callbacks. In subsequent callbacks, the returned value is propagated down the chain, and thrown errors are captured by catch callbacks. This difference adds mental overhead. It would be nice to have all of the functions in the chain behave in the same way.

In this recipe, we’ll see how to use Promise.resolve to start a promise chain.

How to do it…

  1. Open your command-line application and navigate to your workspace.
  2. Create a new folder named 3-05-starting-with-resolve.
  3. Copy or create an index.html that loads and runs a main function from main.js.
  4. Create a main.js file that calls Promise.resolve with an empty object as the first argument:
export function main () { 
  Promise.resolve({}) 
}
  1. Chain a then call off of resolve, and attach rocket boosters to the passed object:
export function main () { 
  Promise.resolve({}).then(function (rocket) {
      console.log('attaching boosters');
      rocket.boosters = [{
        count: 2,
        fuelType: 'solid'
      }, {
        count: 1,
        fuelType: 'liquid'
      }];
      return rocket;
    })
}
  1. Add a final then call to the chain that lets you know when the boosters have been added:
export function main () { 
  Promise.resolve({}) 
    .then(function (rocket) { 
      console.log('attaching boosters'); 
      rocket.boosters = [{ 
        count: 2, 
        fuelType: 'solid' 
      }, { 
        count: 1, 
        fuelType: 'liquid' 
      }]; 
      return rocket; 
    }) 
    .then(function (rocket) {
      console.log('boosters attached');
      console.log(rocket);
    })
}
  1. Start your Python web server and open the following link in your browser: http://localhost:8000/.
  1. You should see the following output:
Promises in ECMAScript - Open Console

How it works…

Promise.resolve creates a new promise that resolves the value passed to it. The subsequent then method will receive that resolved value as it’s argument. This method can seem a little roundabout but can be very helpful for composing asynchronous functions. In effect, the constituents of the promise chain don’t need to be aware that they are in the chain (including the first step). This makes transitioning from code that doesn’t use promises to code that does much easier.

Using Promise.all to resolve multiple promises

So far, we’ve seen how to use promises to perform asynchronous operations in sequence. This is useful when the individual steps are long-running operations. However, this might not always be the more efficient configuration. Quite often, we can perform multiple asynchronous operations at the same time.

In this recipe, we’ll see how to use Promise.all to start multiple asynchronous operations, without waiting for the previous one to complete.

How to do it…

  1. Open your command-line application and navigate to your workspace.
  2. Create a new folder named 3-06-using-promise-all.
  3. Copy or create an index.html that loads and runs a main function from main.js.
  4. Create a main.js file that creates an object named rocket, and calls Promise.all with an empty array as the first argument:
export function main() { 
  console.log('Before promise created'); 
 
  const rocket = {}; 
  Promise.all([]) 
 
  console.log('After promise created'); 
}
  1. Create a function named addBoosters that creates an object with boosters to an object:
function addBoosters (rocket) { 
  console.log('attaching boosters'); 
  rocket.boosters = [{ 
    count: 2, 
    fuelType: 'solid' 
  }, { 
    count: 1, 
    fuelType: 'liquid' 
  }]; 
  return rocket;  
}
  1. Create a function named performGuidanceDiagnostic that returns a promise of a successfully completed task:
function performGuidanceDiagnostic (rocket) { 
  console.log('performing guidance diagnostic'); 
 
  return new Promise(function (resolve) { 
    setTimeout(function () { 
      console.log('guidance diagnostic complete'); 
      rocket.guidanceDiagnostic = 'Completed'; 
      resolve(rocket); 
    }, 2000); 
  }); 
}
  1. Create a function named loadCargo that adds a payload to the cargoBay:
function loadCargo (rocket) { 
  console.log('loading satellite'); 
  rocket.cargoBay = [{ name: 'Communication Satellite' }] 
  return rocket; 
}
  1. Use Promise.resolve to pass the rocket object to these functions within Promise.all:
export function main() { 
 
  console.log('Before promise created'); 
 
  const rocket = {}; 
  Promise.all([
    Promise.resolve(rocket).then(addBoosters),
    Promise.resolve(rocket).then(performGuidanceDiagnostic),
    Promise.resolve(rocket).then(loadCargo)
  ]); 
 
  console.log('After promise created'); 
}
  1. Attach a then call to the chain and log that the rocket is ready for launch:
  const rocket = {}; 
  Promise.all([ 
    Promise.resolve(rocket).then(addBoosters), 
    Promise.resolve(rocket).then(performGuidanceDiagnostic), 
    Promise.resolve(rocket).then(loadCargo) 
  ]).then(function (results) {
    console.log('Rocket ready for launch');
    console.log(results);
  });
  1. Start your Python web server and open the following link in your browser:
    http://localhost:8000/.
  2. You should see the following output:
Open console

How it works…

Promise.all is similar to Promise.resolve; the arguments are resolved as promises. The difference is that instead of a single result, Promise.all accepts an iterable argument, each member of which is resolved individually.

In the preceding example, you can see that each of the promises is initiated immediately. Two of them are able to complete while performGuidanceDiagnostic continues. The promise returned by Promise.all is fulfilled when all the constituent promises have been resolved.

The results of the promises are combined into an array and propagated down the chain. You can see that three references to rocket are packed into the results argument. And you can see that the operations of each promise have been performed on the resulting object.

There’s more

As you may have guessed, the results of the constituent promises don’t have to return the same value. This can be useful, for example, when performing multiple independent network requests. The index of the result for each promise corresponds to the index of the operation within the argument to Promise.all. In these cases, it can be useful to use array destructuring to name the argument of the then callback:

 Promise.all([ 
  findAstronomers, 
  findAvailableTechnicians, 
  findAvailableEquipment 
]).then(function ([astronomers, technicians, equipment]) { 
  // use results for astronomers, technicians, and equipment 
});

Handling errors with Promise.catch

In a previous recipe, we saw how to fulfill a promise with an error state using reject, and we saw that this triggers the next catch callback in the promise chain. Because promises are relatively easy to compose, we need to be able to handle errors that are reported in different ways. Luckily promises are able to handle this seamlessly.

In this recipe, we’ll see how Promises.catch can handle errors that are reported by being thrown or through rejection.

How to do it…

  1. Open your command-line application and navigate to your workspace.
  2.  Create a new folder named 3-07-handle-errors-promise-catch.
  3. Copy or create an index.html that loads and runs a main function from main.js.
  4. Create a main.js file with a main function that creates an object named rocket:
export function main() { 
 
  console.log('Before promise created'); 
 
  const rocket = {}; 
 
  console.log('After promise created'); 
}
  1. Create a function addBoosters that throws an error:
function addBoosters (rocket) { 
  throw new Error('Unable to add Boosters'); 
}
  1. Create a function performGuidanceDiagnostic that returns a promise that rejects an error:
function performGuidanceDiagnostic (rocket) { 
  return new Promise(function (resolve, reject) { 
    reject(new Error('Unable to finish guidance diagnostic')); 
  }); 
}
  1. Use Promise.resolve to pass the rocket object to these functions, and chain a catch off each of them:
export function main() { 
 
  console.log('Before promise created'); 
 
  const rocket = {}; 
  Promise.resolve(rocket).then(addBoosters)
    .catch(console.error);
  Promise.resolve(rocket).then(performGuidanceDiagnostic)
    .catch(console.error);
 
  console.log('After promise created'); 
}
  1. Start your Python web server and open the following link in your browser:
    http://localhost:8000/.
  2. You should see the following output:
Open console

How it works…

As we saw before, when a promise is fulfilled in a rejected state, the callback of the catch functions is triggered. In the preceding recipe, we see that this can happen when the reject method is called (as with performGuidanceDiagnostic). It also happens when a function in the chain throws an error (as will addBoosters).

This has similar benefit to how Promise.resolve can normalize asynchronous functions. This handling allows asynchronous functions to not know about the promise chain, and announce error states in a way that is familiar to developers who are new to promises.

This makes expanding the use of promises much easier.

Simulating finally with the promise API

In a previous recipe, we saw how catch can be used to handle errors, whether a promise has rejected, or a callback has thrown an error. Sometimes, it is desirable to execute code whether or not an error state has been detected. In the context of try/catch blocks, the finally block can be used for this purpose. We have to do a little more work to get the same behavior when working with promises

In this recipe, we’ll see how a final then call to execute some code in both successful and failing fulfillment states.

How to do it…

  1. Open your command-line application and navigate to your workspace.
  2.  Create a new folder named 3-08-simulating-finally.
  3. Copy or create an index.html that loads and runs a main function from main.js.
  4. Create a main.js file with a main function that logs out messages for before and after promise creation:
export function main() { 
 
  console.log('Before promise created'); 
 
  console.log('After promise created'); 
}
  1. Create a function named addBoosters that throws an error if its first parameter is false:
function addBoosters(shouldFail) { 
  if (shouldFail) { 
    throw new Error('Unable to add Boosters'); 
  } 
 
  return { 
    boosters: [{ 
      count: 2, 
      fuelType: 'solid' 
    }, { 
      count: 1, 
      fuelType: 'liquid' 
    }] 
  }; 
}
  1. Use Promise.resolve to pass a Boolean value that is true if a random number is greater than 0.5 to addBoosters:
export function main() { 
 
  console.log('Before promise created'); 
 
  Promise.resolve(Math.random() > 0.5)
    .then(addBoosters) 
 
  console.log('After promise created'); 
}
  1. Add a then function to the chain that logs a success message:
export function main() { 
 
  console.log('Before promise created'); 
  Promise.resolve(Math.random() > 0.5) 
    .then(addBoosters) 
       .then(() => console.log('Ready for launch: ')) 
 
  console.log('After promise created'); 
}
  1. Add a catch to the chain and log out the error if thrown:
export function main() { 
  console.log('Before promise created'); 
  Promise.resolve(Math.random() > 0.5) 
    .then(addBoosters) 
    .then(() => console.log('Ready for launch: ')) 
       .catch(console.error) 
  console.log('After promise created'); 
}
  1. Add a then after the catch, and log out that we need to make an announcement:
export function main() { 
 
  console.log('Before promise created'); 
  Promise.resolve(Math.random() > 0.5) 
    .then(addBoosters) 
    .then(() => console.log('Ready for launch: ')) 
    .catch(console.error)
    .then(() => console.log('Time to inform the press.')); 
  console.log('After promise created'); 
}
  1. Start your Python web server and open the following link in your browser:
    http://localhost:8000/.
  2. If you are lucky and the boosters are added successfully, you’ll see the following output:

Open Console12. If you are unlucky, you’ll see an error message like the following:

Open console

How it works…

We can see in the preceding output that whether or not the asynchronous function completes in an error state, the last then callback is executed. This is possible because the catch method doesn’t stop the promise chain. It simply catches any error states from the previous links in the chain, and then propagates a new value forward.

The final then is then protected from being bypassed by an error state by this catch. And so, regardless of the fulfillment state of prior links in the chain, we can be sure that the callback of this final then will be executed.

To summarize, we learned how to use the Promise API to organize asynchronous programs. We also looked at how to propagate results through promise chains and handle errors. 

You read an excerpt from a book written by Ross Harrison, titled ECMAScript Cookbook. It’s a complete guide on how to become a better web programmer by writing efficient and modular code using ES6 and ES8.

ECMAScript Cookbook

Read Next:

What’s new in ECMAScript 2018 (ES9)?

ECMAScript 7 – What to expect?

Modular Programming in ECMAScript 6

 

LEAVE A REPLY

Please enter your comment!
Please enter your name here