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
- Check the call shape: request(name, callback) or request(name, options, callback) — the callback is always last.
- Pass the function itself, not its result: `() => doWork()`, not `doWork()`.
- If the callback variable can be undefined, default it or assert it before calling request.
- 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
- Keep the callback as the last argument in every call site.
- Pass the function reference, not an invocation result.
- In TypeScript, type the overloads: (name, cb) or (name, options, cb).
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
- NotSupportedError
- Failed to execute 'observe' on 'PerformanceObserver': Cannot
- Failed to execute 'observe' on 'PerformanceObserver': Either
- Failed to execute 'observe' on 'PerformanceObserver': 'entry
- Options cannot be passed with "endMark"
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/05365eb4ab57e9c9.
Report an issue: GitHub.