Skip to content
easy-web-worker

Transferable objects

Move large buffers between threads without copying them, in both directions.

GuideTypeScriptTry an example

Copy or transfer

Values sent to a worker are copied by default. For a large ArrayBuffer that means allocating the same memory twice. A transfer moves ownership instead: the receiving thread gets the memory, and the sender is left with an empty buffer.

ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas and streams are transferable.

From the main thread to the worker

The second argument of every method is the transfer list.

main.ts
import { createWorker } from 'easy-web-worker/createWorker';
import workerUrl from './images.worker?worker&url';
import type { ImagesWorker } from './images.worker';

const images = createWorker<ImagesWorker>(workerUrl, {
  workerOptions: { type: 'module' },
});

export async function invert(file: File) {
  const buffer = await file.arrayBuffer();

  // the second argument is the transfer list
  const inverted = await images.invert(buffer, [buffer]);

  console.log(buffer.byteLength); // 0: the memory moved to the worker
  console.log(inverted.byteLength); // the full size, moved back

  return inverted;
}

From the worker back to the main thread

A returned value is always copied. To transfer a result, complete the call yourself and pass the transfer list to message.resolve.

images.worker.ts
import { defineWorker } from 'easy-web-worker/defineWorker';

const worker = defineWorker(({ onMessage }) => ({
  // a returned value is copied
  histogram: (pixels: ArrayBuffer) => {
    const counts = new Array<number>(256).fill(0);

    new Uint8Array(pixels).forEach((value) => {
      counts[value] += 1;
    });

    return counts;
  },

  // to transfer the result, complete the call yourself
  invert: onMessage<ArrayBuffer, ArrayBuffer>().handle((message) => {
    const bytes = new Uint8Array(message.payload);

    for (let index = 0; index < bytes.length; index++) {
      bytes[index] = 255 - bytes[index];
    }

    message.resolve(bytes.buffer, [bytes.buffer]);
  }),
}));

export type ImagesWorker = typeof worker;

reject, cancel and reportProgress accept a transfer list too.

Try it

The transferable buffers example sends the same buffer as a copy and as a transfer, and measures both.

Documentation
↑↓ Navigate ↵ Open Esc Closeeasy-web-worker docs
Get started

Real Web Workers. Typed methods.
Cancelable promises.