Advanced

Asynchronous JavaScript

Handle async work with callbacks, promises, and async/await.

What you will see

before load loaded 42

About 2 min of reading

Quick check

Before this new lesson, one small question about the lesson you just finished. Recall it, do not look it up.

Try It

Defer work with setTimeout and promises, then read async/await like ordinary synchronous code. Order the output to see why the event loop is single-threaded but non-blocking.

1

Schedule a delayed callback and print a line before and after. The callback runs later, after the synchronous lines finish, proving the main thread never blocked.

console.log("sync 1"); setTimeout(() => console.log("timeout"), 0); console.log("sync 2");

Before you run it, say out loud what it will print. Then open the answer.

What it prints
sync 1 sync 2 timeout
setTimeout schedules a callback without pausing the main thread. JavaScript is single-threaded but non-blocking because of the event loop.
2

Chain a promise with then and print after it settles. The synchronous line still prints first because the awaited work is deferred to the next event-loop tick.

const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); delay(15).then(() => console.log("resolved after 15ms")); console.log("before");

Before you run it, say out loud what it will print. Then open the answer.

What it prints
before resolved after 15ms
`.then` handles success and `.catch` handles rejection. Avoid deep callback nesting by chaining promises or using await.
3

Wrap awaiting in an async function so the code reads top to bottom. Put it in try/catch so a rejected promise is handled instead of crashing.

async function load() { try { const value = await Promise.resolve(42); console.log("loaded", value); } catch (err) { console.log("load failed"); } } load(); console.log("before load");

Before you run it, say out loud what it will print. Then open the answer.

What it prints
before load loaded 42
An async function returns a promise; await pauses until it settles. Always wrap awaits in try/catch so failures do not become unhandled rejections.

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

Async work defers to the event loop; promises settle then await resumes the calling function.
1 Call async function -> returns a promise
↓
2 await suspends the function
↓
3 Event loop runs other work
↓
4 Promise settles -> await resumes with the value
Why this works
The event loop

JavaScript runs one task at a time on the main thread. Async work is delegated to the browser/Node, and callbacks are queued:

console.log("sync 1");
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("sync 2");
// Output: sync 1, sync 2, microtask, timeout

Microtasks (Promises) run before the next macrotask (setTimeout). This ordering matters when both are scheduled.

Error handling

Unhandled rejections crash the process. Catch errors at the edge:

// Option 1: await + try/catch
try { await doThing(); } catch (e) { handleError(e); }

// Option 2: .catch on a promise
doThing().catch(handleError);

// Option 3: allSettled to ignore individual failures
await Promise.allSettled([a, b, c]);

Promise.all rejects if any input rejects; Promise.allSettled waits for all and reports each outcome, which is safer for independent requests.

Examples

Callbacks and setTimeout

Deferring work

setTimeout(() => {
  console.log("after 1 second");
}, 1000);
console.log("first");   // runs before the callback

JavaScript is single-threaded but non-blocking. setTimeout schedules a callback without pausing the main thread. Callbacks are the oldest async pattern but can nest deeply.

Promises

Chaining async work

const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
delay(1000).then(() => {
  console.log("resolved after 1s");
}).catch(err => {
  console.error("failed", err);
});

A Promise represents a value that will exist later. .then handles success, .catch handles rejection. Promises avoid deep callback nesting.

Async/await

Async made readable

async function loadUser() {
  try {
    const res = await fetch("/api/user");
    const user = await res.json();
    console.log(user);
  } catch (err) {
    console.error("load failed", err);
  }
}

async functions return a Promise; await pauses until a Promise settles. This makes async code read like sync code while keeping the event loop free. Always wrap in try/catch.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Arrays and Objects
Previous Lesson
Arrays and Objects
Next Lesson
this and Closures
this and Closures →