Promises and the Microtask Queue

8. Promises and the Microtask Queue

Promises are a fundamental part of modern JavaScript, providing a powerful way to handle asynchronous operations. They represent a value that may be available now, later, or never, allowing developers to write cleaner and more manageable asynchronous code. Promises interact closely with the microtask queue, ensuring that promise resolutions are handled with high priority.

What is a Promise?

A promise is an object representing the eventual completion or failure of an asynchronous operation. Promises can be in one of three states:

  1. Pending: The initial state, meaning the operation has not yet completed.
  2. Fulfilled: The operation completed successfully, and the promise now holds a value.
  3. Rejected: The operation failed, and the promise holds a reason for the failure.

Once a promise is either fulfilled or rejected, it becomes settled and cannot change states. Promises are designed to be immutable once settled, ensuring predictable behavior in asynchronous code.

Creating a Promise

Promises are created using the Promise constructor, which takes a function (the executor) as an argument. This executor function receives two parameters, resolve and reject, which are used to settle the promise.

Example:

javascript
	const myPromise = new Promise((resolve, reject) => {
	  const success = true; // Simulating success or failure
	
	  if (success) {
	    resolve('Operation successful!');
	  } else {
	    reject('Operation failed!');
	  }
	});

Explanation:

  • In this example, a promise is created that either resolves with a success message or rejects with a failure message based on a condition.

Handling Promises with then, catch, and finally

Once a promise is created, you can handle its result using the then, catch, and finally methods:

  • then(onFulfilled, onRejected): Handles the promise’s fulfillment or rejection.
  • catch(onRejected): Handles the promise’s rejection (equivalent to then(null, onRejected)).
  • finally(onFinally): Executes a callback regardless of whether the promise was fulfilled or rejected.

Example:

javascript
	myPromise
	  .then((result) => {
	    console.log(result); // "Operation successful!"
	  })
	  .catch((error) => {
	    console.error(error);
	  })
	  .finally(() => {
	    console.log('Promise has been settled.');
	  });

Explanation:

  • The then method handles the resolved value, catch handles errors, and finally executes after the promise is settled, regardless of the outcome.

Promises and the Microtask Queue

When a promise is resolved or rejected, the corresponding handlers (attached via then, catch, or finally) are placed in the microtask queue. This ensures that they are executed immediately after the current synchronous code, but before any macrotasks from the callback queue.

Example of Promises in the Microtask Queue:

javascript
	console.log('Start');
	
	Promise.resolve('Promise resolved').then((result) => {
	  console.log(result);
	});
	
	console.log('End');

Explanation:

  • The promise is resolved immediately, but its then handler is placed in the microtask queue.
  • The synchronous code (“Start” and “End”) is executed first.
  • The promise’s then handler is executed afterward, even though the promise resolved almost instantly.
  • The output will be:
txt
	Start
	End
	Promise resolved

This demonstrates how promise callbacks are prioritized and handled promptly by the microtask queue.

Chaining Promises

Promises can be chained together to handle sequences of asynchronous operations, where the output of one promise is passed as input to the next.

Example:

javascript
	Promise.resolve(1)
	  .then((value) => {
	    console.log(value); // 1
	    return value + 1;
	  })
	  .then((value) => {
	    console.log(value); // 2
	    return value + 1;
	  })
	  .then((value) => {
	    console.log(value); // 3
	  });

Explanation:

  • Each then method returns a new promise, allowing chaining.
  • The result of one then is passed as an argument to the next.
  • This pattern allows for linear and readable asynchronous code.

Error Handling with Promises

Handling errors in promises is crucial for writing robust asynchronous code. Errors can be caught and managed using the catch method.

Example:

javascript
	Promise.resolve(1)
	  .then((value) => {
	    console.log(value); // 1
	    throw new Error('Something went wrong!');
	  })
	  .then((value) => {
	    console.log(value); // This won't execute
	  })
	  .catch((error) => {
	    console.error(error.message); // "Something went wrong!"
	  });

Explanation:

  • If an error is thrown in a then handler, it is caught by the next catch handler in the chain.
  • This error handling mechanism allows you to manage exceptions gracefully in asynchronous operations.

Using Promise.all and Promise.race

JavaScript provides utility methods like Promise.all and Promise.race for handling multiple promises concurrently:

  • Promise.all: Waits for all promises to settle and returns an array of their results. If any promise is rejected, Promise.all rejects with that error.
  • Promise.race: Returns the result of the first promise to settle, whether fulfilled or rejected.

Example of Promise.all:

javascript
	Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]).then((values) => {
	  console.log(values); // [1, 2, 3]
	});

Example of Promise.race:

javascript
	Promise.race([
	  new Promise((resolve) => setTimeout(() => resolve('First'), 1000)),
	  new Promise((resolve) => setTimeout(() => resolve('Second'), 500))
	]).then((value) => {
	  console.log(value); // "Second"
	});

Explanation:

  • Promise.all waits for all promises to resolve and then returns their results as an array.
  • Promise.race returns the result of the fastest promise.

Summary:

Promises are a powerful tool for managing asynchronous operations in JavaScript, and they interact closely with the microtask queue to ensure prompt execution of promise handlers. By understanding how promises work and how they are processed in the microtask queue, you can write more efficient and predictable asynchronous code.