@react-dnd/asap
@react-dnd/asap is a tiny high-priority callback queue extracted into the React DnD monorepo. Calling asap(fn) postpones fn until after the current synchronous work, then drains queued tasks in first-in, first-out order before yielding to browser rendering or other I/O. It uses MutationObserver where available and a timer-plus-interval fallback elsewhere, and it catches task errors so later queued callbacks can continue before the error is rethrown on a lower-priority turn. This is scheduling infrastructure, not a React hook or drag-and-drop API.
Do not add @react-dnd/asap to a new application just to postpone work; use queueMicrotask, requestAnimationFrame, or a yielding scheduler according to the job. Keep it where an existing React DnD dependency or exact queue semantics make replacement riskier than its stale packaging and documentation.
Use it if
- You maintain code that already receives @react-dnd/asap transitively and need its exact queue ordering or error behavior
- You need a callback queue that drains newly added tasks in the same flush before yielding to rendering or I/O
- You target old browser-style environments where the package's MutationObserver and timer fallback is part of an established compatibility contract
- You are maintaining React DnD internals or another package built around this specific ASAP implementation
- You only need to defer a callback on current Node or browsers: the standard queueMicrotask function does that without installing a package
- You need fairness for rendering, input, networking, or I/O: the README explicitly says the queue drains until empty and can interfere with smooth animation or incoming connections
- You need cancellation, delays, priorities, concurrency limits, or returned handles: asap accepts a callback and returns nothing
- You need current package-specific documentation: the bundled README was copied from the older asap package and still describes require('asap'), asap/raw, Node 0.10, Browserify redirects, and ancient browser versions that @react-dnd/asap 5.0.2 does not export
- You require active standalone maintenance: npm 5.0.2 was published in April 2022 and the last commits touching this package directory were in January 2023
Setup reality
npm install @react-dnd/asap adds no runtime or peer dependencies and performs no native build, code generation, credential lookup, or configuration. Version 5.0.2 is an ES module package and exposes named exports from its root, so use import {asap} from '@react-dnd/asap'. The npm tarball does not publish a CommonJS export map, even though the monorepo's newer unpublished package.json contains separate require and import targets. That means documentation read from the current main branch can describe packaging that the installed 5.0.2 tarball does not have. The included README is older still: it says to require('asap') and documents asap/raw, but neither is an @react-dnd/asap 5.0.2 export. The public TypeScript declarations are the safer API reference. Scheduling has sharp edges. The queue drains completely, including callbacks added during a flush, so recursive scheduling can create an infinite event-loop turn without overflowing the stack. There is no cancel function, Promise result, delay, or backpressure. A thrown callback error is captured, queued callbacks continue, and the error is rethrown later by a timer; local try/catch inside the original caller cannot catch that asynchronous exception. In rendering code, use requestAnimationFrame when work must align with paint, and use a timer or another yielding scheduler when long work must allow input or I/O between chunks.
Patterns
Run a callback after synchronous workdefer-callback
import {asap} from '@react-dnd/asap';
console.log('before');
asap(() => console.log('deferred'));
console.log('after');
// before, after, deferredThe callback never runs inline, but it is scheduled ahead of rendering and most I/O once the current work returns.
Queue callbacks in first-in, first-out orderpreserve-fifo-order
import {asap} from '@react-dnd/asap';
asap(() => console.log('first'));
asap(() => console.log('second'));
asap(() => console.log('third'));A single shared module queue preserves insertion order and drains all three tasks in the same flush.
Append work while the queue is flushingenqueue-during-flush
asap(() => {
console.log('outer');
asap(() => console.log('nested'));
});
asap(() => console.log('sibling'));
// outer, sibling, nestedNew tasks join the current queue and run before the scheduler yields. Unbounded recursive enqueueing can starve the event loop indefinitely.
Make a cached callback consistently asynchronousavoid-zalgo-callback
function loadCached(key: string, done: (value: string) => void) {
const value = cache.get(key);
if (value !== undefined) {
asap(() => done(value));
return;
}
readValue(key, done);
}Deferring the cache-hit branch avoids an API that sometimes calls done before the caller returns and sometimes calls it later.
Handle expected failures inside the taskcapture-callback-error
asap(() => {
try {
updateIndex();
} catch (error) {
reportFailure(error);
}
});Uncaught task errors are rethrown on a later timer turn. Catch errors inside the callback when they are expected and recoverable.
Notify observers after a synchronous state changeschedule-state-notification
const listeners = new Set<() => void>();
function setState(next: State) {
state = next;
for (const listener of listeners)
asap(listener);
}All listeners run asynchronously in registration order. This queue does not batch duplicate callbacks or isolate a slow listener.
Build an independent ASAP queuecreate-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');
}));The root asap function uses a module-global queue. Constructing these exported classes isolates task order, but ties code to lower-level internals.
Wrap scheduling in a Promise for testswait-for-asap-task
function nextAsapTurn(): Promise<void> {
return new Promise(resolve => asap(resolve));
}
asap(() => updateModel());
await nextAsapTurn();
expect(model.updated).toBe(true);asap itself returns void. A Promise wrapper gives tests something to await, but failures thrown by another task are still rethrown separately.
Emit after subscribers can attachdefer-event-emission
import {EventEmitter} from 'node:events';
function createJob() {
const job = new EventEmitter();
asap(() => job.emit('ready'));
return job;
}
createJob().on('ready', () => console.log('ready'));Deferral lets the caller register its listener before emission. Use queueMicrotask directly in new code unless this package is already required.
Use a rendering scheduler for visual workyield-to-rendering
requestAnimationFrame(() => {
updateCanvas();
});This intentionally does not call asap. The package README warns that its fully drained high-priority queue can delay reflow, repaint, and input.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| queue-microtask | npm | Choose it when you need the standard queueMicrotask API plus a fallback for runtimes that do not provide it |
| asap | npm | Choose it only when maintaining software that explicitly depends on the original package and its documented raw subpath |
| p-immediate | npm | Choose it when Promise-based deferral to an immediate task is more useful than a callback queue that drains before I/O |