27. JavaScript Asynchronous

What you will learn

  • Synchronous and asynchronous code
  • Timers and callbacks
  • Promises
  • async and await
  • Running tasks together with Promise.all

One thing at a time

JavaScript runs one statement at a time. If it waited for slow jobs, such as loading data from a server, the whole page would freeze. Asynchronous code starts a slow job, carries on with other work, and comes back when the job is done.

Timers

setTimeout runs a function once after a delay in milliseconds. Notice the order of the output.

Example

<p id="demo"></p>

<script>
  let log = "1. Start";

  setTimeout(function () {
    log += "\n3. Timer finished (after 1 second)";
    document.getElementById("demo").textContent = log;
  }, 1000);

  log += "\n2. End of the script";
  document.getElementById("demo").textContent = log;
</script>

What you see (the third line appears after one second)

1. Start
2. End of the script
3. Timer finished (after 1 second)

The timer does not block the code below it. “End of the script” appears first. setInterval repeats a function, and clearTimeout or clearInterval stops a timer.

Callbacks

A callback is a function you give to something that will call it later. It works, but several steps in a row nest deeper and deeper, which is called “callback hell”:

Syntax

step1(function (a) {
  step2(a, function (b) {
    step3(b, function (c) {
      // hard to read and hard to handle errors
    });
  });
});

Promises and async/await solve this.

Promises

A promise stands for a result that will arrive later. It is in one of three states:

  • pending: still waiting
  • fulfilled: finished with a value
  • rejected: failed with an error

Example

<p id="demo">Working...</p>

<script>
  function wait(ms, value) {
    return new Promise(function (resolve) {
      setTimeout(function () { resolve(value); }, ms);
    });
  }

  wait(800, "The promise is fulfilled!")
    .then(function (result) {
      document.getElementById("demo").textContent = result;
    });
</script>

What you see (after about 0.8 seconds)

The promise is fulfilled!

then, catch, finally

Example

<p id="demo">Working...</p>

<script>
  function risky(shouldFail) {
    return new Promise(function (resolve, reject) {
      setTimeout(function () {
        if (shouldFail) { reject(new Error("Something went wrong")); }
        else { resolve("It worked"); }
      }, 500);
    });
  }

  risky(true)
    .then(function (message) {
      document.getElementById("demo").textContent = message;
    })
    .catch(function (error) {
      document.getElementById("demo").textContent = "Error: " + error.message;
    })
    .finally(function () {
      document.getElementById("demo").textContent += " (finished)";
    });
</script>

What you see (after about half a second)

Error: Something went wrong (finished)

.then() handles success, .catch() handles failure, and .finally() runs either way. Each returns a new promise, so you can chain them.

async and await

async / await lets you write promise code that reads like normal top-to-bottom code.

  • Put async before a function. It then always returns a promise.
  • Inside it, await pauses that function until a promise is ready. The rest of the page keeps running.
  • Use try...catch for errors.

Example

<p id="demo">Working...</p>

<script>
  function wait(ms, value) {
    return new Promise(function (resolve) {
      setTimeout(function () { resolve(value); }, ms);
    });
  }

  async function run() {
    const out = document.getElementById("demo");
    try {
      out.textContent = "Step 1...";
      const a = await wait(600, "one");
      out.textContent = "Step 2... got " + a;
      const b = await wait(600, "two");
      out.textContent = "Finished: " + a + " and " + b;
    } catch (error) {
      out.textContent = "Error: " + error.message;
    }
  }

  run();
</script>

What you see (it changes over about 1.2 seconds)

Finished: one and two

Running tasks together: Promise.all

If two jobs do not depend on each other, start them together. Waiting for each in turn takes the sum of the times, and Promise.all takes only the longest.

Example

<p id="demo">Working...</p>

<script>
  function wait(ms, value) {
    return new Promise(function (resolve) {
      setTimeout(function () { resolve(value); }, ms);
    });
  }

  async function run() {
    const start = Date.now();
    const results = await Promise.all([
      wait(1000, "A"),
      wait(1000, "B"),
      wait(1000, "C")
    ]);
    const seconds = Math.round((Date.now() - start) / 1000);

    document.getElementById("demo").textContent =
      "Got " + results.join(", ") + " in about " + seconds + " second (not 3).";
  }

  run();
</script>

What you see (after about 1 second)

Got A, B, C in about 1 second (not 3).

If any promise in Promise.all fails, the whole thing fails. Promise.allSettled waits for every one and reports each result.

Why the order surprises people

Code after an await or inside .then() runs later, after the current code finishes. Code written below a call does not wait for it unless you use await.

Common mistakes

  • Using await in a function that is not async.
  • Forgetting await and getting a promise object instead of its value.
  • No catch, so failures disappear silently.
  • Awaiting independent jobs one by one when Promise.all would be faster.
  • Expecting code below an async call to wait for it.

Exercise

Using the wait helper, write an async function that waits for Hello, then for World, and shows Hello World in the paragraph. Call the function.

Show solution
<p id="demo">Working...</p>

<script>
  function wait(ms, value) {
    return new Promise(function (resolve) {
      setTimeout(function () { resolve(value); }, ms);
    });
  }

  async function run() {
    const first = await wait(500, "Hello");
    const second = await wait(500, "World");
    document.getElementById("demo").textContent = first + " " + second;
  }

  run();
</script>

Knowledge check

1. What are the three states of a promise?

Pending, fulfilled, and rejected.

2. Where can you use await?

Inside an async function (and at the top level of a module).

3. Why use Promise.all?

To run independent tasks at the same time and wait for all of them.

Summary

  • Asynchronous code lets slow jobs run without freezing the page.
  • Promises represent future results, with then, catch, and finally.
  • async/await makes promise code easy to read. Handle errors with try...catch.
Scroll to Top