denoland/deno · error · TypeError
The "${name}" argument must be of type function. Received ${
Error message
The "${name}" argument must be of type function. Received ${typeof value} What it means
v8.promiseHooks.setOnInit/setBefore/setAfter/setResolve (and the combined alias) require plain synchronous functions. validatePlainFunction rejects non-functions and additionally rejects async functions and async generators (constructor.name AsyncFunction / AsyncGeneratorFunction), because hooks fire for every promise operation — awaiting inside a hook would recurse infinitely. The failure is a TypeError naming the argument.
Source
Thrown at ext/node/polyfills/v8.ts:681
// ---------------------------------------------------------------------------
type PromiseHookFn = (
promise: Promise<unknown>,
parent?: Promise<unknown>,
) => void;
function validatePlainFunction(value: unknown, name: string) {
// Reject non-functions as well as async functions and async generators -
// none of them can be used as promise hooks.
const ctorName = typeof value === "function"
? (value as { constructor?: { name?: string } }).constructor?.name
: undefined;
if (
typeof value !== "function" ||
ctorName === "AsyncFunction" ||
ctorName === "AsyncGeneratorFunction"
) {
throw new TypeError(
`The "${name}" argument must be of type function. Received ${typeof value}`,
);
}
}
// Track all registered hooks so we can rebuild the combined hooks
// when individual hooks are added/removed.
const initHooks: PromiseHookFn[] = [];
const beforeHooks: PromiseHookFn[] = [];
const afterHooks: PromiseHookFn[] = [];
const resolveHooks: PromiseHookFn[] = [];
// Re-entrancy guard: V8 promise hooks fire for ALL promise operations,
// including any promises created/resolved inside the hooks themselves.
let inPromiseHook = false;
// Register dispatchers once. core.setPromiseHooks is additive (no removal),
// so we install permanent dispatchers that check the current hook arrays.View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Make the hook synchronous: record data (queue, counter) inside the hook and process it elsewhere.
- Replace await with fire-and-forget .then(...) on an external promise if async work is unavoidable.
- Check the value is a function before registering when hooks come from config or plugins.
Example fix
// before
v8.promiseHooks.setOnInit(async (promise) => { await log(promise); }); // TypeError
// after
v8.promiseHooks.setOnInit((promise) => { logQueue.push(promise); }); // sync only Defensive patterns
Strategy: type-guard
Validate before calling
function assertSyncHook(fn, name) {
if (!isPlainFunction(fn)) throw new TypeError(`${name} must be a sync function`);
} Type guard
const isPlainFunction = (v) => typeof v === "function" && v.constructor?.name !== "AsyncFunction" && v.constructor?.name !== "AsyncGeneratorFunction";
Prevention
- Never await inside a promise hook; your own promise work re-enters the hook.
- Queue observations in hooks and drain them outside (microtask/setImmediate loop).
- Validate third-party hook plugins with the sync-function guard before registration.
When it happens
Trigger: v8.promiseHooks.onInit(async (p) => { ... }), passing undefined or a string, or wiring a hook variable that was never assigned a function value.
Common situations: Diagnostic/tracing hooks written with await inside; async_hooks-style tracers ported to promiseHooks without removing await; hooks supplied by plugins or configuration where the value arrives as something other than a function.
Related errors
- buffer must be a TypedArray or a DataView
- source must be a TypedArray or a DataView
- Expected the second argument to assertSnapshot() to be an op
- Snapshot serializer must return a string
- Cannot access pointer: expected 'ArrayBuffer', 'SharedArrayB
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/585209ee9d273484.
Report an issue: GitHub.