Parallel Requests

Parallel Requests and Request Deduplication

A book page needs the book, its author and its reviews. Written the obvious way each await waits for the one before it and the page costs the sum of all three. Only the author request depends on the book, so start the independent ones first: a fetch begins when it is called, not when it is awaited, so holding the promise is enough.

Sequential and parallel versions of the same pageJavaScript
const book = await getBook(id);                  // app/seq/[id]/page.js
const author = await getAuthor(book.authorId);   // needs book.authorId
const reviews = await getReviews(id);            // needs nothing
// --------------------------------------------- // app/par/[id]/page.js
const bookP = getBook(id), reviewsP = getReviews(id);   // both in flight already
const book = await bookP;
const [author, reviews] = await Promise.all([getAuthor(book.authorId), reviewsP]);

Each endpoint sleeps 300 ms, so the difference is arithmetic you can watch — three renders of each, timed inside the component and by the dev server:

Output of 27
[seq]      973 ms      GET /seq/bk_3 200 in 1841ms     (first render, compiling)
[parallel] 655 ms      GET /par/bk_3 200 in 1362ms
[seq]      969 ms      GET /seq/bk_3 200 in 1009ms
[parallel] 642 ms      GET /par/bk_3 200 in 676ms
[seq]      958 ms      GET /seq/bk_3 200 in 988ms
[parallel] 641 ms      GET /par/bk_3 200 in 673ms

Three round trips become two and the page gets a third faster. It does not become one: the author is still gated on the book. That residual waterfall is the limit of Promise.all, and the only fix is a server that answers both at once — an ?include=author parameter, or the populate of References and populate.

The same three requests, awaited in series and started together
The same three requests, awaited in series and started together

One request, many components

Prop-drilling exists because fetching in two places used to mean fetching twice. Here it does not: four components on one page, two asking for the same book list and two for the same stats, produced exactly two lines in the Express 24,430 API's own log, a millisecond apart. React 7,897 memoizes fetch calls with the same URL and options for one render pass, including cache: "no-store" ones — per request, not a cache, so nothing survives to the next visitor. Fetch in the component that needs the value. Memoization keys on the fetch call, leaving a Mongoose 243,355 query or a Redis 2,763 GET uncovered; wrap those in cache() from react. Used one step earlier the idea removes a waterfall: call a loader without await — void getItem(id) — before the blocking work, and the memoized call inside the component resolves instantly. Next.js 10,514 calls this preloading.