Asynchronous¶
- Responsive User Interfaces is accomplished primarily in two ways:
- Designing for human perception and expectations
- Using asynchronous execution
- Display progress bars for operations more than 3-4s
- Use skeleton placeholders when loading takes more than 1s. Advantages:
- User adjusts to a layout they'll eventually see
- Loading process seems faster because there is an initial results
- Use periods of low load to pre-compute responses to high probability requests. Speeds up subsequent responses. (When the system isn’t busy, prepare likely next steps the user might take.
- Two main types of Asynchronous Execution
- Asynchronous Programming: Asynchronous programming is a coding approach where tasks are executed in a non-blocking way within a single thread, meaning the program can start a task, pause it while waiting for resources or input/output, and switch to other tasks without being idle. Unlike concurrent or parallel execution, it doesn’t run multiple tasks at the exact same time, but rather schedules them so work continues efficiently without unnecessary waiting. This allows programs, especially those involving network requests or file operations, to remain responsive and improve overall performance from the user’s perspective.
- Threading
- Asynchronous methods are functions that start a task but do not block the rest of the program while waiting for that task to finish.
- A Promise is a JavaScript object that represents a value that will be available now, later, or never (if there’s an error). It has three states:
- Pending → still waiting for the operation to finish.
- Fulfilled → operation completed successfully, value is ready.
- Rejected → operation failed, error is available.
-
In doFetch1, the Fetch API is used with promise chaining via .then() and .catch(). When fetch(url) is called, it immediately returns a pending promise, and the function attaches callbacks to be executed once the promise resolves. However, the rest of the function continues to run right away, so "📦 Fetch1 END" is logged before the network request finishes. The actual work of handling the response and parsing JSON happens later, asynchronously, in the .then() callbacks. This style is explicit about chaining promises but can become harder to read if you have many sequential asynchronous steps, leading to what’s sometimes called “callback hell” in more complex scenarios.
In doFetch2, the Fetch API is used with async/await, which is syntactic sugar over promises. Declaring the function as async allows the use of await, which pauses execution of that function until the promise resolves. This makes the code read in a top-down, synchronous style, even though it’s still non-blocking for the rest of the program. Error handling is done naturally with try/catch, and the log "📦 Fetch2 END" appears only after the network fetch and JSON parsing have finished. This style tends to be more readable and easier to reason about for sequential async operations.
Both approaches are valid and equally capable — they run with the same performance since async/await is just built on top of promises. However, async/await (doFetch2) is generally better for clarity and maintainability, especially when dealing with multiple asynchronous steps in sequence, while promise chaining (doFetch1) can be fine for simpler one-step async operations or when you want to run several promises in parallel using .then() and Promise.all().
-
In JavaScript, when an asynchronous operation like fetch finishes, its associated callback is placed into a queue — promises go into the microtask queue, while other async tasks like setTimeout use the task queue. This scheduling into the queue happens immediately when the async work completes, even if the JavaScript call stack is still busy. However, the event loop will only move that callback from the queue into the call stack when the stack is completely empty. This means that if long-running synchronous code is occupying the stack, the async callback will wait in the queue until the stack clears, which is why blocking code delays asynchronous execution.
- After the previous one is fulfilled, the next
.then()'s callback is put into queue, until the call stack it empty, for which the event loop then takes it from the microtask queue and pushes it onto the call stack to execute.- In other words: the chain advances one link at a time, and each link’s callback only goes into the microtask queue after the promise before it resolves.
- Multiple threads can run different instructions while sharing resources, which helps divide computation and reduce blocking. However, concurrency can introduce risks, such as two threads modifying the same variable at the same time. In browsers, multithreading is supported through worker threads, which come in three types: dedicated workers, shared workers, and service workers.