@react-dnd/asap review
@react-dnd/asap 5.0.2 is the small callback scheduler used inside the React DnD monorepo. `asap(fn)` queues work after the current synchronous call and drains callbacks in insertion order before yielding to rendering or I/O. Browser pages use MutationObserver when present; Node, workers, and older environments fall back to a zero-delay timeout backed by a 50 ms interval. Task errors are captured so the queue keeps moving, then rethrown on a later timer. Version 5.0.2 changes packaging only: compared with 5.0.1, it removes `.swcrc` and `tsconfig.json` from the tarball while leaving runtime files unchanged.
@react-dnd/asap 5.0.2 installed as 1 dependency-free package and bundled to 0.8 KB gzipped in our sandbox, but it can drain an unbounded recursive queue before input, rendering, or I/O. Do not add it merely to defer work; keep it where React DnD or exact legacy ordering already requires it.
We installed it
| Install | ✓ · 1.5s | 1 package on disk · 1 MB |
| Import | ✓ | ESM import works · require() works · ESM package |
| Browser | 0.8 KB | gzipped (1.6 KB minified), bundled with esbuild |
| Types | ✓ | TypeScript types bundled |
| Known vulns | 0 | 0 critical · 0 high · 0 moderate · 0 low (npm audit) |
Answers from our run
Does @react-dnd/asap install cleanly?
Yes. In a fresh container with an empty cache, npm install @react-dnd/asap finished in 2 seconds, leaving 1 package and 1 MB on disk. npm audit reported no known vulnerabilities.
How much does @react-dnd/asap add to a browser bundle?
0.8 KB gzipped (1.6 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.
Does @react-dnd/asap work with both ESM and CommonJS?
Yes. Both import '@react-dnd/asap' and require('@react-dnd/asap') worked in Node 22 in our run. The package is published as ESM.
Does @react-dnd/asap include TypeScript types?
Yes, type declarations ship inside the package, so no @types install is needed.
@react-dnd/asap or queue-microtask: which should you use?
queue-microtask: Use it when you want the standard queueMicrotask call plus a fallback for runtimes that lack the global. @react-dnd/asap 5.0.2 installed as 1 dependency-free package and bundled to 0.8 KB gzipped in our sandbox, but it can drain an unbounded recursive queue before input, rendering, or I/O.
When should you not use @react-dnd/asap?
You only need a microtask in a current runtime. The built-in queueMicrotask() avoids another package and states the scheduling intent directly.
Use it if
- You maintain React DnD internals or another dependency whose ordering tests already assume this exact shared queue.
- Callbacks added during a flush must run in that same flush before the scheduler yields.
- A dependency needs the package's MutationObserver and timer fallback across its established environment matrix.
- You are auditing a transitive install and need to understand why long recursive task chains can block input or I/O.
- You only need a microtask in a current runtime. The built-in `queueMicrotask()` avoids another package and states the scheduling intent directly.
- Paint, input, network, or other I/O needs a chance between chunks. This queue keeps draining callbacks, including newly added ones, until empty.
- Cancellation, delays, priorities, concurrency caps, or a returned Promise are required. `asap()` accepts one callback and returns `void`.
- The README must match the installed API. Its copied instructions use `require('asap')` and `asap/raw`, but version 5.0.2 publishes named ESM exports under `@react-dnd/asap` and no raw subpath.
- Standalone package maintenance matters. npm 5.0.2 shipped in April 2022, and the last path-specific repository changes arrived in January 2023.
Setup reality
Our Node 22 sandbox installed @react-dnd/asap 5.0.2 in 1.5 seconds. The result was 1 package and 1 MB on disk; the package itself was 168 KB unpacked. npm audit found 0 known vulnerabilities. There are 0 direct dependencies and 0 peer dependencies. The MIT package is ESM, has no exports map, and bundles TypeScript declarations. Both require() and ESM import worked under Node 22 in our check. The full browser bundle measured 1.6 KB minified and 0.8 KB gzipped.
Use import {asap} from '@react-dnd/asap'; the bundled README's require('asap') example targets the original package, not this scoped build. Version 5.0.2 does not publish an asap/raw subpath. Its patch over 5.0.1 only removes 2 build configuration files from the npm tarball, so no scheduler behavior changed. There are no credentials, native builds, environment variables, or configuration files. The queue is module-global unless you construct the lower-level AsapQueue and TaskFactory classes yourself.
Scheduling priority is the catch. Once flushing starts, the loop runs until its queue is empty and includes callbacks enqueued by earlier callbacks. That preserves order but permits an infinite recursive chain to occupy one event-loop turn. The implementation periodically compacts after 1,024 completed tasks to limit retained array entries; this protects memory bookkeeping, not fairness. Use requestAnimationFrame() for visual work, a timer or scheduler for yielding chunks, and queueMicrotask() for ordinary current-runtime microtasks.
An uncaught callback error is handed to a pending-error list and thrown later through the timer fallback. A try/catch around the original asap() call cannot catch it because the callback runs after that stack has returned. Catch expected failures inside the callback or resolve/reject your own Promise. asap() offers no cancellation handle and no completion value. Tests that need to wait can wrap one scheduled callback in a Promise, while application code should avoid adopting this package unless exact inherited semantics matter.
Patterns
Run after the current stack defer-callback
import {asap} from '@react-dnd/asap';
console.log('before');
asap(() => console.log('queued'));
console.log('after');The output order is `before`, `after`, `queued`; version 5.0.2 never invokes the callback inline.
Keep callback insertion order preserve-fifo-order
asap(() => console.log('one'));
asap(() => console.log('two'));
asap(() => console.log('three'));One module-global queue runs these 3 callbacks in FIFO order during the same flush.
Append work from a running task enqueue-during-flush
asap(() => {
console.log('outer');
asap(() => console.log('nested'));
});
asap(() => console.log('sibling'));The order is `outer`, `sibling`, `nested`; the nested callback joins the active flush instead of waiting for a later turn.
Make cache hits asynchronous avoid-mixed-callback-timing
function loadValue(key, done) {
const cached = cache.get(key);
if (cached !== undefined) {
asap(() => done(null, cached));
return;
}
readValue(key, done);
}The cache-hit callback runs after `loadValue()` returns, matching an asynchronous miss path more closely.
Catch an expected callback failure handle-task-error
asap(() => {
try {
rebuildIndex();
} catch (error) {
reportFailure(error);
}
});Without the inner catch, version 5.0.2 queues the error and throws it on a later timer while continuing other tasks.
Give a test an awaitable boundary await-one-flush
function nextAsapTask() {
return new Promise((resolve) => asap(resolve));
}
asap(() => updateModel());
await nextAsapTask();
expect(model.updated).toBe(true);`asap()` returns `void`; the Promise wrapper waits for its own queued callback and does not capture errors from unrelated tasks.
Notify listeners after a state write notify-observers
function setState(next) {
state = next;
for (const listener of listeners) {
asap(listener);
}
}Listeners run in iteration order, but the queue does not deduplicate 2 registrations of the same function or isolate a slow listener.
Isolate lower-level task ordering create-private-queue
import {AsapQueue, TaskFactory} from '@react-dnd/asap';
const queue = new AsapQueue();
const factory = new TaskFactory(queue.registerPendingError);
queue.enqueueTask(factory.create(() => {
console.log('private queue');
}));This bypasses the module-global `asap` queue and couples the caller to 2 lower-level classes that the README does not teach.
Let callers attach an event listener emit-after-subscription
import {EventEmitter} from 'node:events';
function createJob() {
const job = new EventEmitter();
asap(() => job.emit('ready'));
return job;
}
createJob().on('ready', () => console.log('ready'));The deferred emission occurs after `createJob()` returns, giving the caller time to attach the `ready` listener.
Prefer the platform for new deferral use-native-microtask
queueMicrotask(() => {
notifySubscribers();
});Current Node and browsers provide `queueMicrotask()` without a package; it is the simpler choice when ASAP-specific fallbacks are unnecessary.
Align visual work with paint schedule-visual-update
requestAnimationFrame(() => {
drawSelectionOverlay();
});Visual updates should use the rendering clock; an ASAP flush runs before rendering and can postpone a frame.
Break CPU work into timer chunks yield-long-work
function runChunk() {
for (let i = 0; i < 100 && jobs.length; i += 1) {
processJob(jobs.shift());
}
if (jobs.length) setTimeout(runChunk, 0);
}
setTimeout(runChunk, 0);A timer yields between 100-job chunks; recursively calling `asap(runChunk)` would keep extending one high-priority flush.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| queue-microtask | npm | Use it when you want the standard queueMicrotask call plus a fallback for runtimes that lack the global. |
| scheduler | npm | Use it when work needs explicit priority levels and should cooperate with rendering rather than drain one FIFO immediately. |
| p-immediate | npm | Use it when Promise-based deferral to an immediate task fits better than a callback-only queue. |
| asap | npm | Keep it only in software that explicitly depends on the original package and its documented raw entry point. |
More utils guides
lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

