Why Does async/await Appear to Block Page Rendering? The Real Reason Explained
This article explains why using async/await inside a loop can make a page seem frozen, clarifies that await itself does not block the main thread, and shows how to replace serial awaits with Promise.all and other concurrency tools for truly non‑blocking UI updates.
async/awaitlets us write asynchronous code in a synchronous style, but a classic scenario often confuses developers: looping over a user list with async/await causes a long white screen until all requests finish, seemingly blocking page rendering.
Misconception Clarified: What Does await Actually Block?
async/await never blocks the JavaScript main thread; it is non‑blocking syntactic sugar.
When the JavaScript engine encounters the await keyword, it pauses the current async function and returns control to the main thread. The main thread remains free to handle other tasks, such as user input, other scripts, and—most importantly—page rendering. After the awaited Promise resolves, the event loop pushes the remaining async code back onto the task queue to resume execution.
The Real Culprit: Serial await in a Loop
// Simulated API request
function fetchUser(id) {
return new Promise(resolve => {
setTimeout(() => {
console.log(`Fetched user ${id}`);
resolve({ id, name: `User ${id}` });
}, 1000); // each request takes 1 s
});
}
// Wrong example: serial await in a for‑of loop
async function fetchAllUsers(userIds) {
console.time('Fetch All Users');
const users = [];
for (const id of userIds) {
// The loop pauses here, waiting for the previous request to finish
const user = await fetchUser(id);
users.push(user);
}
console.timeEnd('Fetch All Users');
// Assume UI update happens here
renderUsers(users);
return users;
}
const userIds = [1, 2, 3, 4, 5];
fetchAllUsers(userIds);
// Console output: Fetch All Users: ~5005 msThe problem is obvious: the five requests run serially , one after another, so total time equals the sum of each request (~5 seconds). The UI update renderUsers(users) only occurs after this long wait, creating the illusion of a blocked page.
Correct Approach: Parallel Execution with Promise.all
Since the requests are independent, they can be fired simultaneously. Promise.all accepts an array of Promises and returns a new Promise that resolves when all input Promises are fulfilled, yielding an array of results.
After refactoring, total time drops from 5 seconds to about 1 second, and the UI updates promptly.
Advanced Concurrency Tools
Promise.allis powerful but fails fast: a single rejection aborts the whole operation. If you need to wait for all Promises regardless of outcome, use Promise.allSettled, which returns each Promise’s status and value.
// fetchUser(3) may fail
// const promises = [fetchUser(1), fetchUser(2), fetchUserThatFails(3)];
// const results = await Promise.allSettled(promises);
/* results will be:
[
{ status: 'fulfilled', value: { id: 1, ... } },
{ status: 'fulfilled', value: { id: 2, ... } },
{ status: 'rejected', reason: 'Error: User not found' }
]
*/Other useful methods:
Promise.race : resolves or rejects with the first settled Promise—useful for scenarios like CDN speed tests.
Promise.any : resolves with the first fulfilled Promise; only rejects if all Promises fail.
Controlling Concurrency Limits
When dealing with many IDs (e.g., 1000), firing all requests at once can overwhelm the server and hit browser limits. A simple concurrency pool can limit the number of simultaneous requests.
async function limitedConcurrency(tasks, limit) {
const results = [];
const executing = [];
for (const task of tasks) {
const p = Promise.resolve().then(() => task());
results.push(p);
if (limit <= tasks.length) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}
// Usage example
const userIds = [1,2,3,4,5,6,7];
const tasks = userIds.map(id => () => fetchUser(id));
limitedConcurrency(tasks, 3).then(users => {
console.log('All users fetched with limited concurrency:', users);
});This function ensures that no more than limit requests are “in flight” at any time. In production, you can also rely on mature third‑party libraries for more elegant solutions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JavaScript
Provides JavaScript enthusiasts with tutorials and experience sharing on web front‑end technologies, including JavaScript, Node.js, Deno, Vue.js, React, Angular, HTML5, CSS3, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
