JavaScript promises explained

JavaScript promises explained

Promises are one of the core features of JavaScript that allows us to write asynchronous code in a more synchronous way. Promises give us a way to handle asynchronous operations in a more linear fashion and make it easier to reason about our code. In this article, we'll take a look at what promises are, how they work, and how to use them in your own code.

What are Promises in JavaScript?

A promise is an object that represents the result of an asynchronous operation. Promises are used to handle asynchronous operations in a more synchronous way. When an asynchronous operation is started, a promise is created. The promise is then waiting for the asynchronous operation to finish. Once the asynchronous operation finishes, the promise is either resolved ( fulfilled) or rejected.

If the asynchronous operation finishes successfully, the promise is resolved with a value. This value is then passed to any registered onfulfilled callbacks. If the asynchronous operation fails, the promise is rejected with a reason. This reason is then passed to any registered onrejected callbacks.

Creating Promises

There are a few ways to create promises in JavaScript. The most common way is using the Promise constructor. The Promise constructor takes a function as an argument. This function is called the executor function. The executor function is responsible for starting the asynchronous operation and resolving or rejecting the promise.

The following is an example of using the Promise constructor to create a promise that resolves with a value:

const promise = new Promise((resolve, reject) => {
  // Start the asynchronous operation.
  resolve('Value');
});

The following is an example of using the Promise constructor to create a promise that rejects with a reason:

const promise = new Promise((resolve, reject) => {
  // Start the asynchronous operation.
  reject('Reason');
});

You can also create a promise that is already resolved or rejected by using the Promise.resolve() and Promise.reject() methods.

The following is an example of using the Promise.resolve() method to create a promise that is already resolved with a value:

const promise = Promise.resolve('Value');

We can use Promise.reject() method to create a promise that is already rejected with a reason:

const promise = Promise.reject('Reason');

Consuming Promises

Once a promise is created, you can consume it by using the then() and catch() methods. The then() method takes two arguments: onfulfilled and onrejected. The onfulfilled argument is a callback that is called if the promise is resolved. The onrejected argument is a callback that is called if the promise is rejected.

The following is an example of using the then() method to consume a promise:

promise.then((value) => {
  // Handle the success case.
}, (reason) => {
  // Handle the error case.
});

The catch() method is a shortcut for handling the error case of a promise. The catch() method takes an onrejected callback as its only argument. The following is equivalent to the previous example, but uses the catch() method:

promise.catch((reason) => {
  // Handle the error case.
});

Creating Chains of Promises

One of the strengths of promises is that they can be chained together. This allows you to create a chain of asynchronous operations where the output of one operation is passed as the input to the next operation.

The following is an example of chaining promises together:

const promise1 = Promise.resolve(1);
const promise2 = promise1.then((value) => {
  return value + 1;
});
const promise3 = promise2.then((value) => {
  return value + 1;
});

The above code creates a chain of three promises. The first promise is resolved with the value 1. The second promise is resolved with the value 2 (1 + 1). The third promise is resolved with the value 3 (2 + 1).

You can also create chains of promises that are rejected. The following is an example of a chain of promises that are rejected:

const promise1 = Promise.reject('Error');
const promise2 = promise1.catch((reason) => {
  return 'Recovered from error';
});
const promise3 = promise2.then((value) => {
  return value + 1;
});

The above code creates a chain of three promises. The first promise is rejected with the reason 'Error'. The second promise is resolved with the value 'Recovered from error' (the return value of the onrejected callback). The third promise is resolved with the value 1 ('Recovered from error' + 1).

Error Handling

It's important to note that catch() only handles errors that occur in the promise chain after the catch() method is called. If there is an error in the promise chain before the catch() method is called, the error will be unhandled.

The following is an example of an unhandled error:

const promise = Promise.reject('Error');

promise.then((value) => {
  // This code is never executed because the promise is rejected.
});

The above code creates a promise that is rejected with the reason 'Error'. The promise has a then() method, but the onfulfilled callback is never executed because the promise is rejected. The error is unhandled.

If you want to handle errors that occur anywhere in the promise chain, you can use the .catch() method on the promise returned by Promise.resolve().

The following is an example of handling errors that occur anywhere in the promise chain:

const promise = Promise.reject('Error');

primise.resolve()
  .then((value) => {
    // This code is never executed because the promise is rejected.
  })
  .catch((reason) => {
    // This code is executed because the promise is rejected.
  });

The above code creates a promise that is rejected with the reason 'Error'. The promise has a catch() method that is executed because the promise is rejected.

Async/Await

Async/await is a language feature that allows us to write asynchronous code in a more synchronous way. With async/await, we can write asynchronous code that looks and reads like synchronous code.

Async/await is built on top of promises. As such, async/await is only available in JavaScript environments that support promises.

In this article, we will take a brief look at it as it broad topic which we will tackle next time.

Creating Async Functions

An async function is a function that is prefixed with the async keyword. Async functions always return a promise. If the value returned from an async function is not a promise, it is implicitly wrapped in a promise.

The following is an example of an async function that returns a promise that is resolved with a value:

async function func() {
  return 'Value';
}

The following is an example of an async function that returns a promise that is rejected with a reason:

async function func() {
  throw 'Reason';
}

Consuming Async Functions

Async functions can be consumed using the await keyword. The await keyword can only be used inside an async function. When the await keyword is used, it waits for the promise to be resolved before moving on to the next line of code.

The following is an example of using the await keyword to consume an async function:

async function func() {
  const value = await promise;
  // The code in this line will only be executed after the promise is resolved.
}

Error Handling

Async functions use try/catch statements for error handling. The following is an example of an async function that uses a try/catch statement for error handling:

async function func() {
  try {
    const value = await promise;
  } catch (error) {
    // This code is executed if the promise is rejected.
  }
}

Conclusion

Promises are a core feature of JavaScript that allows us to write asynchronous code in a more synchronous way. Promises give us a way to handle asynchronous operations in a more linear fashion, and make it easier to reason about our code. In this article, we've taken a look at what promises are, how they work, and how to use them in your own code. If you want to develop web applications with JavaScript sooner or later you will need to add promises to your toolbox.