Skip to main content

Sync vs Async

arkffi’s default C function calls are synchronouslib.symbols.add(2.0, 3.0) blocks the current thread until the function returns. For expensive operations (complex computation, I/O), use callAsync to offload the call to a libuv worker thread and get a Promise back.

ffi.callAsync

import ffi from 'liblibrary.so';

const handle = ffi.load('libffi_target.so');
ffi.defineFunction(handle, 'factorial', 'i', 'l');

ffi.callAsync(handle, 'factorial', 'i', 'l', [10], []).then((r: number) => {
  console.log(r); // 3628800
  ffi.close(handle);
});

Syntax

function callAsync(
  handle: bigint,
  funcName: string,
  argTypes: string,
  returnType: string,
  numArgs: number[],
  strArgs: string[],
): Promise<number>;

Parameters

ParameterTypeDescription
handlebigintLibrary handle from ffi.load()
funcNamestringFunction name (must be registered via defineFunction)
argTypesstringArgument type codes
returnTypestringReturn type code
numArgsnumber[]Numeric arguments
strArgsstring[]String arguments

AsyncCFunction

AsyncCFunction is the async counterpart of CFunction with the same signature, returning Promise<number>. No defineFunction needed — works directly with function pointers:
import { AsyncCFunction, FFIType } from 'arkffi';
import ffi from 'liblibrary.so';

const handle = ffi.load('libffi_target.so');
const addPtr = ffi.getSymbolPtr(handle, 'add');

const asyncAdd = AsyncCFunction({
  args: [FFIType.double, FFIType.double],
  returns: FFIType.double,
  ptr: addPtr,
});

asyncAdd(2.0, 3.0).then((r: number) => {
  console.log(r); // 5.0
});

Promise.all([asyncAdd(1.0, 2.0), asyncAdd(3.0, 4.0)]).then((results) => {
  console.log(results); // [3.0, 7.0]
});

asyncAdd.close();
ffi.close(handle);

Comparison

FeatureCFunctionAsyncCFunction
Returnsnumber (sync)Promise<number> (async)
Blocks main thread✅ Yes❌ No
Needs defineFunction❌ No❌ No
Type supportAllAll
Close method.close().close()

Usage with dlopen

Functions must be registered via defineFunction before making async calls:
const handle = ffi.load('libffi_target.so');
ffi.defineFunction(handle, 'add', 'dd', 'd');

const p1 = ffi.callAsync(handle, 'add', 'dd', 'd', [1.0, 2.0], []);
const p2 = ffi.callAsync(handle, 'add', 'dd', 'd', [3.0, 4.0], []);
Promise.all([p1, p2]).then(([r1, r2]) => {
  console.log(r1, r2);
  ffi.close(handle);
});

Implementation

  1. callAsync parses arguments on the JS thread and creates a napi_async_work
  2. The worker thread executes DispatchCallRaw — a pure C function pointer call with no NAPI involvement
  3. On completion, the Promise is resolved on the JS thread
  4. The JS main thread is never blocked

Limitations

  • String return types are not supported (no async version of callString)
  • Callbacks (JSCallback) cannot be invoked from the async worker thread
  • The function must be registered via defineFunction first