What you'll be able to do

By the end of this page you should be able to mark a function async, await a promise, wrap awaits in try/catch, and catch the classic bug: forgetting await so you log a Promise object instead of the value.

Previous post: promise chaining.

Who this is for

  • People who understand .then but want code that reads top-to-bottom
  • Beginners about to use fetch (next posts)
  • Anyone who saw Promise { <pending> } in the console and blinked

You can skip this if async/await is already muscle memory. Come back when a missing await ships to production.

async functions return promises

async function load() {
  return 42;
}
load().then(console.log); // 42

Scenario A — even sync-looking returns become promises. Callers can .then or await them.

Scenario B — throw inside async. Becomes a rejection — catch with .catch or try/catch when awaiting.

await pauses this function only

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

async function run() {
  console.log("start");
  await wait(500);
  console.log("after half second");
}
run();
console.log("run() was called — this logs before 'after'");

Scenario A — sequential steps inside run. Each await waits before the next line in that function.

Scenario B — the rest of the page. Other code still runs; await does not freeze the browser like a blocking sleep in some other languages.

try/catch with await

async function loadUser() {
  try {
    const value = await Promise.reject(new Error("network"));
    console.log(value);
  } catch (err) {
    console.error("failed:", err.message);
  }
}

Scenario A — replace .catch. Same idea as Blog #22 try/catch, now around async work.

Scenario B — rethrow. catch log and throw err if a parent should handle it.

Forgotten await (the #1 bug)

async function broken() {
  const data = wait(10); // missing await
  console.log(data); // Promise — not the resolved value
}

Scenario A — logging. You see Promise { <pending> } instead of the string/object.

Scenario B — using fields. data.name is undefined because data is still a Promise.

Fix: const data = await wait(10);

A tiny practice file

Save as async-await.html.

<!DOCTYPE html>
<html>
  <body>
    <button id="go">Run</button>
    <pre id="out"></pre>
    <script>
      const out = document.getElementById("out");
      function wait(ms) {
        return new Promise(function (resolve) {
          setTimeout(function () {
            resolve("waited " + ms + "ms");
          }, ms);
        });
      }
      document.getElementById("go").addEventListener("click", async function () {
        out.textContent = "working…\n";
        try {
          const a = await wait(400);
          out.textContent += a + "\n";
          const b = await wait(400);
          out.textContent += b + "\n done";
        } catch (err) {
          out.textContent += err.message;
        }
      });
    </script>
  </body>
</html>

Note: the click handler is async so we can await inside it.

Mistakes I see a lot

1. Forgetting await.

2. Using await outside async. Syntax error — wrap in async function or top-level await in modules (later/environment-specific).

3. Sequential awaits when work could be parallel. Fine for learning; Promise.all comes in #58 when you mean parallel.

4. Empty catch. Still log or surface the error (Blog #22 habit).

What to try before the next post

  1. Rewrite a three-step .then chain with async/await.
  2. Omit await once; inspect the console.
  3. Reject a promise and catch it with try/catch.
  4. Build async-await.html.

Next in this series: fetch API basics — real HTTP requests and JSON.

Try this next outside the series

Async code gets much easier when you see the same ideas on the browser side and the server side.