Threadsafe functions
Contents
- The pattern
- Reference counting
- The payload type
- Call modes
- Keeping the event loop alive
- Method summary
- Signal-only callbacks
ThreadsafeFn(T) lets a background thread call back into JS. Node is single-threaded, so you cannot call N-API from a spawned thread directly. ThreadsafeFn queues calls back to the main thread and runs them there.
Use it for multi-call patterns: progress events, streaming results, pub/sub. For single-result background work, use Workers instead.
The pattern
- Create a
ThreadsafeFnfrom anapi.Callbackwithcb.threadsafe(env, name, T). - Hand the
ThreadsafeFnto whatever spawned threads will call into JS. - Each thread calls
acquire()to register itself, then callscall(value, mode)whenever it wants to invoke the JS callback. - Each thread calls
release()when it is done.
Reference counting
ThreadsafeFn lives as long as it has at least one reference. The reference count starts at 1 (held by the main thread that created it). Every additional thread that wants to call must:
acquire()before it starts, to register itself.release()when it stops, to drop its reference.
When the count drops to zero, the wrapper is destroyed and the JS callback is unrooted. Forgetting to release keeps the JS function alive forever; double-release is a use-after-free.
The standard recipe (as in the example above) is defer ts.release() immediately after a successful acquire().
The payload type
The third argument to cb.threadsafe(env, name, T) is the per-call payload. The bridge converts each T to a JS value before invoking the callback.
For struct payloads, the field-by-field walk applies as usual:
For slice-backed payloads (including strings and slices nested in structs, arrays, optionals, or tagged unions), call queues a deep snapshot. The calling thread may mutate or release the original slice after call returns. Raw pointers and opaque external resources are not cloned automatically.
If payload conversion fails or the JavaScript callback throws, the bridge forwards that error exactly once through Node.js's uncaughtException path. A threadsafe call has no per-call Promise or return channel, so applications should install the error policy appropriate for their process.
Call modes
call(value, mode) takes a napi.ThreadsafeFn(T).Mode:
.blockingwaits if the queue is full. Safe but can deadlock if the main thread is blocked too..non_blockingreturnserror.QueueFullimmediately if the queue is full. Drop the event or retry, your choice.
For low-rate signaling, .blocking is fine. For high-rate streams, .non_blocking plus a backpressure strategy is safer.
Keeping the event loop alive
A ThreadsafeFn keeps the Node.js event loop alive by default. The process will not exit while a ThreadsafeFn exists. Use unref(env) to detach:
This is the same semantic as setInterval(..).unref() in Node. Use it when the threadsafe function is a watchdog or telemetry channel that should not by itself prevent shutdown.
ref(env) reverses it.
Method summary
Signal-only callbacks
Use void as the payload type when the JS callback takes no arguments: