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

  1. Make the hook synchronous: record data (queue, counter) inside the hook and process it elsewhere.
  2. Replace await with fire-and-forget .then(...) on an external promise if async work is unavoidable.
  3. 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

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


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/585209ee9d273484. Report an issue: GitHub.