Skip to the content.

Asynchronous JavaScript - Promises, references

Promises in brief

promise
   .then(onFulfilled[, onRejected])
  [.catch(onExceptionOrError)];
promise
  .then(
    (value) => {
      // do something when the promise is fulfilled
    },
    (reason) => {
      // do something when the promise is rejected
      // i.e. throw new Error()
    }
  )
  .catch(error => {
      // handle the exceptions ... note:
      // - we can throw an error within the promise constructor in case we made it...
      // - we can throw an error within `onRejected` function...
      // - also an error could arise within the `onFulfilled` function...
  });

Promise terminology

Promises come with some quite specific terminology that it’s worth getting clear about.

First, a promise can be in one of three states:

Note that what “succeeded” or “failed” means here is up to the API in question: for example, fetch() considers a request successful if the server returned an error like 404 Not Found, but not if a network error prevented the request being sent.

Sometimes we use the term settled to cover both fulfilled and rejected.

A promise is resolved if it is settled, or if it has been “locked in” to follow the state of another promise.

The article Let’s talk about how to talk about promises gives a great explanation of the details of this terminology.

fetch() method in brief

// Do a simple GET request
const response = fetch(url);
// Do a POST request
const response = fetch(url, { method: 'POST', body: formData });

Note the response returned by fetch() is a promise so one of the things we can do is:

fetch(url)
   .then(response => response.text())
   .then(data => console.log(`Do something with ${data}`))
   .catch(error => console.error(`We have an error: ${error}`));
fetch(url)
   .then(response => {
        if (response.ok) {
            return response.json();
        } else {
            throw new Error(`Something went wrong: ${response.status}`)
        }
    )
   .then(data => console.log(`Do something with ${data}`))
   .catch(error => console.error(`We have an error: ${error}`));

Or we can handle it by await within async function:

async function handleFetchPromise() {
    try {
        const responsePromise = await fetch(url);
        
        if (responsePromise.ok) {
            const responseData = await responsePromise.json();
            console.log(`Do something with ${data}`);
        } else {
            throw new Error(`Something went wrong: ${responsePromise.status}`)
        }
    }
    catch (error) {
        console.error(`We have a problem: ${error}`)
    }
}
handleFetchPromise();

Nested fetch() requests example


const dom = {
    'button': document.querySelector('button'),
    'content': document.querySelector('.content')
}
const url1 = './data/sample1.txt';
const url2 = './data/sample2.txt';

function doSomethingWithData(data) {
    dom.content.innerHTML = data;
}
function doSomethingElseWithData(data) {
    dom.content.innerHTML += `<br>${data}`;
}

function processResponse(response) {
    if (response.status === 200) {
        return response.text()
    } else {
        throw new Error(`Error: ${response.status}`)
    }
}

dom.button.addEventListener('click', (event) => {
    fetch(url1)
        .then(response => processResponse(response))
        .then(text => {
            doSomethingWithData(text);
            return fetch(url2);
        })
        .then(response => processResponse(response))
        .then(text => doSomethingElseWithData(text))
        .catch(err => console.log(err));
});

Examples and exercises about Promise:

The Promise() constructor

The Promise() constructor takes a single function as an argument. We’ll call this function the executor. When you create a new promise you supply the implementation of the executor.

This executor function itself takes two arguments, which are both also functions, and which are conventionally called resolve and reject.

You can pass a single parameter of any type into resolve and reject.

About the executor, it’s important to understand the following:

When called via new, the Promise constructor returns a Promise object. The promise object will become “resolved” when either of the functions resolutionFunc or rejectionFunc are invoked. Note that if you call resolutionFunc or rejectionFunc and pass another Promise object as an argument, you can say that it is “resolved”, but still cannot be said to be “settled”.

Exercise: How to implement a Promise()-based API

Implementing an alarm() API (reference):

Sequencing animations by Promises (three solutions): README.md and Live preview of the end result.