Skip to content

createDeduper()

Amphiluke edited this page Dec 1, 2025 · 4 revisions

Synopsis

The createDeduper() API is somewhat similar to createCacher() in that it guards a user-defined async function by producing a wrapper async function, a “deduper”. The difference is that the deduper protects the original async function from repeated invocations just while it is pending. Every repeated call gets the same pending promise produced by the first call in the active queue. As soon as the currently pending promise settles, the wrapper allows for a new call of the original function.

This technique is useful in cases where several independent parties may simultaneously access the same asynchronous API. Without appropriate measures, this may lead to request duplication. A deduper prevents this situation by allowing multiple callers to share the same pending promise.

Usage example

import {createDeduper} from 'async-aid';

// We expect multiple parties to request the list of users at the same time
const getUsers = createDeduper(async () => {
  console.log('Fetching…');
  const response = await fetch('/user-api/users');
  return await response.json();
});

// Now we’ve avoided the situation of duplicated parallel requests.
// Three calls below result in a single fetch request
const [userList1, userList2, userList3] = await Promise.all([
  getUsers(), // logs 'Fetching…' and sends a request
  getUsers(), // logs nothing and doesn’t send a new request
  getUsers(), // logs nothing and doesn’t send a new request
]);

console.assert(userList1 === userList2); // OK
console.assert(userList2 === userList3); // OK

// Here, no pending requests exist, so this initiates a new request
const userList4 = await getUsers(); // logs 'Fetching…'

Key-based deduplication

If you want your deduper to perform deduplication selectively based on the arguments it is passed, you’ll need to provide it with a key function. For example, we can enhance our fictional User API by allowing one to query a list of users with a specific role. The deduper will use the provided key function to differentiate logically independent async processes.

import {createDeduper} from 'async-aid';

const getUsersWithRole = createDeduper(async (role) => {
  console.log(`Fetching: ${role}…`);
  const response = await fetch(`/user-api/users?role=${role}`);
  return await response.json();
}, {
  // Use role name as a distinct key
  keyFn: (role) => role,
});

const [testers, qa, developers, programmers] = await Promise.all([
  getUsersWithRole('tester'), // logs 'Fetching: tester…' and sends a request
  getUsersWithRole('tester'), // logs nothing and doesn’t send a new request
  getUsersWithRole('developer'), // logs 'Fetching: developer…' and sends a request
  getUsersWithRole('developer'), // logs nothing and doesn’t send a new request
]);

console.assert(testers === qa); // OK
console.assert(developers === programmers); // OK
console.assert(testers !== developers); // OK

Clone this wiki locally