angular/angular · error · Error
flush failed after reaching the limit of ${limit} tasks. Doe
Error message
flush failed after reaching the limit of ${limit} tasks. Does your code use a polling timeout? What it means
In a fakeAsync test, flush() drains the fake scheduler queue, executing queued timer callbacks while advancing virtual time. flushNonPeriodic loops until only periodic/rAF tasks remain, but if each flushed task schedules another non-periodic timer (a polling loop such as recursive setTimeout), the queue never empties; after `limit` iterations (default 20) it aborts with this error, suggesting the polling-timeout cause. It is a runaway-loop detector, not a timer bug.
Source
Thrown at packages/zone.js/lib/zone-spec/fake-async-test.ts:332
if (this._schedulerQueue.length === 0) {
return 0;
}
// Find the last task currently queued in the scheduler queue and tick
// till that time.
const startTime = this._currentTickTime;
const lastTask = this._schedulerQueue[this._schedulerQueue.length - 1];
this.tick(lastTask.endTime - startTime, doTick);
return this._currentTickTime - startTime;
}
private flushNonPeriodic(limit: number, doTick?: (elapsed: number) => void): number {
const startTime = this._currentTickTime;
let lastCurrentTime = 0;
let count = 0;
while (this._schedulerQueue.length > 0) {
count++;
if (count > limit) {
throw new Error(
'flush failed after reaching the limit of ' +
limit +
' tasks. Does your code use a polling timeout?',
);
}
// flush only non-periodic timers.
// If the only remaining tasks are periodic(or requestAnimationFrame), finish flushing.
if (
this._schedulerQueue.filter((task) => !task.isPeriodic && !task.isRequestAnimationFrame)
.length === 0
) {
break;
}
const current = this._schedulerQueue.shift()!;
lastCurrentTime = this._currentTickTime;
this._currentTickTime = current.endTime;View on GitHub (pinned to 51cb07e980)
Solutions
- Flush with an explicit higher limit when the number of timer generations is known: flush(100)
- Use discardPeriodicTasks() after the assertions if the remaining work is a legit interval — but convert recursive setTimeout polling to setInterval so the scheduler classifies it periodic and flush stops on it
- Refactor the code under test to be bounded (max retries, takeUntil) so flush terminates
- Prefer tick(exactMs) with known elapsed time over flush() when the schedule is deterministic
Example fix
// before (component polls with recursive setTimeout)
refresh() { this.http.get('/status').subscribe(() => setTimeout(() => this.refresh(), 1000)); }
// test: flush(); // never drains -> limit of 20 tasks exceeded
// after (bounded flush in test)
flush(50); // known upper bound of poll generations
discardPeriodicTasks();
// or in component: use setInterval + clear on destroy so flush() treats it as periodic Defensive patterns
Strategy: validation
Validate before calling
// in the fakeAsync test const FLUSH_LIMIT = 50; // known upper bound of timer generations const elapsed = flush(FLUSH_LIMIT); discardPeriodicTasks(); // for legit remaining intervals
Try / catch
try {
flush();
} catch (e) {
if ((e as Error).message.includes('polling timeout')) {
discardPeriodicTasks(); // remaining work is periodic/polling
} else throw e;
} Prevention
- Use setInterval for polling so flush() treats it as periodic and stops on it
- Bound retries/polling in code under test
- Prefer tick(exactMs) when the schedule is deterministic; pass an explicit limit to flush()
When it happens
Trigger: Code under fakeAsync that polls: function poll() { ...; setTimeout(poll, 1000) } with setTimeout (each flush iteration enqueues the next); retry/backoff loops with setTimeout; watch-mode style checks inside components under test; calling flush() where tick() or a bounded flush was intended.
Common situations: Component tests where the component starts a status-polling interval on init; websocket-reconnect logic with recursive setTimeout exercised by flush(); services with exponential retry; tests migrating from jasmine done() to fakeAsync where unbounded awaits became timer loops.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- The code should be running in the fakeAsync zone to call thi
- Expected timers to have been patched.
- Cannot make XHRs from within a fake async test. Request URL:
- Expected one matching request for criteria "${description}",
- Expected one matching request for criteria "${description}",
AI-assisted analysis of angular/angular@51cb07e980 (2026-08-22).
Data as JSON: /api/errors/faadf0ac893bf407.
Report an issue: GitHub.