A Short Guide to JavaScript Promises

Promises are the modern alternative to callbacks for handling asynchronous code in JavaScript.

To create a promise, call the Promise object with the new operator before it, passing into it a function.

The return value is a promise:

const promise = new Promise((resolve, reject) => {
  // Code to be waited upon
});

console.log(promise); // Promise {<pending>}

The initial status of a promise is always pending.

When you call the resolve or reject parameter anywhere in the function you passed in when creating the promise (known as the ‘executor function’), this status changes to either fulfilled or rejected:

const promise = new Promise((resolve, reject) => {

  setTimeout(() => {

    resolve("success");

  }, 1000);

});

console.log(promise); // Promise {<pending>}

You cannot see this and get the value above because you need to wait for the outcome.

To do so, attach .then() and .catch() to the promise.

.then() runs if resolve is called and .catch() in the case of reject.

Each expects a function to be passed in that will make the value available as a parameter:

const promise = new Promise((resolve, reject) => {

  setTimeout(() => {

    resolve("success");

  }, 1000);

});

promise
.then(res => console.log(res)) // Logs "success" after one second
.catch(err => console.error(err))

If there is the possibility of an error, call reject when it should be triggered:

const imageLoad = new Promise((resolve, reject) => {
  img.onload = () => {
    resolve("Loaded");
  }
  img.onerror = () => {
    reject("Error");
  }
});

imageLoad
.then((res) => console.log(res)) // Runs if the image loads successfully
.catch((err) => console.error(err)) // Catch runs if it fails

A very commonly used promise-based function is fetch for making HTTP requests:

fetch('https://jsonplaceholder.typicode.com/posts') // Returns a promise
.then(res => res.json()) // res.json() also returns a promise!
.then(data => console.log(data)) 
.catch(err => console.log(err))

It is possible to handle one promise and then another by passing down a promise result in a .then() chain:

const promise = new Promise((resolve) => {
  resolve(1);
})

promise
.then(res => new Promise(resolve => resolve(res + 1)))
.then(res => res + 1)
.then(res => new Promise(resolve => resolve(res + 1)))
.then(res => console.log(res)) // 4

If you have more than one promise, you can wait for them using methods on the Promise object.

Promise.all() waits for promises passed into it in an array:

const getPhoto = fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json()).catch(err => "Fetch failed");

const getPosts = fetch('http://picsum.photos/400')
.then(res => res.blob()).catch(err => "Fetch failed");

const pageLoaded = new Promise(resolve => { 
  window.onload = () => resolve();
})

Promise.all([getPosts, getPhoto, pageLoaded])
.then(res => console.log(res)) // Result returns in array if successful
.catch(err => console.error(err)); // If one fails, catch runs immediately

To avoid .catch() being triggered for a single promise failing, you can use Promise.allSettled() instead:

Promise.allSettled([getPosts, getPhoto, pageLoaded])
.then(res => console.log(res))
.catch(err => console.error(err));

// Retuns array of objects after all complete, successful or not:
// [ 
//   { "status": "fulfilled", "value": "Fetch failed" },
//   { "status": "fulfilled", "value": "Fetch failed" },
//   { "status": "fulfilled" }
// ]

A modern way to handle promises is using async/await.

It uses .then() and .catch() under the hood but allows you to write more synchronous-looking code:

(async function() {
  try {
    const res = await fetch();
    const data = await res.json();
    console.log(data); 
  } catch(e) {
    console.error(err);
  }
}();