xSKILLby Infirals
Register

JavaScript for Beginners

0 of 24 notes read 0%

Reading progress is saved to your INFIRALS account. Sign in to keep this note marked as read.

Timers and callbacks

Async JavaScript14 min readLesson 1 of 4

1.1 Synchronous code runs to completion

JavaScript runs one thing at a time on its main thread. A script executes from top to bottom, and while it runs, nothing else in that thread runs. That model is simple and it is also why a long loop freezes a page: the browser cannot handle a click until the loop ends.

Asynchronous code breaks the work into a first part that runs now and a callback that runs later. The callback is a function registered for a future moment: after a timer, after a network response, or after a user event. The main thread continues with the next statement instead of waiting.

This module focuses on timers and promises. Every example prints values so the order is visible. Output order is the heart of asynchronous JavaScript, and the console is the best place to see it.

1.2 setTimeout

setTimeout(callback, delay) registers the callback to run after at least the given number of milliseconds. The delay is a minimum, not a promise. A busy main thread can delay the callback well past the requested time.

The function returns a timer id. Passing that id to clearTimeout cancels the callback if it has not run yet. Cancellation matters when the user navigates away or changes their mind before a delayed action happens.

javascript
console.log("start");

setTimeout(() => {
  console.log("timer");
}, 0);

console.log("end");

Output

start
end
timer

The output above is not a trick of the zero delay. The timer callback is placed in a queue, and the queue is processed after the current script finishes. Even with a delay of 0, the callback runs after the last synchronous line.

setInterval(callback, delay) repeats the callback every delay milliseconds until cleared. Pass its id to clearInterval to stop it. A repeating timer that is never cleared keeps running for the life of the page, and it can keep a removed component alive.

Both functions accept arguments after the delay, which are passed to the callback. That is convenient for tiny examples and easy to misuse for larger data; a closure over a variable is usually clearer.

javascript
let count = 0;

const timer = setInterval(() => {
  count += 1;
  console.log("tick " + count);
  if (count === 2) clearInterval(timer);
}, 10);

Output

tick 1
tick 2

1.3 Callbacks as a pattern

A callback is a function passed to another function to be called later. The timer functions use one, and so do array methods, event listeners and network helpers. The pattern separates "what to do" from "when to do it".

The main risk is losing control of the flow. Three timers that start together finish in delay order, which can be hard to read once callbacks call other callbacks. Nesting several callbacks to sequence steps is sometimes called callback hell, and promises exist to flatten that nesting.

Error handling with callbacks is manual. A callback receives its result through the argument the caller provides, and an error often arrives as a first error argument in Node-style callbacks. Promises standardise the two channels into resolved and rejected values.

1.4 The event loop

The event loop is the mechanism that keeps the main thread moving. Synchronous code runs in a call stack. Finished asynchronous work is queued, and when the stack is empty the loop takes the next task. Microtasks from promises run before the next timer task, which explains why a resolved promise logs before a zero-delay timer.

The detail matters for reading output questions. A microtask queue is drained completely after the current task, so a chain of promise callbacks can run before the timer callback that was scheduled first. A long synchronous block delays both queues.

javascript
console.log("1");

setTimeout(() => console.log("4 timer"), 0);

Promise.resolve().then(() => console.log("3 microtask"));

console.log("2");

Output

1
2
3 microtask
4 timer

1.5 Scheduling patterns

Two timer patterns appear in most interfaces. A debounce waits until the user stops typing before running a search, and a throttle runs an action at most once every few hundred milliseconds while a scroll continues. Both are built from setTimeout and a stored timer id.

A debounce clears the previous timer and sets a new one inside the event handler. The callback runs only when no new event arrived during the delay window. A throttle stores the time of the last run and skips events that arrive too soon, or schedules one trailing run.

Neither pattern needs a library for simple cases, and both depend on clearing timers correctly. Name the timer variable outside the handler so every event can clear the pending one, and clear the last timer when the component is removed.

1.6 Timers and cleanup

A timer is a reference that outlives the statement that created it. If a user opens a panel and closes it, a pending timer from the panel still runs its callback. The callback may read missing elements or update state that no longer exists.

Store timer ids on the component or in a module variable, and clear them in the cleanup path. For a page with several timers, a Set of ids or a single named list keeps the cleanup honest. In a framework, the cleanup runs in the effect teardown; in plain JavaScript, it runs in the function that closes or hides the component.

A long delay is not a guarantee that the page is still in the same state. The callback should check the current state before acting, even when the timer was cleared, because more than one event can schedule the same id variable.

1.7 Reading asynchronous output

Console output from async code is a timeline. The first line after calling an async function is synchronous, the next line inside the function appears after an await, and a timer may appear after a promise callback. Reading the source top to bottom is not enough.

A useful habit is to label the source of each line: sync, microtask, timer. When two lines share a source, their order follows registration order. When sources differ, the queue priority decides, and microtasks always drain before the next timer.

1.8 Common mistakes

  • Assuming a delay is exact and ordering timers by their requested time.
  • Forgetting to clear an interval, so it keeps running forever.
  • Building deep callback nests instead of a promise chain.
  • Losing an error inside a callback that never receives it.
  • Blocking the main thread with a long loop and blaming the timer for being late.

Exam

Ordering questions mix synchronous lines, timers and promise callbacks. Write three lists: stack, microtask queue, timer queue. Run the stack, drain microtasks, then take timers in schedule order. The answer usually surprises the intuition on the timer line.

  • Run the start-end-timer example and predict the order first.
  • Schedule three timers with different delays and log their order.
  • Start an interval and stop it after three ticks.
  • Explain the difference between the timer queue and the microtask queue.

Quiz

Check: timers and callbacks

Four questions on the single thread, setTimeout delays, cancellation and setInterval.

4 questions

Question 1 of 4

0/4 answered

The delay passed to setTimeout is:

Answer all you can, then submit.