petkaantonov/bluebird · error · TypeError

onCancel must be a function, got: %s

Error message

onCancel must be a function, got: %s

What it means

When an executor uses Bluebird's third executor argument (the onCancel registration callback), the value passed must be a function. cancellationExecute validates typeof onCancel and throws a TypeError('onCancel must be a function, got: %s') otherwise. The onCancel callback is what Bluebird invokes to roll back work when the promise is cancelled, so a non-function is always a caller bug.

Source

Thrown at src/debuggability.js:414

Promise.prototype._setOnCancel = function (handler) { USE(handler); };
Promise.prototype._attachCancellationCallback = function(onCancel) {
    USE(onCancel);
};
Promise.prototype._captureStackTrace = function () {};
Promise.prototype._attachExtraTrace = function () {};
Promise.prototype._dereferenceTrace = function () {};
Promise.prototype._clearCancellationData = function() {};
Promise.prototype._propagateFrom = function (parent, flags) {
    USE(parent);
    USE(flags);
};

function cancellationExecute(executor, resolve, reject) {
    var promise = this;
    try {
        executor(resolve, reject, function(onCancel) {
            if (typeof onCancel !== "function") {
                throw new TypeError("onCancel must be a function, got: " +
                                    util.toString(onCancel));
            }
            promise._attachCancellationCallback(onCancel);
        });
    } catch (e) {
        return e;
    }
}

function cancellationAttachCancellationCallback(onCancel) {
    if (!this._isCancellable()) return this;

    var previousOnCancel = this._onCancel();
    if (previousOnCancel !== undefined) {
        if (util.isArray(previousOnCancel)) {
            previousOnCancel.push(onCancel);
        } else {
            this._setOnCancel([previousOnCancel, onCancel]);

View on GitHub (pinned to c220cfe480)

Solutions

  1. Pass an actual function: onCancel(() => cleanup())
  2. Check the variable passed to onCancel is defined and a function (typeof x === 'function')
  3. Remember onCancel takes a callback, not a reason string — move reason into your own closure state
  4. Wrap executor body so a bad onCancel call rejects rather than crashes the constructor

Example fix

// before
new Promise((resolve, reject, onCancel) => {
  onCancel('request aborted'); // not a function
});
// after
new Promise((resolve, reject, onCancel) => {
  onCancel(() => socket.close('request aborted'));
});
Defensive patterns

Strategy: type-guard

Validate before calling

new Promise((resolve, reject, onCancel) => {
  const cb = () => cleanup();
  if (typeof cb !== 'function') throw new TypeError('onCancel callback must be a function');
  onCancel(cb);
  ...
});

Type guard

function isOnCancelCallback(fn) {
  return typeof fn === 'function';
}

Try / catch

new Promise((resolve, reject, onCancel) => {
  try {
    onCancel(() => rollback());
    doWork(resolve, reject);
  } catch (e) {
    reject(e);
  }
});

Prevention

When it happens

Trigger: new Promise((resolve, reject, onCancel) => { onCancel(someNonFunction) }) — e.g. onCancel(someVar) where someVar is undefined, null, a number, or the result of a misreferenced identifier; also passing a truthy non-function like a promise or config object.

Common situations: Passing the wrong variable to onCancel; expecting onCancel to accept a value/reason instead of a function; typos like onCancel(this.cleanup) where cleanup is undefined; copy-paste errors when adding cancellation support to executors.

Related errors


AI-assisted analysis of petkaantonov/bluebird@c220cfe480 (2026-09-02). Data as JSON: /api/errors/eba1e8d16c5b7207. Report an issue: GitHub.