angular/angular · error · Error
Task is missing scheduleFn.
Error message
Task is missing scheduleFn.
What it means
When a task is scheduled through a zone delegate chain that does not handle scheduling itself, zone.js falls back to task.scheduleFn; for microTasks it drains the microtask queue instead. If the task has no scheduleFn and is not a microTask (i.e. macroTask or eventTask), there is no way to actually start it, so scheduleTask throws 'Task is missing scheduleFn.' This almost always means a raw Task object was passed to scheduleTask instead of being created via the helper APIs.
Source
Thrown at packages/zone.js/lib/zone-impl.ts:1313
let returnTask: ZoneTask<any> = task as ZoneTask<any>;
if (this._scheduleTaskZS) {
if (this._hasTaskZS) {
returnTask._zoneDelegates!.push(this._hasTaskDlgtOwner!);
}
returnTask = this._scheduleTaskZS.onScheduleTask!(
this._scheduleTaskDlgt!,
this._scheduleTaskCurrZone!,
targetZone,
task,
) as ZoneTask<any>;
if (!returnTask) returnTask = task as ZoneTask<any>;
} else {
if (task.scheduleFn) {
task.scheduleFn(task);
} else if (task.type == microTask) {
scheduleMicroTask(<MicroTask>task);
} else {
throw new Error('Task is missing scheduleFn.');
}
}
return returnTask;
}
invokeTask(targetZone: ZoneImpl, task: Task, applyThis: any, applyArgs?: any[]): any {
return this._invokeTaskZS
? this._invokeTaskZS.onInvokeTask!(
this._invokeTaskDlgt!,
this._invokeTaskCurrZone!,
targetZone,
task,
applyThis,
applyArgs,
)
: task.callback.apply(applyThis, applyArgs);
}
View on GitHub (pinned to 51cb07e980)
Solutions
- Use Zone.current.scheduleMacroTask(source, callback, data, scheduleFn, cancelFn) or scheduleEventTask(...) which wire scheduleFn correctly
- If building a Task by hand, supply the third-party scheduling step as scheduleFn: task.scheduleFn = (t) => { t.data!.handle = nativeSchedule(t.invoke) }
- For fire-and-forget microtasks, use Zone.current.scheduleMicroTask(source, callback, data) instead
Example fix
// before
const task = {type: 'macroTask', source: 'ipc', callback: onMsg} as any;
Zone.current.scheduleTask(task); // no scheduleFn -> throws
// after
const task = Zone.current.scheduleMacroTask('ipc', onMsg, null, (t) => {
t.data = {...(t.data||{}), handle: ipcPort.once('msg', t.invoke)};
}, (t) => ipcPort.removeListener('msg', t.invoke)); Defensive patterns
Strategy: validation
Validate before calling
// never hand-build tasks; use the helper that sets scheduleFn
const task = Zone.current.scheduleMacroTask(
'my-source', cb, null,
(t) => { t.data = {handle: nativeSchedule(t.invoke)}; },
(t) => nativeCancel(t.data!.handle),
); Type guard
const isSchedulableTask = (t: any): boolean => typeof t.scheduleFn === 'function' || t.type === 'microTask';
Prevention
- Use scheduleMacroTask/scheduleEventTask/scheduleMicroTask instead of constructing Task objects manually
- If a Task-like object is required, always assign scheduleFn and cancelFn before scheduleTask
When it happens
Trigger: Constructing a task manually (new (Zone as any).Task(...) or a hand-rolled object) and calling zone.scheduleTask(task) with scheduleFn unset; copying the Task shape from documentation but omitting the scheduleFn argument; calling scheduleEventTask/scheduleMacroTask with both customSchedule undefined while the delegate chain is empty — the normal helpers set scheduleFn, so hitting this usually means bypassing them.
Common situations: Library authors integrating custom async primitives (IPC, worker messages, custom timers) with zone.js; adapting old zone.js example code to newer typings; partially mocked Zone objects in tests where scheduleFn was stubbed away.
Related errors
- A task can only be run in the zone of creation! (Creation: $
- A task can only be cancelled in the zone of creation! (Creat
- More tasks executed then were scheduled.
- callback is not defined
- ${this.type} '${this.source}': can not transition to '${toSt
AI-assisted analysis of angular/angular@51cb07e980 (2026-08-22).
Data as JSON: /api/errors/1a50acc55d1334a2.
Report an issue: GitHub.