denoland/deno · error · TypeError

callback must be a function

Error message

callback must be a function

What it means

LockManager.request(name, callback) or LockManager.request(name, options, callback) requires a function as the callback that receives the held Lock. Deno throws a plain TypeError from ext/web/locks.js when the resolved callback argument is not a function. Note the argument shapes: with two arguments the second is the callback; with three, the third is — mixing up options and callback produces this error.

Source

Thrown at ext/web/locks.js:114

    webidl.illegalConstructor();
  }

  async request(name, optionsOrCallback, callback = undefined) {
    webidl.assertBranded(this, LockManagerPrototype);

    const prefix = "Failed to execute 'request'";
    webidl.requiredArguments(arguments.length, 2, prefix);

    let options;
    if (arguments.length === 2) {
      options = {};
      callback = optionsOrCallback;
    } else {
      options = optionsOrCallback;
    }

    if (typeof callback !== "function") {
      throw new TypeError("callback must be a function");
    }

    options = webidl.converters.LockOptions(
      options,
      prefix,
      "Argument 2",
    );

    if (StringPrototypeStartsWith(name, "-")) {
      throw new DOMException(
        "'name' must not start with '-'",
        "NotSupportedError",
      );
    }
    if (options.steal && options.ifAvailable) {
      throw new DOMException(
        "'steal' and 'ifAvailable' are exclusive",
        "NotSupportedError",

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Check the call shape: request(name, callback) or request(name, options, callback) — the callback is always last.
  2. Pass the function itself, not its result: `() => doWork()`, not `doWork()`.
  3. If the callback variable can be undefined, default it or assert it before calling request.
  4. Ensure you are not forwarding options into the callback slot when building dynamic argument lists.

Example fix

// before
navigator.locks.request("res", { mode: "shared" }); // no callback

// after
navigator.locks.request("res", { mode: "shared" }, (lock) => doWork());
Defensive patterns

Strategy: type-guard

Validate before calling

function request(name, optionsOrCallback, maybeCallback) {
  const callback = maybeCallback ?? optionsOrCallback;
  if (typeof callback !== "function") {
    throw new TypeError("callback must be a function");
  }
  const options = maybeCallback ? optionsOrCallback : {};
  return navigator.locks.request(name, options, callback);
}

Type guard

function isLockCallback(v) {
  return typeof v === "function";
}

Try / catch

try { await navigator.locks.request(n, opts, cb); } catch (e) { if (e instanceof TypeError && e.message === "callback must be a function") throw new Error(`misuse of locks.request for ${n}`); throw e; }

Prevention

When it happens

Trigger: navigator.locks.request("r", { mode: "shared" }) — forgot the callback; request("r", callback, options) — arguments in the wrong order so an options object lands in the callback slot; passing an async arrow stored in a variable that is actually undefined.

Common situations: Translating between the two- and three-argument forms during refactors; passing a method reference that got lost (undefined); passing a promise-returning expression `doWork()` instead of the function `doWork`; typed wrappers that swap parameter order.

Related errors


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