Execute Program

JavaScript Concurrency: Promise States

Welcome to the Promise States lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • So far, we've seen fulfilled and rejected promises.

  • >
    Promise.resolve('ok');
    Asynchronous Icon Async Result:
    {fulfilled: 'ok'}Pass Icon
  • >
    Promise.reject('oh no');
    Asynchronous Icon Async Result:
    {rejected: 'oh no'}Pass Icon
  • We also know that promises can take time to fulfill.

  • >
    new Promise(resolve => {
    console.log('setting timer');
    setTimeout(() => resolve(), 1000);
    }).then(() => {
    console.log('fulfilled');
    });
    Asynchronous Icon async console output
  • For the first 1000 or so milliseconds before it was fulfilled, the promise above was in a state we haven't discussed yet. That state is "pending". Promises are initially created in the pending state, and can then be fulfilled with a value or rejected with a reason.

  • If you create a pending promise directly in your browser's console, you'll see something like this:

  • >
    new Promise(resolve => { /* don't call resolve */ });
    Result:
  • Unlike fulfilled and rejected promises, Execute Program has no way to directly show a pending promise. (Also, this is the only lesson in the course where it would be useful!)

  • However, with a bit of instrumentation we can observe a pending promise indirectly. Here's an example that tracks a promise's state over time. It does this by using a state variable to record the state in the stateHistory array after every second.

  • >
    const stateHistory = [];
    let state = 'pending';
    let resolve;

    // Create a promise and store its resolve function.
    new Promise(r => { resolve = r; })
    .then(() => {
    state = 'fulfilled';
    });

    // Resolve the promise after 1.5 seconds
    setTimeout(resolve, 1500);

    // Check the promise's state once per second.
    setTimeout(() => { stateHistory.push(state); }, 0);
    setTimeout(() => { stateHistory.push(state); }, 1000);
    setTimeout(() => { stateHistory.push(state); }, 2000);

    stateHistory;
    Asynchronous Icon Async Result:
    ['pending', 'pending', 'fulfilled']Pass Icon
  • In an earlier lesson, we saw one reason Promise.resolve isn't called Promise.fulfill: it can also create rejected promises. The next example shows another example of this: we use Promise.resolve to create a promise that's pending for about a second, and then rejects. It's never fulfilled at any point!

  • >
    Promise.resolve(
    new Promise(resolve => setTimeout(resolve, 1000))
    .then(() => {
    throw new Error('it failed');
    })
    );
    Asynchronous Icon Async Result:
    {rejected: 'Error: it failed'}Pass Icon
  • Let's summarize what we've learned about promise states so far:

    1. A promise can be pending, fulfilled with a value, or rejected with a reason.
    2. Pending promises can become fulfilled or rejected.
    3. Once a promise is fulfilled or rejected, it stays in that state forever.
  • In real-world discussion of promises, it's common for people to say "resolved" when they mean "fulfilled". For example: "the promise resolved with the value 5."

  • But, "resolved" isn't actually a promise state. The resolve method creates a new promise from the value we pass in. When we pass it a non-promise argument into resolve, it creates a fulfilled promise. However, if we pass in a rejected promise, the new promise will also reject.

  • >
    const rejectedPromise = Promise.reject('rejection reason');
    Promise.resolve(rejectedPromise);
    Asynchronous Icon Async Result:
    {rejected: 'rejection reason'}Pass Icon
  • In this course, we've been careful to avoid saying "resolved" when we mean "fulfilled". Although this is a relatively harmless error once you understand the difference, mixing the two terms up can lead to confusion.