fabricjs/fabric.js · warning · SignalAbortedError

loadImage

Error message

loadImage

What it means

SignalAbortedError thrown by loadImage when the options.signal is already aborted before the image load starts (or after the resourceValidator async check completes). It is the standard AbortSignal integration: aborting an enliven/load operation should cancel pending image fetches, and fabric surfaces that as a distinct error type rather than a generic failure.

Source

Thrown at packages/core/src/util/misc/objectEnlive.ts:44

 * @param {LoadImageOptions} [options] image loading options
 * @returns {Promise<HTMLImageElement>} the loaded image.
 */
export const loadImage = (
  url: string,
  { signal, crossOrigin = null, resourceValidator }: LoadImageOptions = {},
): Promise<HTMLImageElement> => {
  if (signal && signal.aborted) {
    return Promise.reject(new SignalAbortedError('loadImage'));
  }
  if (url && resourceValidator) {
    return Promise.resolve()
      .then(() => resourceValidator(url))
      .then((isAllowed) => {
        if (!isAllowed) {
          throw new FabricError(`Resource '${url}' is not allowed`);
        }
        if (signal && signal.aborted) {
          throw new SignalAbortedError('loadImage');
        }
        return loadImage(url, { signal, crossOrigin });
      });
  }
  return new Promise<HTMLImageElement>(function (resolve, reject) {
    if (signal && signal.aborted) {
      return reject(new SignalAbortedError('loadImage'));
    }
    const img = createImage();
    let abort: EventListenerOrEventListenerObject;
    if (signal) {
      abort = function (err: Event) {
        img.src = '';
        reject(err);
      };
      signal.addEventListener('abort', abort, { once: true });
    }
    const done = function () {

View on GitHub (pinned to 2bd4992cab)

Solutions

  1. Check signal.aborted before starting the load and skip it if aborted
  2. Ensure the AbortController isn't aborted prematurely (e.g. abort on unmount only when the load is truly obsolete)
  3. Catch SignalAbortedError specifically and treat it as a cancel, not a failure (skip UI error states)
  4. Create a fresh controller per load instead of reusing an aborted one

Example fix

// before
const img = await fabric.loadImage(url, { signal: controller.signal }); // throws SignalAbortedError('loadImage')

// after
if (!controller.signal.aborted) {
  const img = await fabric.loadImage(url, { signal: controller.signal });
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!controller.signal.aborted) {
  img = await fabric.loadImage(url, { signal: controller.signal });
}

Type guard

const isAborted = (signal?: AbortSignal): boolean => !!signal?.aborted;

Try / catch

try {
  await fabric.loadImage(url, { signal: controller.signal });
} catch (e) {
  if (e.name === 'SignalAbortedError' || controller.signal.aborted) {
    // cancelled: silently return, do not surface as an error
  } else throw e;
}

Prevention

When it happens

Trigger: Calling fabric.loadImage(url, { signal }) with an already-aborted AbortController's signal, or the controller being aborted while the resourceValidator promise resolves. Also occurs during canvas.loadFromJSON when the caller aborts mid-enliven.

Common situations: React components that abort a controller on unmount and then a late-loading image starts with the dead signal; race conditions where cleanup runs before load; passing a shared controller's signal that another operation already aborted.

Related errors


AI-assisted analysis of fabricjs/fabric.js@2bd4992cab (2026-08-28). Data as JSON: /api/errors/d7ea3ae69f895969. Report an issue: GitHub.