JavaScript
async/await
programming
asynchronous programming
coding misconceptions

Misunderstanding of async/await functions

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

async and await make asynchronous JavaScript easier to read, but they do not change how the runtime works. Most confusion comes from assuming that await blocks the whole program, or that marking a function async somehow makes slow work run in parallel automatically.

What async Actually Does

An async function always returns a promise, even when it looks like it returns a plain value.

javascript
1async function getNumber() {
2  return 42;
3}
4
5async function demo() {
6  const value = await getNumber();
7  console.log(value);
8}
9
10demo();

If you call getNumber() without await, you do not get 42. You get a Promise that will resolve to 42.

That is the first mental model to keep: async changes the function's return type.

What await Does and Does Not Pause

await pauses only the current async function until the awaited promise settles. It does not freeze the event loop, stop timers, or block unrelated requests.

javascript
1function delay(ms) {
2  return new Promise(resolve => setTimeout(resolve, ms));
3}
4
5async function task() {
6  console.log("before await");
7  await delay(1000);
8  console.log("after await");
9}
10
11console.log("start");
12task();
13console.log("end");

The output order is:

  • 'start'
  • 'before await'
  • 'end'
  • 'after await'

That ordering proves the whole program did not stop while task() was waiting.

Sequential and Parallel Work Are Different

Another common misunderstanding is thinking that multiple await calls run in parallel by default. They do not. If you await one promise before creating the next, the work is sequential.

javascript
1async function sequential() {
2  const a = await fetch("https://example.com/a");
3  const b = await fetch("https://example.com/b");
4  return [a.status, b.status];
5}

If the operations are independent, start both first and then wait for both:

javascript
1async function parallel() {
2  const pa = fetch("https://example.com/a");
3  const pb = fetch("https://example.com/b");
4
5  const [a, b] = await Promise.all([pa, pb]);
6  return [a.status, b.status];
7}

await improves readability. It does not create concurrency for you.

Error Handling Still Matters

Promise rejections do not disappear just because the syntax is cleaner. Use try and catch when an async function can fail.

javascript
1async function loadJson(url) {
2  try {
3    const res = await fetch(url);
4    if (!res.ok) {
5      throw new Error(`HTTP ${res.status}`);
6    }
7    return await res.json();
8  } catch (err) {
9    console.error("loadJson failed:", err.message);
10    throw err;
11  }
12}

A related mistake is forgetting to await the async call at the caller. If you forget, the rejection may escape the try block you expected to catch it.

await Does Not Fix CPU-Bound Work

async and await are mainly about waiting for I/O, such as network calls, database drivers, or file operations. A tight CPU loop still blocks the thread.

javascript
1async function heavyWork() {
2  let total = 0;
3  for (let i = 0; i < 1_000_000_000; i++) {
4    total += i;
5  }
6  return total;
7}

Even though the function is async, that loop still monopolizes the JavaScript thread until it finishes. For CPU-heavy work in Node.js, use worker threads or another process. In the browser, use Web Workers.

Loops Need Intentional Design

await inside a loop is not automatically wrong. It is wrong only when you needed concurrency and accidentally serialized everything.

Sequential version:

javascript
1for (const id of [1, 2, 3]) {
2  const user = await loadJson(`https://example.com/users/${id}`);
3  console.log(user.id);
4}

Concurrent version:

javascript
1const ids = [1, 2, 3];
2const users = await Promise.all(
3  ids.map(id => loadJson(`https://example.com/users/${id}`))
4);
5console.log(users.length);

Choose the version that matches the real requirement. Some APIs need order, rate limiting, or dependency between steps. In those cases sequential await is correct.

Common Pitfalls

  • Forgetting that every async function returns a promise.
  • Assuming await blocks the whole application instead of only the current async function.
  • Writing sequential await calls when the work could run concurrently.
  • Forgetting to await a promise and then treating it like the resolved value.
  • Expecting async syntax to make CPU-heavy code non-blocking.

Summary

  • 'async changes a function so it returns a promise.'
  • 'await pauses only the current async function, not the whole runtime.'
  • Independent operations should usually be started first and awaited together.
  • Rejections still need explicit error handling.
  • 'async and await help with asynchronous I/O, not CPU-bound computation.'

Course illustration
Course illustration

All Rights Reserved.