dldr: Batching and Caching Utility
dldr (pronounced "dataloader") is a minimalist JavaScript utility, currently at version 0.0.10, designed for efficiently batching and caching operations. It is particularly useful in data fetching scenarios, such as optimizing queries within GraphQL resolvers. The library distinguishes itself by its extremely small footprint (367B gzipped) and its use of `queueMicrotask` to schedule and execute batched load functions within the current event loop tick. This mechanism ensures that multiple requests for the same or different keys, made in quick succession within the same microtask queue, are consolidated into a single call to the underlying data fetching function. dldr offers both a basic batching mechanism and an extended version accessible via `dldr/cache` that incorporates an in-memory `Map`-based cache, preventing redundant data fetches for previously loaded keys. Its primary goal is to improve performance by reducing the number of requests to databases or APIs, positioning it as a lightweight alternative to more feature-rich dataloading solutions. While in early development, its API is straightforward, centered around `load` functions that accept an array of keys and return corresponding results.
Common errors
-
TypeError: loadFn is not a function
cause The first argument passed to `load` was not a function, or was null/undefined.fixEnsure the first argument to `load` (your data fetching function) is a valid, callable function that accepts an array of keys. -
TypeError: Cannot read properties of undefined (reading 'bind')
cause Attempting to use `load.bind` when `load` itself is not correctly imported or is undefined.fixVerify that `import { load } from 'dldr';` or `import { load } from 'dldr/cache';` is correctly specified and executed before calling `load.bind`.
Warnings
- gotcha dldr batches operations within the current microtask queue. Requests made across different event loop ticks (e.g., separated by `setTimeout` or `setImmediate` calls without an intervening microtask) will not be batched together, leading to multiple calls to your underlying `loadFn`.
- breaking As dldr is in an early development stage (version 0.0.10), its API surface may undergo changes without adhering to strict semantic versioning. Early minor or patch releases could potentially introduce breaking changes.
Install
-
npm install dldr -
yarn add dldr -
pnpm add dldr
Imports
- load
const load = require('dldr').load;import { load } from 'dldr'; - load
const load = require('dldr/cache').load;import { load } from 'dldr/cache';
Quickstart
import { load } from 'dldr';
// Mock a simple database interaction
const mockDb = {
posts: new Map([
['123', { id: '123', name: 'Post One' }],
['456', { id: '456', name: 'Post Two' }],
['789', { id: '789', name: 'Post Three' }]
]),
// Simulates a database call that takes an array of keys
execute: async (query: string, keys: string[]): Promise<Array<{ id: string, name: string }>> => {
console.log(`[DB] Executing query for keys: ${keys.join(', ')}`);
// Simulate network delay
await new Promise(resolve => setTimeout(resolve, 50));
return keys.map(key => mockDb.posts.get(key) || { id: key, name: 'Unknown Post' });
}
};
// Define the core data loading function that dldr will batch
const getPosts = async (keys: string[]): Promise<Array<{ id: string, name: string }>> => {
// In a real application, this would be a single database call
// that fetches multiple records based on the provided keys.
return mockDb.execute('SELECT id, name FROM posts WHERE id IN (?)', keys);
};
async function main() {
console.log('Demonstrating batching with dldr:\n');
// Request multiple posts concurrently. dldr will batch these into a single call to `getPosts`.
const post123Promise = load(getPosts, '123');
const post456Promise = load(getPosts, '456');
// Even if requested again, it's still part of the same batch operation if in the same tick.
const post123AgainPromise = load(getPosts, '123');
// Add another request later in the same event loop tick (e.g., from another resolver)
// This will still be part of the initial batch
await Promise.resolve(); // Ensures other microtasks run, still within the same tick conceptually
const post789Promise = load(getPosts, '789');
const loadedPosts = await Promise.all([
post123Promise,
post123AgainPromise,
post456Promise,
post789Promise
]);
console.log('\nResults from batched load:');
console.log(loadedPosts);
console.log('\nDemonstrating `load.bind` for convenience:');
const loadPost = load.bind(null, getPosts);
const boundPostPromise = loadPost('123');
const anotherBoundPostPromise = loadPost('456');
const boundLoadedPosts = await Promise.all([boundPostPromise, anotherBoundPostPromise]);
console.log(boundLoadedPosts);
}
main().catch(console.error);