denoland/deno · error · TypeError

expected a function

Error message

expected a function

What it means

Deno's core bootstrap re-wraps queueMicrotask so that exceptions raised inside the microtask are routed to the runtime's exception reporting instead of disappearing. Before enqueueing anything, the wrapper enforces the platform contract: the callback must be a function, otherwise it throws TypeError('expected a function'). This lives in libs/core/01_core.js and covers every queueMicrotask call routed through the core runtime.

Source

Thrown at libs/core/01_core.js:583

  // V8 skips experimental globals while snapshotting, so the native function is
  // only installed at runtime. In a from-scratch runtime it's already on the
  // global when this module runs (before our wrapper replaces it below), so we
  // can grab it directly. When restoring from a snapshot it isn't there yet, so
  // fall back to a lazy capture on first use: our wrapper shadows the native one
  // as an own property of the global, so momentarily remove our property to
  // reveal the native one underneath, then restore our wrapper.
  let nativeQueueMicrotask = window.queueMicrotask;
  function captureNativeQueueMicrotask() {
    const wrapper = window.queueMicrotask;
    delete window.queueMicrotask;
    nativeQueueMicrotask = window.queueMicrotask;
    window.queueMicrotask = wrapper;
    return nativeQueueMicrotask;
  }

  function queueMicrotask(cb) {
    if (typeof cb != "function") {
      throw new TypeError("expected a function");
    }
    const enqueue = nativeQueueMicrotask ?? captureNativeQueueMicrotask();
    return enqueue(() => {
      try {
        cb();
      } catch (error) {
        reportExceptionCallback(error);
      }
    });
  }

  // Some "extensions" rely on "BadResource", "Interrupted", "NotCapable"
  // errors in the JS code (eg. "deno_net") so they are provided in "Deno.core"
  // but later reexported on "Deno.errors"
  class BadResource extends Error {
    constructor(msg, options) {
      super(msg, options);
      this.name = "BadResource";

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Pass a function reference or wrap the call: `queueMicrotask(() => fn())`
  2. Default optional callbacks: `queueMicrotask(cb ?? (() => {}))`, or skip the call when cb is missing
  3. Guard with `typeof cb === 'function'` before enqueueing

Example fix

// before
queueMicrotask(flushQueue()); // passes flushQueue()'s undefined return value

// after
queueMicrotask(() => flushQueue());
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof cb !== 'function') {
  throw new TypeError('expected a function');
}
queueMicrotask(cb);

Type guard

const isFunction = (v) => typeof v === 'function';
// usage: isFunction(cb) ? queueMicrotask(cb) : fallback();

Prevention

When it happens

Trigger: Calling queueMicrotask with a non-function: `queueMicrotask(fn())` executes fn immediately and passes its (often undefined) return value; passing an optional callback that was never supplied; passing an object holding a callable field instead of the function itself.

Common situations: Immediately-invoked setup calls that return undefined; APIs shaped as `queueMicrotask(maybeCb)` where maybeCb is undefined; passing results of array operations that produced no function.

Related errors


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