#Promises#Performance

Query Batching

Collapse many individual lookups made in the same tick into one request, then hand each caller its own result.

By Pratik RaiHard

Write createBatcher(fetchMany). It returns a function that takes a single id and returns a promise for that id's value.

Every id requested before the next microtask is collected and passed to fetchMany as one array. fetchMany(ids) resolves to an array of values in the same order, and each caller receives only its own.

Examples

Input:

JSfile.javascript
1const load = createBatcher(async (ids) => { 2 console.log('one request for', ids); 3 return ids.map((id) => 'user-' + id); 4}); 5 6await Promise.all([load(1), load(2), load(3)]);

Output:

one request for [1, 2, 3]
['user-1', 'user-2', 'user-3']

Three calls, one request.

Constraints

  • fetchMany resolves to an array the same length as its input

Notes

  • This is what DataLoader does, and how a GraphQL server avoids the N+1 problem.
  • Swap the queue for a fresh array before awaiting, or later calls join a batch that has already gone.

Goal: One request per tick, with every caller resolved from the batched response.

Source

Frequently asked questions

What is query batching?
Collecting several individual lookups made close together into a single request. Fifty rows each asking for their author become one request for fifty authors, and each caller still gets its own promise.
Why use a microtask rather than setTimeout?
`Promise.resolve().then(...)` runs after the current synchronous block but before any timer, so it catches everything requested in the same tick without adding measurable delay. A `setTimeout(0)` would work but costs at least a frame.
What is the N+1 problem?
One query to fetch a list, then one more per item to fetch its related data — fifty-one requests where two would do. Batching is the standard fix, and is what DataLoader implements for GraphQL servers.
Why swap the queue before awaiting?
Because calls made while the request is in flight must start a new batch. If they join the array that has already been sent, their promises never resolve.

Related Challenges

Continue learning with these related challenges

View All
JavaScript

Promise Pool

Run a list of async tasks with a hard limit on how many are in flight at once — the concurrency control Promise.all does not give you.

JavaScript · ES6Pratik Rai ·

JavaScript

Promise Time Limit

Wrap an async function so it gives up after a deadline — the building block behind every request timeout.

JavaScript · ES6Pratik Rai ·

JavaScript

Promisify a Callback Function

Convert a Node-style (error, value) callback API into one that returns a promise.

JavaScript · ES6Pratik Rai ·