What does Javascript Promise us for 2020?

sonicoder

Gábor Soós

Posted on November 3, 2019

What does Javascript Promise us for 2020?

Promises are available since ES2015 to simplify the handling of asynchronous operations. Promises have two well-known combinators: all and race. Both of them are useful, but they don't cover all the use-cases. What if we want to wait for all the Promises even if some of them go to error or only the first successful operation is essential? I'll show you the new Promise combinators that will help you give the answers to these questions.

First, we will look at the combinators that we already have and then look at two upcoming ones.

Promise.all (docs)

The all combinator takes multiple promises (an iterator, in most cases an array) and returns a single promise that resolves when all the promises are completed, or the iterator doesn't contain any element. There is no ordering of the given promises; they execute in parallel. However, the return value order of each input promise will be the same as were in the input. The returned promise will contain the value of the inputs in an array.

const first = Promise.resolve('Batman');
const second = Promise.resolve('Joker');

Promise
  .all([first, second])
  .then(results => {
    // results = ['Batman', 'Joker']
  });
Enter fullscreen mode Exit fullscreen mode

What happens if one of the promises get rejected? The returned promise gets rejected, and we won't know what happened with the others.

const first = Promise.resolve('Batman');
const second = Promise.reject(new Error('Joker'));

Promise
  .all([first, second])
  .then(results => {
    // we won't get here
  })
  .catch(error => {
    // Error: Joker
  });
Enter fullscreen mode Exit fullscreen mode

We can't wait for all the promises to finish even if some of them would be fulfilled.

Promise.race (docs)

The promise returned from the race function fulfills or rejects as soon as one of the passed promises resolve or reject. It is useful if you are interested in the first result and want to neglect the slow ones.

const first = Promise.resolve('Batman');
const second = Promise.resolve('Joker');

Promise
  .race([first, second])
  .then(result => {
    // result = 'Batman' or 'Joker'
  });
Enter fullscreen mode Exit fullscreen mode

The same happens when one of the promises get rejected.

const first = Promise.resolve('Batman');
const second = Promise.reject(new Error('Joker'));

Promise
  .race([first, second])
  .then(result => {
    // we get here
  })
  .catch(error => {
    // or here
  });
Enter fullscreen mode Exit fullscreen mode

We can't wait for the first promise to resolve if the previous one is rejected.

The future

The upcoming versions give us two new combinators that will help us overcome the limitations of all and race. The new versions will also introduce new methods to handle failed operations easier.

Promise.allSettled (docs)

The all combinator takes multiple promises and returns a single promise that resolves when all the inputs are completed or rejected. The big difference to the all combinator is that it won't be rejected if one the promises reject. allSettled will wait for all the others and return both fulfilled and rejected promise results.

const first = Promise.resolve('Batman');
const second = Promise.reject(new Error('Joker'));

Promise
  .allSettled([first, second])
  .then(results => {
    // results = [
      { status: 'fulfilled', value: 'Batman' }, 
      { status: 'rejected', reason: Error: Joker }
    ]
  });
Enter fullscreen mode Exit fullscreen mode

For every item that is fulfilled, we get an object with the status property fulfilled and the value property containing the return value of that promise. For rejected items, we get an object with the status property rejected and the reason property containing the error.

If you want to separate rejected and fulfilled promises, you will have to run a quick filter on the result array.

Promise.any (docs)

The promise returned from the any function waits until one of the supplied promises resolve. It will still resolve when some of the promises fail. If all the promises reject, the race function will also reject.

const first = Promise.resolve('Batman');
const second = Promise.reject(new Error('Joker'));

Promise
  .any([first, second])
  .then(result => {
    // result = 'Batman'
  });
Enter fullscreen mode Exit fullscreen mode

It is a great way to wait for the first successful operation to complete and ignore the rest.

Promise.prototype.finally (docs)

Promises have two states: fulfilled or rejected. When the promise is fulfilled it executes the then callback, when rejected the catch callback. What happens if we want to run some cleanup commands afterward? We have to include it in both callbacks. Here is where the finally method becomes the gamechanger because it is called in both scenarios.

const second = Promise.reject(new Error('Joker'));

second
  .then(result => {})
  .catch(error => {})
  .finally(() => {
    // do some cleanup
  });
Enter fullscreen mode Exit fullscreen mode

Promise.try

The method try receives a function that can throw synchronous errors and return rejected promises, and it will convert both types to a rejected promise.

Promise.try(() => {
  throw new Error('Joker'); // synchronous
  return Promise.reject(new Error('Joker')) // asynchronous
});
Enter fullscreen mode Exit fullscreen mode

It can come in handy when you do synchronous operations before asynchronous ones, for example in a command-line script. If you have synchronous code, error handling will be in a try-catch block. Asynchronous codes error handling will be in a catch callback. With Promise.try, you won't need separate error handling.

If the method is still not precise I would recommend reading this detailed article about the subject.

Availability

Promise.prototype.finally and Promise.allSettled are available in all modern browsers and in Node.js (from version 12), but the others are still in the draft stage. We need the corresponding shims to use them.

require('promise.allSettled').shim();
require('promise.any').shim();
require('promise.finally').shim();
require('promise.try').shim();
Enter fullscreen mode Exit fullscreen mode

You can find them under ECMAScript shims.

Summary

Organizing asynchronous code got a massive leap with Promises. It became more comfortable and more readable. Nowadays, the TC39 committee is working on how to make it more natural. The result is the two new combinator functions (allSettled, any), and the finally method along with the try method. If you like these methods, start using them today with one of the shims.

💖 💪 🙅 🚩
sonicoder
Gábor Soós

Posted on November 3, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Simplified: Javascript V8 engine ✨
javascript Simplified: Javascript V8 engine ✨

October 1, 2024

What is JSON?
javascript What is JSON?

September 20, 2024

Introduction to Node.js
javascript Introduction to Node.js

August 1, 2024

JavaScript on the Server: Node.js
javascript JavaScript on the Server: Node.js

July 28, 2024