Skip to content

Concurrency runtime for JavaScript

Move JavaScript
off the main thread.

npm install knittingNode · Deno · Bun
server.ts
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { createPool, isMain } from "knitting";

export const double = (n: number) => n * 2;

if (isMain) {
  const pool = createPool({
    threads: 1,
  })({ double });

  const app = new Hono().get("/", async (c) =>
    c.json({ result: await pool.call.double(21) })
  );
  serve({ fetch: app.fetch, port: 3000 });
}
❯ bun run server.ts
{ result: 42 }
host bun · main thread
await pool.call.double(21)

same process, shared memory

Call your functions in a thread or a separate process.
Zero dependenciesNothing else to install
Typed callsTyped arguments and results
Threads or processesThe same API for both
Open sourceApache-2.0 licensed

Quick start

Run a function in a worker.

This Hono route compresses HTML in a worker and returns the compressed size.

main.tsTypeScript
import { serve } from "@hono/node-server";
import { brotliCompressSync } from "node:zlib";
import { Hono } from "hono";
import { createPool, isMain } from "knitting";
export const compress = (html: string) =>
brotliCompressSync(html).byteLength;
if (isMain) {
const pool = createPool({ threads: 1 })({ compress });
const app = new Hono().get("/", async (c) => {
const html = c.req.query("html") ?? "<h1>Hello from Hono</h1>";
return c.json({ compressedBytes: await pool.call.compress(html) });
});
serve({ fetch: app.fetch, port: 3000 });
}
  1. 01
    Export your function

    Define and export the function at the top level of its module.

  2. 02
    Create the pool

    Create a pool with one worker and pass it your function.

  3. 03
    Call it from your route

    Use await to get the result, just like any other async call.

InstallInstall the example’s dependencies.
npm install hono @hono/node-server knitting
deno add --npm hono @hono/node-server knitting
bun add hono @hono/node-server knitting
Installation details

In a busy server

Keep fast routes fast.

When the main thread is busy, even a simple /ping has to wait. Moving React rendering and JWT signing into one worker leaves more room for the main thread to respond.

In this Hono benchmark, /ping responded sooner at the same load, with roughly the same CPU use. The ping route itself stayed on the main thread.

Measured in 15-second runs with one worker. Results depend on your workload and pool size.

/ping response time p99 · lower is better

16.93 msBefore
2.31 msWith one worker
6,000 requests/sec in both runs.
CPU per request: 180 µs before · 183 µs with one worker.

Working with a pool

Pass data, handle errors, and debug.

Large inputs

Share large buffers.

Pass ordinary JavaScript values directly. For large buffers, shared memory and buffer references can reduce copying. The available options depend on your worker type.

  • Promise inputs
  • SharedArrayBuffer
  • ProcessSharedBuffer
  • BufferReference
  • Typed arrays
Passing data

Isolation

Run workers in separate processes.

Threads are the default. For stronger isolation, use a separate process inside a sandbox or container. Use importTask to load the task’s code only in the worker.

threadprocesssandbox
Process workers

Errors and timeouts

Stop tasks that take too long.

Use timeouts and abort signals for tasks that support cancellation. For stuck CPU work, set a deadline that terminates the worker. Errors keep their stack traces and causes.

Tasks and timeouts

Debugging

See what your workers are doing.

Turn on logs for worker setup, imports, and task activity. Choose which diagnostics to enable; the logger stays unloaded when they’re off.

Configure a pool

Scheduling

Keep available workers busy.

With work stealing, available workers can pick up waiting tasks instead of leaving them queued behind a busy worker. Knitting enables this automatically for compatible pools.

  • Work stealing

How scheduling works

Examples

Rendering, hashing, and compression.

Each example includes code and measurements. Workers are useful for CPU work; they usually won’t help code that mostly waits for a database or network response.

Benchmarks and tests

See how it’s tested.

The benchmarks include the setup and methodology. The tests cover shared memory, timeouts, permissions, and worker failures. Found a bug? Open an issue.

Pick one slow function.

Move it into a worker and compare how your app responds.