Skip to content
React Course1.0.0 beta
CSEN
React Course1.0.0 beta
CSEN
🏠 Home🧭 Where to start?🔁 Review📄 Cheat sheets📰 What's newℹ️ About the course
0. JavaScript0/22

JavaScript in general

  • 0.1 Variables, types and operators0/2
  • 0.2 Functions and closures0/2
  • 0.3 Objects0/2
  • 0.4 Arrays I: map, filter and friends0/2
  • 0.5 Arrays II: reduce, sorting and grouping0/3
  • 0.6 Conditions and logical operators0/2
  • 0.7 Asynchronous JavaScript0/2
  • 0.8 Modules and built-in tools0/2

JavaScript for React

  • 0.9 References and immutability0/3
  • 0.10 JS patterns you meet everywhere in React0/2

Basics

  • 1. A quick review of the basics0/3
  • 2. State and events (useState)0/3

Hooks in depth

  • 3. Effects (useEffect)0/3
  • 4. Refs (useRef)0/3
  • 5. More complex state (useReducer)0/3
  • 6. Context0/3
  • 7. Custom hooks0/3

Forms

  • 8. Forms and React 19 Actions0/3

Performance

  • 9. Performance and rendering0/3

Patterns

  • 10. Component design patterns0/3

Ecosystem

  • 11. Where the type goes (TypeScript syntax)0/3
  • 12. TypeScript with React0/3
  • 13. Routing (React Router)0/3
  • 14. Data fetching0/3
  • 15. Application state management0/3

Project

  • 16. Final project: Kanban0/2

Summary

  • 17. Summary: the principles of React

Your account

Privacy

Lesson 0.7 · JavaScript in general

Asynchronous JavaScript

setTimeout and the event loop, Promise, async/await, errors, Promise.all and fetch.

Loading lesson…

💬 Found a mistake, something unclear, or have an idea? Let me know.
← Conditions and logical operatorsModules and built-in tools →

Before we start: a waiter with order tickets

Picture a restaurant with a single waiter. If after every order they stood by the kitchen waiting for the cook to finish, the other guests wouldn't even get to order. So they do it the smart way: they take the order to the kitchen, leave the guest a ticket ("your food is coming") and go to serve the next table. When the cook rings the bell, they bring the food.

JavaScript in the browser is that waiter – it has only one thread. If it waited for the server's answer (which can easily take a second), the whole page would freeze: you couldn't click, type or scroll. So slow things aren't waited for, they're ordered – and the ticket for a future result is called a Promise.

ℹ️ What's in this lesson
  1. setTimeout and setInterval.
  2. The event loop: why code doesn't always run top to bottom.
  3. Promise: a ticket for a future result.
  4. async and await: asynchronous code that reads like normal code.
  5. Errors: try, catch and finally.
  6. One by one, or all at once: Promise.all and friends.
  7. fetch: how you really talk to a server.
  8. The most common mistakes.

1. setTimeout and setInterval

console.log('A')
setTimeout(() => console.log('B – after 1 second'), 1000)
console.log('C')
// Prints: A, C, … and a second later B
// setTimeout doesn't wait! It just "orders" a function call and the code carries on.
const id = setTimeout(() => alert('Boom'), 5000)
clearTimeout(id) // cancels the planned call
const tick = setInterval(() => console.log('tick'), 1000) // every second
clearInterval(tick) // stops it
⚠️ Clean up after yourself
An interval runs until you stop it – even when nobody needs it any more. That's why in React intervals and timeouts are cancelled in an effect's cleanup function (lesson 3). Without that, every time the component appeared another interval would be added.

2. The event loop: in what order code runs

The waiter has rules: first they finish what they're doing right now (synchronous code). Then they check the urgent tickets (microtasks – fulfilled Promises). Only then do they take the next ordinary task from the queue (setTimeout, a click, a server response).

console.log('1')
setTimeout(() => console.log('4'), 0) // even with zero, after everything else
Promise.resolve().then(() => console.log('3')) // a microtask – before the timeout
console.log('2')
// 1, 2, 3, 4
Demo

The order of output

What you see: Code with console.log, setTimeout and a Promise. The button runs it and shows the order the output appeared in.

Try it:

  1. Before you click, try to guess the order.
  2. Click Run. The synchronous output comes first, then the microtasks (Promise, queueMicrotask), and setTimeout with zero right at the end.

The takeaway: setTimeout(fn, 0) doesn't mean "right now" but "when there's time". Synchronous code always finishes completely before anything asynchronous gets its turn.

Loading the interactive part…

❓ What if synchronous code takes a long time?

Then everything else waits – clicks and rendering included. A loop over a million items or a complex calculation will "freeze" the page for a moment. That's why slow things (the network, files, timers) are done asynchronously, and the tools from the lesson on performance help with heavy calculations.

3. Promise: a ticket for a future result

A Promise is an object that stands in for a value that isn't available yet. It can be in one of three states:

StateMeaningThe restaurant ticket
pendingwe're waitingthe food is cooking
fulfilledfulfilled – we have the valuethe food is on the table
rejectedrejected – an error happenedthey ran out of ingredients
const promise = fetchUser(1) // returns a Promise right away, the data isn't there yet
console.log(promise) // Promise { <pending> }
// .then – "when it's ready, do this"
promise.then((user) => console.log(user.name))
// .catch – "if it goes wrong", .finally – "either way"
fetchUser(99)
.then((user) => console.log(user.name))
.catch((error) => console.error('It failed:', error.message))
.finally(() => console.log('Done (success or error)'))

Chaining then

fetchUser(1)
.then((user) => fetchPosts(user.id)) // returns another Promise…
.then((posts) => posts.items.length) // …and the next then gets its result
.then((count) => console.log(count))
.catch((error) => console.error(error)) // one catch for errors from the whole chain

Your own Promise

Usually you just receive Promises (from fetch, from libraries). But sometimes it's handy to make your own – typically for waiting:

function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms) // after ms it calls resolve → the Promise is fulfilled
})
}
sleep(1000).then(() => console.log('A second has passed'))
// A Promise that can fail
function rollDice() {
return new Promise((resolve, reject) => {
const n = Math.ceil(Math.random() * 6)
if (n === 1) reject(new Error('You rolled a one'))
else resolve(n)
})
}

4. async and await

.then chains work, but they're hard to read. async/await does the same thing and reads like ordinary code, top to bottom:

// With then
function showUserPosts(id) {
return fetchUser(id)
.then((user) => fetchPosts(user.id))
.then((posts) => console.log(posts.items))
}
// With async/await – the same
async function showUserPosts(id) {
const user = await fetchUser(id) // wait for the user
const posts = await fetchPosts(user.id) // then for the posts
console.log(posts.items)
}
  • await "unwraps" a Promise: it waits until it's fulfilled and returns its value. Only this function waits – the rest of the page keeps running.
  • await can only be used inside a function marked async (or at the top level of a module).
  • An async function always returns a Promise. Even if you write return 5, the caller gets a Promise with a five.
async function getNumber() {
return 5
}
getNumber() // Promise { 5 } – not 5!
const n = await getNumber() // 5
getNumber().then((n) => …) // or like this
// An async arrow function
const loadUser = async (id) => {
const user = await fetchUser(id)
return user.name
}

5. Errors: try, catch, finally

async function loadProfile(id) {
try {
const user = await fetchUser(id) // if this fails…
return user
} catch (error) {
console.error(error.message) // …it jumps here
return null
} finally {
console.log('Loading finished') // always runs (success or error)
}
}
✨ A typical pattern: loading, data, error
let isLoading = true
let error = null
let data = null
try {
data = await fetchUser(id)
} catch (e) {
error = e.message
} finally {
isLoading = false
}
You'll display exactly these three states (loading / have the data / error) in React all the time. In the lesson on data fetching you'll see how a library handles it for you.
❓ What happens to an error nobody catches?

The console shows "Uncaught (in promise) Error…" and the function waiting for the result never finishes. You won't see anything on the page – the user just waits for data that never comes. That's why every server call needs a plan B.

6. One by one, or all at once?

When you need several independent things, there's no point waiting for each one separately. A waiter doesn't take orders to the kitchen one at a time, waiting for each to be cooked, either.

// ❌ One by one: 4 × ~1 s = ~4 s
const users = []
for (const id of [1, 2, 3, 4]) {
users.push(await fetchUser(id))
}
// ✅ All at once: ~1 s (as long as the slowest one takes)
const users = await Promise.all([1, 2, 3, 4].map((id) => fetchUser(id)))
// ↑ map starts all 4 loads right away, all waits for all of them
const [user, posts] = await Promise.all([fetchUser(1), fetchPosts(1)]) // different things at once
Demo

One by one vs. in parallel

What you see: Two buttons that load four users from the fake API (each answers in 0.3–1.5 s). The bars show when each load started and finished.

Try it:

  1. Click one by one. The bars follow each other like stairs – every load waits for the previous one.
  2. Click Promise.all. All the bars start at the same time. The total time = the slowest user.
  3. Repeat it a few times – the times differ because of the random delay, but the parallel version always wins.

The takeaway: Start independent loads all at once with Promise.all. One by one only when the next step needs the result of the previous one.

Loading the interactive part…

MethodFulfilled when…Rejected when…
Promise.allall of them are fulfilled (results in order)any of them fails (right away)
Promise.allSettledall of them finish – successfully or notnever
Promise.racethe first one finishes successfullythe first one finishes with an error
Promise.anyany of them is fulfilledall of them fail
// allSettled – when you don't want one error to bring everything down
const results = await Promise.allSettled([fetchUser(1), fetchUser(99)])
// [{ status: 'fulfilled', value: {…} }, { status: 'rejected', reason: Error }]
const found = results.filter((r) => r.status === 'fulfilled').map((r) => r.value)
// race – a time limit
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Timed out')), 3000))
const user = await Promise.race([fetchUser(1), timeout])

7. fetch: a real server call

In the course we use a fake API (src/course/fakeApi.en.ts) so everything works offline. In a real app you talk to the server with the built-in fetch function:

async function loadUsers() {
const response = await fetch('https://api.example.com/users')
if (!response.ok) { // ⚠️ fetch does NOT fail on 404 or 500!
throw new Error(`The server returned ${response.status}`)
}
const users = await response.json() // the response body → a JS object (also a Promise)
return users
}
// Sending data
await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'Hello' }), // object → text
})
⚠️ fetch fails only when the network is down
When the server answers "404 Not Found" or "500 Server Error", from fetch's point of view that's a successful response. Always check response.ok (true for statuses 200–299).

Cancelling a request: AbortController

const controller = new AbortController()
fetch('/api/search?q=react', { signal: controller.signal })
.then((r) => r.json())
.catch((e) => {
if (e.name === 'AbortError') return // we cancelled it ourselves – not an error
throw e
})
controller.abort() // cancels the request in progress

It's handy for search, for example: when the user keeps typing, nobody cares about the old request any more. In React, this is how requests are cancelled in an effect's cleanup (lesson 3).

8. The most common mistakes

// ❌ A forgotten await – you have a Promise, not the data
const user = fetchUser(1)
user.name // undefined – a Promise has no name
// ❌ await in forEach doesn't wait – forEach knows nothing about Promises
ids.forEach(async (id) => {
await save(id)
})
console.log('Done') // prints RIGHT AWAY, the saving is still running
// ✅ for…of (one by one) or Promise.all (all at once)
for (const id of ids) await save(id)
await Promise.all(ids.map((id) => save(id)))
// ❌ map with async returns an array of Promises
const names = ids.map(async (id) => (await fetchUser(id)).name) // [Promise, Promise…]
// ✅ wrap it in Promise.all
const names = await Promise.all(ids.map(async (id) => (await fetchUser(id)).name))
// ❌ An error without catch
async function load() {
const data = await fetchUser(99) // throws an error and nobody catches it
}

Check yourself

Quiz In what order is this printed: console.log('A'); setTimeout(() => console.log('B'), 0); console.log('C')?
Quiz What does async function f() { return 5 } return when you call f()?
Quiz You need to load 5 independent users. What's the fastest?
Quiz The server answers 404. What does await fetch(url) do?

Warm-up

Challenge

Wait and answer

Warm-up

Two short async functions: one waits a moment, the other loads a user from the fake API.

Task (the same text is in the comment at the top of the file)
CHALLENGE: Wait and answer

1. getAnswer() is an async function: first wait 100 ms (await sleep(100)), then return 42.
2. loadCity(1) should load the user (await fetchUser(id)) and return their city – 'London'.

Loading the interactive part…

Challenge

Challenge

Loading data

Medium Bonus challenge

Six functions over the fake API: waiting, loading one and several users at once, handling an error and a time limit. The tests take a moment because the API answers with a delay.

🔒 Bonus challenge

Medium and hard challenges are a bonus for signed-in readers. Signing up is free – just an e-mail, no password.

How we handle your data

Summary

  • JavaScript has one thread. Slow things are "ordered" and the code keeps running meanwhile.
  • The order: first all the synchronous code, then fulfilled Promises (microtasks), then setTimeout and events.
  • A Promise is a ticket for a future result: pending → fulfilled, or rejected.
  • async/await reads Promises like normal code. An async function always returns a Promise.
  • Catch errors with try/catch; finally always runs.
  • Load independent things all at once with Promise.all. await in forEach doesn't work.
  • fetch doesn't fail on 404/500 – check response.ok. You can cancel with AbortController.
🧩 Where do the things not defined here come from (Checks, Check, fetchUser, sleep)
Checks, Check – from the file src/course/checks.tsx. The course helper for function-style challenges: runs the tests and shows ✅/❌. You don't need to know how it works inside – it just tells you which steps are done.
/** One test: a description and a function returning true (passed) or false. It may be async. */
export type Check = [name: string, test: () => boolean | Promise<boolean>]
/**
* Runs the tests of a challenge and shows ✅ or ❌ for each.
* An error thrown in a test (e.g. when trying to change a frozen object) is shown under the test.
*/
export function Checks({ tests }: { tests: Check[] }) {
const t = useT()
const [results, setResults] = useState<Result[]>(() => tests.map(() => ({ status: 'pending' })))
useEffect(() => {
let ignore = false
tests.forEach(([, test], i) => {
Promise.resolve()
.then(test)
.then(
(ok): Result => (ok ? { status: 'ok' } : { status: 'fail' }),
(e): Result => ({ status: 'fail', error: e instanceof Error ? e.message : String(e) }),
)
.then((result) => {
if (ignore) return
setResults((prev) => prev.map((r, idx) => (idx === i ? result : r)))
})
})
return () => {
ignore = true
}
}, [tests])
const passed = results.filter((r) => r.status === 'ok').length
return (
<div className="stack">
<strong className={passed === tests.length ? 'ok' : undefined}>
{t.checksPassed(passed, tests.length)}
</strong>
<ul className="list">
{tests.map(([name], i) => {
const r = results[i] ?? { status: 'pending' }
return (
<li key={name}>
{r.status === 'pending' ? '⏳' : r.status === 'ok' ? '✅' : '❌'} {name}
{r.status === 'fail' && r.error && (
<div className="bad muted">
{t.checksError} {r.error}
</div>
)}
</li>
)
})}
</ul>
</div>
)
}
fetchUser, sleep – from the file src/course/fakeApi.en.ts. The course fake API: data and functions pretending to be a server (with a delay, sometimes even with an error). In a real app you would call fetch() here.
export interface User {
id: number
name: string
email: string
city: string
}
export async function fetchUser(id: number): Promise<User> {
await sleep(randomDelay())
const user = users.find((u) => u.id === id)
if (!user) throw new Error(`User ${id} doesn't exist`)
return user
}
export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))