Promises From The Ground Up

Building deep intuition for async JavaScript

June 3, 202422 min read8 views
javascriptpromisesasync

There are a lot of speed bumps and potholes on the road to JavaScript proficiency. One of the biggest and most daunting is Promises.

In order to understand Promises, we need a surprisingly deep understanding of how JavaScript works and what its limitations are. Without that context, Promises won't really make much sense.

Why This Matters

The Promises API is so important nowadays. It's become the de facto way of working with asynchronous code. Modern web APIs are built on top of Promises.

Why would they design it this way??

Suppose we wanted to build a Happy New Year! countdown. If JavaScript was like most other programming languages, we could solve the problem like this:

hypothetical.js
class="text-pink-400">function newYearsCountdown() {
  print(class="text-emerald-class="text-amber-400">400">"class="text-amber-400">3");
  sleep(class="text-amber-400">1000);

  print(class="text-emerald-class="text-amber-400">400">"class="text-amber-400">2");
  sleep(class="text-amber-400">1000);

  print(class="text-emerald-class="text-amber-400">400">"class="text-amber-400">1");
  sleep(class="text-amber-400">1000);

  print(class="text-emerald-class="text-amber-400">400">"Happy New Year! 🎉");
}

Unfortunately, there is no sleep function in JavaScript, because it's a single-threaded language. A "thread" is a long-running process that executes code. JavaScript only has one thread, and so it can only do one thing at a time.

Callbacks

The main tool in our toolbox for solving these sorts of problems is setTimeout. It accepts a chunk of work to do and the amount of time to wait for.

callback-hell.js
console.log(class="text-emerald-class="text-amber-400">400">"class="text-amber-400">3…");

setTimeout(() => {
  console.log(class="text-emerald-class="text-amber-400">400">"class="text-amber-400">2…");

  setTimeout(() => {
    console.log(class="text-emerald-class="text-amber-400">400">"class="text-amber-400">1…");

    setTimeout(() => {
      console.log(class="text-emerald-class="text-amber-400">400">"Happy New Year!!");
    }, class="text-amber-400">1000);
  }, class="text-amber-400">1000);
}, class="text-amber-400">1000);

Callback Hell

This pattern of nested callbacks was so common (and problematic) that we gave it a name: Callback Hell. Promises were developed to solve these problems.

Introducing Promises

Instead of nesting, what if we could chain them together? This is the core idea behind Promises. A Promise is a special construct, added to JavaScript in 2015.

promise-chain.js
class="text-pink-400">function wait(duration) {
  class="text-pink-400">return new Promise((resolve) => {
    setTimeout(resolve, duration);
  });
}

wait(class="text-amber-400">1000)
  .then(() => {
    console.log(class="text-emerald-class="text-amber-400">400">'class="text-amber-400">2');
    class="text-pink-400">return wait(class="text-amber-400">1000);
  })
  .then(() => {
    console.log(class="text-emerald-class="text-amber-400">400">'class="text-amber-400">1');
    class="text-pink-400">return wait(class="text-amber-400">1000);
  })
  .then(() => {
    console.log(class="text-emerald-class="text-amber-400">400">'Happy New Year!!');
  });

Async / Await

One of the really great parts of modern JavaScript is the async/await syntax. Using this syntax, we can get pretty darn close to our ideal countdown structure:

async-await.js
class="text-pink-400">async class="text-pink-400">function countdown() {
  console.log(class="text-emerald-class="text-amber-400">400">"class="text-amber-400">5…");
  class="text-pink-400">await wait(class="text-amber-400">1000);

  console.log(class="text-emerald-class="text-amber-400">400">"class="text-amber-400">4…");
  class="text-pink-400">await wait(class="text-amber-400">1000);

  console.log(class="text-emerald-class="text-amber-400">400">"class="text-amber-400">3…");
  class="text-pink-400">await wait(class="text-amber-400">1000);

  console.log(class="text-emerald-class="text-amber-400">400">"class="text-amber-400">2…");
  class="text-pink-400">await wait(class="text-amber-400">1000);

  console.log(class="text-emerald-class="text-amber-400">400">"class="text-amber-400">1…");
  class="text-pink-400">await wait(class="text-amber-400">1000);

  console.log(class="text-emerald-class="text-amber-400">400">"Happy New Year!");
}

The Magic of async/await

Promises give JavaScript the underlying infrastructure it needed to provide syntax that looks and feels synchronous, while actually being asynchronous under the hood. It's pretty friggin' great.

Share this post

61k
likes