What the problem is
Ordinary code runs top to bottom. Asynchronous code doesn't.
console.log("one");
setTimeout(() => console.log("two"), 0);
console.log("three");
Prints: one, three, two. Even with a zero delay.
Understanding why is half the topic.
Why it happens
The browser has a single thread of execution: it does one thing at a time and can't wait. If a request to a server halted execution, the page would freeze for seconds.
So long operations work differently: you say "do this and call me when it's done", and execution continues. When the operation completes, your handler joins a queue and runs as soon as the thread is free.
Hence the rule: anything involving waiting joins a queue and runs later than the ordinary code that follows it.
The first approach: callbacks
Historically first: you pass a function to be called when ready.
The problem is well known: with several operations chained, nesting grows and the code becomes a staircase nobody can read.
The second approach: promises
A promise is an object representing a result you don't have yet. It has three states: pending, fulfilled, rejected.
fetch("/api/notes")
.then((res) => res.json())
.then((data) => console.log(data))
.catch((err) => console.error(err));
It reads top to bottom: made the request, then parsed the response, then used it; an error anywhere in the chain lands in the error handler.
The third approach: await
The modern syntax, with the same machinery behind it:
async function loadNotes() {
try {
const res = await fetch("/api/notes");
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
}
Here the code looks sequential again. The await keyword says: "stop here until there's a result, but don't block everything else".
Two rules:
- Await works only inside a function marked as asynchronous.
- Such a function always returns a promise, even when the body returns an ordinary value.
Common mistakes
Forgetting to await. The function was called but the result wasn't awaited — you get a promise instead of data.
Awaiting in a loop when the operations are independent. Five requests one after another instead of together. If order doesn't matter, start them together and await them all at once.
No error handling. A failed request without a catch produces a cryptic console error and leaves the interface hanging.
Awaiting inside an array iteration helper. A classic: ordinary iteration can't wait, and the code moves on regardless.
Checking that you understood
Answer this: why does "two" print last in the opening example, despite a zero delay? If you can explain it in words about the queue and the thread being freed, the topic is closed.
Then practise: build a page making three requests — two independent ones together and a third after them — with error handling and a loading state.
Хватит читать — пора делать
На CohortX можно найти команду под пет-проект и получить тот самый опыт, о котором спрашивают на собеседовании.
Похожие статьи
- TypeScript for beginners: why typesWhich problems types solve, the minimum to get started, and whether to take it on straight after JavaScript.
- TypeScript для новичка: зачем нужны типыКакие проблемы решают типы, минимум для начала работы, и стоит ли браться за это сразу после JavaScript.
- Асинхронность в JavaScript: понять раз и навсегдаПочему код выполняется не по порядку, что такое обещание и как читать современную запись без страха.