avatarJennifer Fu

Summary

The provided web content offers a comprehensive guide on JavaScript Promises, covering their definitions, async/await usage, concurrency methods, and practical applications.

Abstract

The web content titled "Everything About JavaScript Promise" delves into the intricacies of JavaScript Promises, outlining their states and how they are created and used. It explains the basic structure of a Promise, including the use of resolve and reject callbacks, and demonstrates how to handle settled Promises with .then(), .catch(), and .finally() methods. The article further explores the cleaner syntax provided by async and await for handling Promises, and introduces the concept of Promise concurrency with methods like Promise.all(), Promise.any(), Promise.allSettled(), and Promise.race(). It also provides examples of practical Promise usages, such as summing values from multiple Promises, implementing a time-limited function, creating an asynchronous sleep function, and building a Promise pool to manage concurrent asynchronous operations. The guide aims to deepen the reader's understanding of Promises and their applications in asynchronous JavaScript programming.

Opinions

  • The author emphasizes the importance of understanding Promises for handling asynchronous operations in JavaScript.
  • The use of async and await is presented as a preferable alternative to traditional Promise chains for cleaner and more readable code.
  • The article suggests that mastering Promise concurrency methods is crucial for efficient asynchronous task management.
  • The practical examples provided are designed to illustrate real-world applications of Promises, enhancing the learning experience for developers.
  • The guide encourages readers to explore further by connecting to additional resources, indicating a commitment to continuous learning and improvement in web development.

Everything About JavaScript Promise

Details on Promise definitions, Promise async and await, Promise Concurrency, and Promise Usages

Photo by Pawel Czerwinski on Unsplash

Promise Definitions

A Promise is an object representing the eventual completion or failure of an asynchronous operation. A Promise is in one of these states:

  • pending: It is the initial state, neither fulfilled nor rejected.
  • fulfilled: It means that the operation has completed successfully.
  • rejected: It means that the operation has failed.

A Promise is said to be settled if it is either fulfilled or rejected, but not pending. A Promise is created by a function with two callbacks, resolve and reject. A Promise instance's Promise.prototype.then() method takes up to two arguments: the first argument is a callback function for the fulfilled case of a Promise, and the second argument is a callback function for the rejected case. It also means that Promises are thenable.

Here is a Promise example:

function promiseCall() {
  // two callbacks, resolve and reject
  const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.5) {
        resolve(1); // the case of resolve
      } else {
        reject(0); // the case of reject
      }
    }, 300); // with 300 milliseconds delay
  });

  // then method
  myPromise.then(value => console.log(`Resolved to ${value}`),
    error => console.log(`Rejected with value ${error}`) );
}

// call promiseCall 5 times
promiseCall();
promiseCall();
promiseCall();
promiseCall();
promiseCall();

Each execution returns a random result, and the following result comes from one of the executions:

Resolved to 1
Resolved to 1
Rejected with value 0
Resolved to 1
Rejected with value 0

A Promise instance’s Promise.prototype.catch() method schedules a function to be called when a promise is rejected. It is a shortcut for Promise.prototype.then(undefined, onRejected).

A Promise instance’s Promise.prototype.finally() method schedules a function to be called when a Promise is settled.

The then, catch, and finally methods return an equivalent Promise object that allows us to chain calls to other promise methods.

Here is the modified example that chains then, catch, and finally methods:

function promiseCall() {
  // two callbacks, resolve and reject
  const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.5) {
        resolve(1); // the case of resolve
      } else {
        reject(0); // the case of reject
      }
    }, 300); // with 300 milliseconds delay
  });

  // then, catch, and finally methods
  myPromise.then(value => console.log(`Resolved to ${value}`))
    .catch(error => console.log(`Rejected with value ${error}`))
    .finally(() => console.log('Done')); 
}

// call promiseCall 5 times
promiseCall();
promiseCall();
promiseCall();
promiseCall();
promiseCall();

The following result comes from one of the executions:

Resolved to 1
Done
Resolved to 1
Done
Resolved to 1
Done
Resolved to 1
Done
Rejected with value 0
Done

Promise async and await

The async keyword provides a cleaner style for Promise, avoiding the need to explicitly configure Promise chains. Adding async at the start of a function makes it an async function, where we can use the await keyword before a Promise call. The code waits until the Promise is settled, at which point the fulfilled value of the Promise is treated as a return value, or the rejected value is thrown.

Here is the previous example by using async and await.

async function promiseCall() {
  // two callbacks, resolve and reject
  const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.5) {
        resolve(1); // the case of resolve
      } else {
        reject(0); // the case of reject
      }
    }, 300); // with 300 milliseconds delay
  });

  try {
    // await result
    const value = await myPromise;
    console.log(`Resolved to ${value}`);
  } catch (error) { // catch error, including rejected call
    console.log(`Rejected with value ${error}`);
  } finally { // execute final calls
    console.log('Done');
  };
}

// call promiseCall 5 times
promiseCall();
promiseCall();
promiseCall();
promiseCall();
promiseCall();

The following result comes from one of the executions:

Rejected with value 0
Done
Rejected with value 0
Done
Rejected with value 0
Done
Resolved to 1
Done
Resolved to 1
Done

The Promise.resolve() static method resolves a given value to a Promise. The Promise.reject() static method returns a Promise object that is rejected with a given reason.

The async function* declaration defines an async generator function, which returns an AsyncGenerator object. Here is an example of the async generator function:

// an async generator function
async function* build() {
  // yield pauses the execution util the generator calls
  yield await Promise.resolve(1); 
  yield await Promise.resolve(2);
  yield await Promise.resolve(3);
  yield await Promise.resolve(4);
  yield await Promise.resolve(5);
}

// loop through the async generator function
for await (const value of build()) {
  console.log(value);
}

Here is the execution result:

1
2
3
4
5
undefined

Promise Concurrency

Promise has four static methods to facilitate async task concurrency:

  • Promise.all(): It fulfills when all of the promises fulfill, or rejects when any of the promises rejects.
Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)])
  .then(values => console.log(values)); // [1, 2, 3]

Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.reject(3)])
  .then(undefined, error => console.log(error)); // 3
  • Promise.any(): It fulfills when any of the promises fulfills, or rejects when all of the promises reject.
Promise.any([Promise.resolve(1), Promise.resolve(2), Promise.reject(3)])
  .then(values => console.log(values)); // 1

Promise.any([Promise.reject(1), Promise.reject(2), Promise.reject(3)])
  .then(undefined, error => console.log(error)); // AggregateError: All promises were rejected
  • Promise.allSettled(): It fulfills when all promises settle.
Promise.allSettled([Promise.resolve(1), Promise.resolve(2), Promise.reject(3)])
  .then(values => console.log(values)); 
// [
//   {
//     "status": "fulfilled",
//     "value": 1
//   },
//   {
//     "status": "fulfilled",
//     "value": 2
//   },
//   {
//     "status": "rejected",
//     "reason": 3
//   }
// ]

Promise.allSettled([Promise.reject(1), Promise.reject(2), Promise.reject(3)])
  .then(values => console.log(values));
// [
//   {
//     "status": "rejected",
//     "reason": 1
//   },
//   {
//     "status": "rejected",
//     "reason": 2
//   },
//   {
//     "status": "rejected",
//     "reason": 3
//   }
// ]
  • Promise.race(): It settles when any of the promises settles.
Promise.race([Promise.resolve(1), Promise.resolve(2), Promise.reject(3)])
  .then(values => console.log(values)); // 1

Promise.race([Promise.reject(1), Promise.resolve(2), Promise.reject(3)])
  .then(undefined, error => console.log(error)); // 1

Promise Usages

We have explained details on Promise definitions, Promise async and await, and Promise Concurrency. Let’s see some Promise Usages.

Calculate sum of all Promise values

Given a list of Promises that resolves to integers, calculate sum of all resolved values.

The following is the code of implementation:

function sumOfPromises (promises) {
  // sumarize all resolved values
  return promises.reduce(async(sum, promise) => await sum + await promise, 0);
};

// example of three resolved integers
sumOfPromises([
  Promise.resolve(1), 
  Promise.resolve(2), 
  Promise.resolve(3)
  ]).then(console.log); // 6

What if some Promises do not resolve to integers? We ignore them:

function sumOfPromises (promises) {
  return promises.reduce(async(sum, promise) => {
    const newValue = await promise;
    return Number.isInteger(newValue) ? await sum + newValue :  sum;
  }, 0);
};

// example of three resolved integers or strings
sumOfPromises([
  Promise.resolve(1), 
  Promise.resolve('a'), 
  Promise.resolve(3)
  ]).then(console.log); // 4

What if some Promises are rejected? We catch the error and ignore them:

function sumOfPromises (promises) {
  return promises.reduce(async(sum, promise) => {
    try {
      const newValue = await promise;
      // ignore non-integer value
      return Number.isInteger(newValue) ? await sum + newValue :  sum;
    } catch (e) {
      return sum; // do nothing for rejected promise
    }
  }, 0);
};

// example of one reject value and three resolved integers or strings
sumOfPromises([
  Promise.reject(1), 
  Promise.resolve('a'), 
  Promise.resolve(3),
  Promise.resolve(4)
  ]).then(console.log); // 7

Execute an asynchronous function with the time limit

Given an asynchronous function fn and a time t in milliseconds, return a new time limited version function that follows the following rules:

  • If the fn completes within the time limit of t milliseconds, the time limited function should resolve with the result.
  • If the execution of the fn exceeds the time limit, the time limited function should reject with the error, "Exceed time limit".

This can be accomplished by Promise.race(), which settles either when fn completes or the time limit is reached.

function timeLimitedFuntion(fn, t) {
  return async function(...args) {
    // race two promises: the function call and the time limit
    return Promise.race([fn(...args), new Promise((_, reject) => 
      setTimeout(() => reject('Exceed time limit'), t))]);
  }
};

// an example of fn with the execution timer and two values to be summerized
const fn = (t, a, b) => new Promise(async (resolve) => {
    await setTimeout(() => resolve(a + b), t);
});

// fn completes at 50, before the time limit, 100
timeLimitedFuntion(fn, 100)(50, 10, 20).then(console.log); // 30

// fn is assumed to complete at 150, but the time limit, 100, has been reached
timeLimitedFuntion(fn, 100)(150, 10, 20).catch(console.log); // Exceed time limit

Write an asynchronous sleep function

Write an asynchronous sleep function that waits for a specific time t milliseconds.

async function sleep(t) {
  // resolve the promise at the specific time t
  return new Promise((resolve) => setTimeout(resolve, t));
}

const t = Date.now(); // current time
await sleep(100); // sleep for 100
console.log(Date.now() - t); // elapsed time

await sleep(200); // sleep for 200
console.log(Date.now() - t); // elapsed time

The following result comes from one of the executions:

111
318

Build a Promise pool

A promisePool is the pool that executes n Promises simultaneously. Given an array of asynchronous functions functions and a pool limit n, build a Promise pool that returns a Promise that resolves when all the input functions resolve.

async function promisePool (functions, n) {
  // result of all resolved promise values
  const results = [];
  
  async function evaluateNext() {
    // all functions have been called
    if (functions.length === 0) {
      return;
    }
    
    // get the first function in the list
    const fn = functions.shift();

    // await for the fn result
    const result = await fn();

    // push the result into result
    results.push(result);

    // execute the next function
    await evaluateNext();
  }

  // start first n function calls
  await Promise.all(Array(n).fill().map(evaluateNext));

  // resolve all results
  return Promise.resolve([results]);
}

async function sleep(t) {
  // resolve the promise at the specific time t with the value t
  return new Promise((resolve) => setTimeout(() => resolve(t), t));
}

const t = Date.now(); // current time
// execute 3 promise calls with the pool size of 2
const results = await promisePool([() => sleep(100), () => sleep(200), () => sleep(110)], 2);

// display resolved results and the elapsed time
console.log(results, Date.now() - t);

The following result comes from one of the executions:

[100, 200, 110]
227

Conclusions

We have walked through details on Promise definitions, Promise async and await, Promise Concurrency, and Promise Usages. Hopefully, it helps you to get a deeper understanding of Promises.

Thanks for reading.

Want to Connect?

If you are interested, check out my directory of web development articles.

More content at PlainEnglish.io.

Sign up for our free weekly newsletter. Follow us on Twitter, LinkedIn, YouTube, and Discord.

Promises
Concurrency
JavaScript
Web Development
Programming
Recommended from ReadMedium