JavaScript Promises

JavaScript Promises

A Promise in JavaScript is like a magical container that holds a value that may be available now, later, or never. Instead of waiting for something to finish immediately, Promises lets your code say “this will happen in the future,” and then respond when it does—by resolving with a result or rejecting with an error. Promises bring asynchronous actions into a clear, readable chain.

In practical terms, a Promise starts in a pending state, then moves to fulfilled if successful or rejected if there’s an issue. This article focuses on how to construct, use, and combine Promises in fun and illustrative examples—from potion brewing and treasure chests to fetching pretend data.

Creating a Simple Promise

Let’s begin by crafting a basic Promise using new Promise(). A mythical wand-polishing ritual unfolds:

<!DOCTYPE html>
<html>
<head>
  <title>Simple Promise</title>
</head>
<body>

<script>

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

    setTimeout(() => {

      const shine = true;

      if (shine) {
        resolve('🪄 Wand is polished!');
      } else {
        reject('💥 Polishing failed!');
      }

    }, 1000);

  });

  polishWand.then(message => console.log(message))
           .catch(err => console.log(err));

</script>

</body>
</html>

In this example, the Promise uses setTimeout to simulate a delay, then either resolves with a success message or rejects with a failure. The .then() handles the success path, and .catch() handles rejection.

Using .then() to Handle Fulfillment

When a Promise fulfills, .then() allows you to access its result. You can chain .then() blocks to process the value further.

<!DOCTYPE html>
<html>
<head>
  <title>Treasure Chest Chain</title>
</head>
<body>

<script>

  const openChest = new Promise(resolve => {
    setTimeout(() => resolve('🔓 Chest opened'), 500);
  });

  openChest.then(msg => {

    console.log(msg);
    return 'Collected treasure';

  }).then(collected => {
    console.log(collected);
  });

</script>

</body>
</html>

Here, opening the chest logs the first message, then passes a new value to the next .then() block. This chaining keeps things organized and story-like.

Using .catch() for Errors

When something goes wrong, .catch() lets you respond to errors and keep the flow under control. Here’s a dragon-taming example:

<!DOCTYPE html>
<html>
<head>
  <title>Dragon Taming</title>
</head>
<body>

<script>

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

    setTimeout(() => reject('🐲 Dragon flew away!'), 700);

  });

  tameDragon.then(msg => console.log(msg))
            .catch(err => console.log('Taming failed:', err));

</script>

</body>
</html>

When the dragon escapes, .catch() captures the rejection and logs the playful failure message.

Using .finally()

Whether the Promise resolves or rejects, .finally() always runs after. This is perfect for cleanup or concluding actions:

<!DOCTYPE html>
<html>
<head>
  <title>Summon Creature</title>
</head>
<body>

<script>

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

    setTimeout(() => resolve('Creature appears!'), 600);

  });

  summon.then(msg => console.log(msg))
        .catch(err => console.log(err))
        .finally(() => console.log('Summoning attempt concluded.'));

</script>

</body>
</html>

The final message always logs after the Promise completes—regardless of result.

Chaining Promises

You can chain multiple asynchronous steps in sequence, each represented by a Promise. Think of it as a quest sequence:

<!DOCTYPE html>
<html>
<head>
  <title>Wizard Quest</title>
</head>
<body>

<script>

  function step(msg, delay) {

    return new Promise(resolve => {
      setTimeout(() => resolve(msg), delay);
    });

  }

  step('Quest accepted', 300)
    .then(msg => {

      console.log(msg);
      return step('Gathering herbs', 500);

    }).then(msg => {

      console.log(msg);
      return step('Brewing potion', 400);

    }).then(msg => {

      console.log(msg);
      return step('Delivering potion', 200);

    }).then(msg => {
      console.log(msg);
    });

</script>

</body>
</html>

Each step returns a Promise that resolves the next part of the chain.

Returning Values from Promises

Values returned inside .then() propagate to the next .then() down the chain. This lets you transform and pass data onward.

<!DOCTYPE html>
<html>
<head>
  <title>Wizard Test</title>
</head>
<body>

<script>

  function testSpell(score) {

    return new Promise(resolve => {
      setTimeout(() => resolve(score), 300);
    });

  }

  testSpell(50)
    .then(score => {

      console.log('First test:', score);
      return score + 30;

    }).then(newScore => {

      console.log('Second test:', newScore);
      return newScore * 2;

    }).then(final => console.log('Final score:', final));

</script>

</body>
</html>

Each .then() transforms the result and passes it onward until you reach the final value.

Nesting vs. Chaining

Nesting Promises inside .then() can make code look nested and harder to follow. Chaining keeps each step flat and readable.

Here’s nesting:

<!DOCTYPE html>
<html>
<head>
  <title>Nesting Example</title>
</head>
<body>

<script>

  function step(msg, delay) {

    return new Promise(resolve => {
      setTimeout(() => resolve(msg), delay);
    });

  }

  step('Start', 200).then(msg => {

    console.log(msg);

    step('Continue', 200).then(msg2 => {
      console.log(msg2);
    });

  });

</script>

</body>
</html>

And chaining version (shown earlier) achieves the same flow without nesting. Both ways work—this is simply how-to view of each style.

Promises in Functions

You can write your own functions that return Promises for use elsewhere:

<!DOCTYPE html>
<html>
<head>
  <title>Spell Function</title>
</head>
<body>

<script>

  function castSpell(spellName) {

    return new Promise(resolve => {
      setTimeout(() => resolve(`Spell ${spellName} cast!`), 300);
    });

  }

  castSpell('Leviosa').then(msg => console.log(msg));

</script>

</body>
</html>

The function castSpell returns a Promise, which you then handle with .then().

Promise.all()

Promise.all() runs several Promises in parallel and returns a single Promise that resolves with all their results.

<!DOCTYPE html>
<html>
<head>
  <title>Ingredient Gathering</title>
</head>
<body>

<script>

  const quest1 = new Promise(res => setTimeout(() => res('Herbs'), 200));
  const quest2 = new Promise(res => setTimeout(() => res('Crystal'), 300));
  const quest3 = new Promise(res => setTimeout(() => res('Moon Water'), 100));

  Promise.all([quest1, quest2, quest3]).then(results => {
    console.log('Collected ingredients:', results.join(', '));
  });

</script>

</body>
</html>

This logs all gathered items together once all Promises complete.

Promise.race()

Promise.race() returns the first Promise that settles—useful when you want to react to whichever finishes first.

<!DOCTYPE html>
<html>
<head>
  <title>Wizard Race</title>
</head>
<body>

<script>

  const speedy = new Promise(res => setTimeout(() => res('Speedy wizard!'), 200));
  const slowpoke = new Promise(res => setTimeout(() => res('Slow wizard'), 500));

  Promise.race([speedy, slowpoke]).then(winner => {
    console.log('Winner:', winner);
  });

</script>

</body>
</html>

The first resolved value triggers the output.

Promises with DOM Interactions

You can use Promises to manage DOM changes after delays:

<!DOCTYPE html>
<html>
<head>
  <title>Reveal Enchanted Item</title>
</head>
<body>

<div id="container">🔒 Enchanted Chest</div>

<script>

  new Promise(res => setTimeout(() => res(), 1000))
    .then(() => {
      document.getElementById('container').textContent = '✨ Chest opened!';
    });

</script>

</body>
</html>

After the delay, the content changes—improving perception of timed stages.

Using Promises with fetch()

Modern APIs like fetch() return Promises. Here’s a pretend API call for a magical creature:

<!DOCTYPE html>
<html>
<head>
  <title>Fetch Creature</title>
</head>
<body>

<script>

  fetch('https://jsonplaceholder.typicode.com/users/1')
    .then(res => res.json())
    .then(data => {
      console.log('Creature:', data.name);
    })
    .catch(err => console.log('Failed to fetch creature:', err));

</script>

</body>
</html>

This shows handling of network requests using the Promise pattern.

Wrapping Callbacks with Promises

You can convert callback-based code to use Promises. Here’s how to wrap setTimeout:

<!DOCTYPE html>
<html>
<head>
  <title>Wait Spell</title>
</head>
<body>

<script>

  function wait(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  wait(800).then(() => console.log('Wait spell finished!'));

</script>

</body>
</html>

This turns a callback into a simple Promise-based wait.

Conclusion

You’ve now explored how to use JavaScript Promises: creating them, chaining with .then(), handling failures with .catch(), concluding with .finally(), running multiple with Promise.all() or race(), using them with fetch() and DOM changes, and even wrapping callbacks. Each example brings a playful narrative approach to async behavior, all with practical code that executes directly.

References

If you’d like to explore more, here are some trusted documentation pages with detailed explanations and examples:

Scroll to Top