openNativeAsync<T> static method

Future<T> openNativeAsync<T>({
  1. required void call(
    1. int dartPort
    ),
  2. required T unpack(
    1. dynamic raw
    ),
  3. void cleanup()?,
  4. String methodName = '',
})

Opens a single-use WebReceivePort, hands its port id to call so the WASM implementation can post the result through the module post callback, then waits for exactly one message and converts it with unpack. Mirrors the native contract, including NitroConfig.nativeAsyncTimeoutMs.

Implementation

static Future<T> openNativeAsync<T>({
  required void Function(int dartPort) call,
  required T Function(dynamic raw) unpack,
  void Function()? cleanup,
  String methodName = '',
}) {
  final cfg = NitroConfig.instance;
  final effective = cfg.effectiveLogLevel;
  final traceTimeline = cfg.timelineTracingEnabled;
  final timeoutMs = cfg.nativeAsyncTimeoutMs;
  final sw = effective == NitroLogLevel.verbose || cfg.slowCallThresholdUs > 0 ? (Stopwatch()..start()) : null;

  String tag() => methodName.isEmpty ? 'nativeAsync' : 'nativeAsync($methodName)';

  if (effective == NitroLogLevel.verbose) _log(NitroLogLevel.verbose, tag(), 'calling');

  final port = WebReceivePort();
  if (traceTimeline) developer.Timeline.startSync(_timelineLabel(tag()));

  void terminate() {
    port.close();
    cleanup?.call();
    if (traceTimeline) developer.Timeline.finishSync();
  }

  try {
    call(port.sendPort.nativePort);
  } catch (e, st) {
    terminate();
    if (effective != NitroLogLevel.none) {
      _log(NitroLogLevel.error, tag(), 'threw: $e', e, st);
    }
    rethrow;
  }

  T handle(dynamic raw) {
    if (sw != null) _logCallTiming(sw, tag());
    try {
      return unpack(raw);
    } catch (e, st) {
      if (effective != NitroLogLevel.none) {
        _log(NitroLogLevel.error, tag(), 'threw during unpack: $e', e, st);
      }
      rethrow;
    }
  }

  if (timeoutMs <= 0) {
    return port.first.then(handle).whenComplete(terminate);
  }

  final completer = Completer<dynamic>();
  final timer = Timer(Duration(milliseconds: timeoutMs), () {
    if (!completer.isCompleted) {
      completer.completeError(
        TimeoutException('${tag()} did not post a result within ${timeoutMs}ms', Duration(milliseconds: timeoutMs)),
      );
    }
  });
  final sub = port.listen((msg) {
    if (!completer.isCompleted) completer.complete(msg);
  });
  return completer.future.then(handle).whenComplete(() {
    timer.cancel();
    sub.cancel();
    terminate();
  });
}