Skip to content

Timeout & Cancellation

All async functions in imgkit support timeout and cancellation via AsyncOptions.

AsyncOptions

typescript
interface AsyncOptions {
  /** Timeout in milliseconds. Operation rejects after this duration. */
  timeoutMs?: number;
  /** AbortSignal for cancellation. Operation rejects when signal is aborted. */
  signal?: AbortSignal;
}

Every async function accepts AsyncOptions as its last parameter. Sync functions are unaffected.

Timeout

Set timeoutMs to reject the promise if the operation exceeds the specified duration.

typescript
import { resize, toWebp, metadata } from 'imgkit';

// Reject after 5 seconds
const resized = await resize(buffer, { width: 800 }, { timeoutMs: 5000 });

// Works with all async functions
const info = await metadata(buffer, { timeoutMs: 1000 });
const webp = await toWebp(buffer, { quality: 85 }, { timeoutMs: 10000 });

When a timeout occurs, the promise rejects with an error containing "timed out" in the message:

typescript
try {
  await resize(buffer, { width: 100 }, { timeoutMs: 1 });
} catch (e) {
  console.log(e.message); // "Operation timed out after 1ms"
}

Behavior Note

The timeout stops the JavaScript promise from waiting, but the underlying Rust processing thread continues until completion. This is the same tradeoff used by sharp, fetch(), and other tools built on spawn_blocking.

AbortSignal

Use an AbortSignal to cancel operations from your code.

AbortController

typescript
import { transform } from 'imgkit';

const controller = new AbortController();

const promise = transform(buffer, {
  resize: { width: 1920 },
  output: { format: 'webp', webp: { quality: 85 } }
}, { signal: controller.signal });

// Cancel the operation (e.g., user navigated away)
controller.abort();

try {
  await promise;
} catch (e) {
  console.log(e.name); // "AbortError"
}

AbortSignal.timeout()

Use the built-in AbortSignal.timeout() for a signal-based timeout:

typescript
import { resize } from 'imgkit';

// Rejects with TimeoutError after 3 seconds
const result = await resize(buffer, { width: 800 }, {
  signal: AbortSignal.timeout(3000)
});

Custom Abort Reason

typescript
const controller = new AbortController();
controller.abort(new Error('User cancelled'));

try {
  await resize(buffer, { width: 100 }, { signal: controller.signal });
} catch (e) {
  console.log(e.message); // "User cancelled"
}

Already-Aborted Signal

If the signal is already aborted when the function is called, it rejects immediately without starting the operation:

typescript
const controller = new AbortController();
controller.abort();

// Rejects immediately — no work is started
await resize(buffer, { width: 100 }, { signal: controller.signal });

Combining Timeout and AbortSignal

You can use both timeoutMs and signal together. The operation rejects as soon as either triggers:

typescript
const controller = new AbortController();

const result = await resize(buffer, { width: 800 }, {
  timeoutMs: 10000,        // Rust-level timeout (10s)
  signal: controller.signal // JS-level cancellation
});
  • timeoutMs — handled in Rust via tokio, most efficient for pure time limits
  • signal — handled in JavaScript, best for programmatic cancellation (user actions, cleanup)

Supported Functions

All async functions support AsyncOptions:

FunctionSignature
metadata()metadata(input, asyncOptions?)
resize()resize(input, options, asyncOptions?)
crop()crop(input, options, asyncOptions?)
toJpeg()toJpeg(input, options?, asyncOptions?)
toPng()toPng(input, options?, asyncOptions?)
toWebp()toWebp(input, options?, asyncOptions?)
transform()transform(input, options, asyncOptions?)
blurhash()blurhash(input, cx?, cy?, asyncOptions?)
thumbhash()thumbhash(input, asyncOptions?)
thumbhashToRgba()thumbhashToRgba(hash, asyncOptions?)
imageHash()imageHash(input, options?, asyncOptions?)
imageHashDistance()imageHashDistance(h1, h2, asyncOptions?)
smartCrop()smartCrop(input, options, asyncOptions?)
smartCropAnalyze()smartCropAnalyze(input, options, asyncOptions?)
dominantColors()dominantColors(input, count?, asyncOptions?)
toTensor()toTensor(input, options?, asyncOptions?)
writeExif()writeExif(input, options, asyncOptions?)
stripExif()stripExif(input, asyncOptions?)
thumbnail()thumbnail(input, options, asyncOptions?)
thumbnailBuffer()thumbnailBuffer(input, options, asyncOptions?)

Use Cases

HTTP Server with Request Timeout

typescript
import { resize, toWebp } from 'imgkit';

Bun.serve({
  async fetch(req) {
    const controller = new AbortController();

    // Cancel if client disconnects
    req.signal.addEventListener('abort', () => controller.abort());

    try {
      const buffer = Buffer.from(await req.arrayBuffer());
      const resized = await resize(buffer, { width: 400 }, {
        timeoutMs: 5000,
        signal: controller.signal,
      });
      const webp = await toWebp(resized, { quality: 85 }, {
        timeoutMs: 5000,
        signal: controller.signal,
      });
      return new Response(webp, {
        headers: { 'Content-Type': 'image/webp' }
      });
    } catch (e) {
      return new Response('Processing failed', { status: 500 });
    }
  }
});

Batch Processing with Global Timeout

typescript
import { thumbnail } from 'imgkit';

const signal = AbortSignal.timeout(30000); // 30s for entire batch

const results = await Promise.allSettled(
  images.map(buf =>
    thumbnail(buf, { width: 200 }, { signal })
  )
);