From 781939e2fd5efe15918614fac6b99363c43bd216 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Tue, 20 Oct 2026 20:54:07 +0200 Subject: [PATCH] feat: await Swift callbacks with the call operator --- docs/docs/types/callbacks.md | 10 +++++++--- .../nitro-core/native/apple/core/Callback.swift | 10 ++++++++++ .../nitro-test/ios/HybridTestObjectSwift.swift | 15 +++++++-------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/docs/docs/types/callbacks.md b/docs/docs/types/callbacks.md index 2aac078cfc..5853b39e1d 100644 --- a/docs/docs/types/callbacks.md +++ b/docs/docs/types/callbacks.md @@ -135,7 +135,7 @@ Since JS callbacks could theoretically be called from any native Thread, callbac class HybridMath: HybridMathSpec { func calculate(getValue: any Callback<(), Double>) { Task { - let result = try await getValue().await() // Double + let result = try await getValue() // Double } } } @@ -153,8 +153,9 @@ func call(callback: any Callback<(), Void>) throws { } ``` -Additionally, the call operator overload either chooses `callAndForget()` when the callback returns `Void`, or `callAsync()` when it returns -a value. +In synchronous code, the call operator chooses `callAndForget()` when the callback returns `Void`, or returns a `Promise` when it returns a +value. In async code, `try await callback(...)` waits for the result and propagates errors, just like `try await callback.callAsync(...)`. +These Swift async overloads require a `Sendable` result. Use `callAndForget()` explicitly to fire and forget from an async function. ```swift func callVoid(callback: any Callback<(), Void>) { @@ -163,6 +164,9 @@ func callVoid(callback: any Callback<(), Void>) { func callResult(callback: any Callback<(), Double>) { callback() // Promise (just like callAsync()) } +func callResultAsync(callback: any Callback<(), Double>) async throws { + let result = try await callback() // Double +} ``` :::warning diff --git a/packages/nitro-core/native/apple/core/Callback.swift b/packages/nitro-core/native/apple/core/Callback.swift index b32cd2c2c9..8d408a9dff 100644 --- a/packages/nitro-core/native/apple/core/Callback.swift +++ b/packages/nitro-core/native/apple/core/Callback.swift @@ -77,6 +77,12 @@ extension Callback where Return: Sendable { public nonisolated(nonsending) func callAsync(_ args: repeat each A) async throws -> Return where Arguments == (repeat each A) { return try await callAsync((repeat each args)).await() } + + /// Invokes this callback and awaits its result, throwing if the callback fails. + public nonisolated(nonsending) func callAsFunction(_ args: repeat each A) async throws -> Return + where Arguments == (repeat each A) { + return try await callAsync(repeat each args) + } } /// Extension to support callAsFunction() which returns a `Promise` if it has a return type @@ -91,4 +97,8 @@ extension Callback where Return == Void { public func callAsFunction(_ args: repeat each A) where Arguments == (repeat each A) { callAndForget((repeat each args)) } + + public nonisolated(nonsending) func callAsFunction(_ args: repeat each A) async throws where Arguments == (repeat each A) { + try await callAsync((repeat each args)).await() + } } diff --git a/tests/standalone/nitro-test/ios/HybridTestObjectSwift.swift b/tests/standalone/nitro-test/ios/HybridTestObjectSwift.swift index 0728974cf6..15fcf18ae3 100644 --- a/tests/standalone/nitro-test/ios/HybridTestObjectSwift.swift +++ b/tests/standalone/nitro-test/ios/HybridTestObjectSwift.swift @@ -110,8 +110,7 @@ class HybridTestObjectSwift: HybridTestObjectSwiftKotlinSpec { -> any Promise { return .async { - let callPromise = callback() - let resultPromise = try await callPromise.await() + let resultPromise = try await callback() try await resultPromise.await() } } @@ -132,7 +131,7 @@ class HybridTestObjectSwift: HybridTestObjectSwiftKotlinSpec { Double > { return .async { - let jsResult = try await getValue().await() + let jsResult = try await getValue() return jsResult } } @@ -141,8 +140,8 @@ class HybridTestObjectSwift: HybridTestObjectSwiftKotlinSpec { callback: any Callback<(), String>, andThenCall: any Callback ) throws -> any Promise { return .async { - let jsResult = try await callback().await() - andThenCall(jsResult) + let jsResult = try await callback() + andThenCall.callAndForget(jsResult) } } @@ -150,7 +149,7 @@ class HybridTestObjectSwift: HybridTestObjectSwiftKotlinSpec { var result = 0.0 return .async { for _ in 0.. { return .async { - let promise = try await callback().await() + let promise = try await callback() let result = try await promise.await() return result } @@ -254,7 +253,7 @@ class HybridTestObjectSwift: HybridTestObjectSwiftKotlinSpec { callback: any Callback<(), any Promise<[Powertrain]>> ) throws -> any Promise<[Powertrain]> { return .async { - let promise = try await callback().await() + let promise = try await callback() return try await promise.await() } }