Master the event loop, setTimeout, Promises, and async/await. Learn how JavaScript handles concurrency on a single thread and how to avoid callback hell.
Why: JavaScript is single-threaded. The event loop processes a call stack and a task queue. setTimeout schedules a callback after a delay; Promise.then runs as a microtask (before the next task).
console.log('1 — synchronous start');
// setTimeout pushes a callback to the task queue after ~0ms
setTimeout(() => {
console.log('4 — setTimeout callback (task queue)');
}, 0);
// Microtasks (Promise.then) run before the next task
Promise.resolve().then(() => {
console.log('3 — Promise.then (microtask queue)');
});
console.log('2 — synchronous end');
// Order: 1 → 2 → 3 (microtask) → 4 (task)
// setInterval — repeats until cleared
let ticks = 0;
const id = setInterval(() => {
ticks++;
console.log('tick', ticks);
if (ticks === 3) clearInterval(id);
}, 5);Why: Promises represent a future value and replace deeply nested callbacks. Chain .then() for sequential steps, .catch() for errors, and Promise.all() for parallel work.
function fetchUser(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id < 0) reject(new Error('Invalid id'));
else resolve({ id, name: `User ${id}` });
}, 10);
});
}
// Chain .then()
fetchUser(1)
.then(user => {
console.log('Got:', user.name);
return user.name.toUpperCase(); // value passed to next .then
})
.then(name => console.log('Uppercased:', name))
.catch(err => console.log('Error:', err.message));
// Promise.all — runs in parallel, resolves when all settle
Promise.all([fetchUser(1), fetchUser(2), fetchUser(3)])
.then(users => {
users.forEach(u => console.log(u.name));
});
// Promise.allSettled — resolves even if some reject
Promise.allSettled([fetchUser(1), fetchUser(-1)])
.then(results => {
results.forEach(r => console.log(r.status, r.value?.name ?? r.reason?.message));
});Why: async/await is syntactic sugar over promises. It lets you write async code that reads like synchronous code, with regular try/catch error handling.
function fetchUser(id) {
return new Promise(resolve =>
setTimeout(() => resolve({ id, name: `User ${id}` }), 10)
);
}
async function loadUsers() {
console.log('loading...');
// await pauses until the Promise settles
const user1 = await fetchUser(1);
console.log('got:', user1.name);
// Error handling with try/catch — same as sync code
try {
const user2 = await fetchUser(2);
console.log('got:', user2.name);
} catch (err) {
console.log('error:', err.message);
}
// Parallel — don't await sequentially if independent
const [a, b] = await Promise.all([fetchUser(3), fetchUser(4)]);
console.log('parallel:', a.name, b.name);
return 'done';
}
// async function always returns a Promise
loadUsers().then(r => console.log(r));