Skip to content

Commit 7b2edbe

Browse files
Add JSClosure APIs to support specifying TaskExecutor and TaskPriority
1 parent 83e2335 commit 7b2edbe

File tree

1 file changed

+82
-6
lines changed

1 file changed

+82
-6
lines changed

Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public protocol JSClosureProtocol: JSValueCompatible {
1818
public class JSOneshotClosure: JSObject, JSClosureProtocol {
1919
private var hostFuncRef: JavaScriptHostFuncRef = 0
2020

21-
public init(_ body: @escaping (sending [JSValue]) -> JSValue, file: String = #fileID, line: UInt32 = #line) {
21+
public init(file: String = #fileID, line: UInt32 = #line, _ body: @escaping (sending [JSValue]) -> JSValue) {
2222
// 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`.
2323
super.init(id: 0)
2424

@@ -44,11 +44,34 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol {
4444
}
4545

4646
#if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI))
47+
/// Creates a new `JSOneshotClosure` that calls the given Swift function asynchronously.
48+
///
49+
/// - Parameters:
50+
/// - priority: The priority of the new unstructured Task created under the hood.
51+
/// - body: The Swift function to call asynchronously.
4752
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
4853
public static func async(
54+
priority: TaskPriority? = nil,
55+
file: String = #fileID, line: UInt32 = #line,
4956
_ body: sending @escaping (sending [JSValue]) async throws(JSException) -> JSValue
5057
) -> JSOneshotClosure {
51-
JSOneshotClosure(makeAsyncClosure(body))
58+
JSOneshotClosure(file: file, line: line, makeAsyncClosure(priority: priority, body))
59+
}
60+
61+
/// Creates a new `JSOneshotClosure` that calls the given Swift function asynchronously.
62+
///
63+
/// - Parameters:
64+
/// - taskExecutor: The executor preference of the new unstructured Task created under the hood.
65+
/// - priority: The priority of the new unstructured Task created under the hood.
66+
/// - body: The Swift function to call asynchronously.
67+
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
68+
public static func async(
69+
executorPreference taskExecutor: (any TaskExecutor)? = nil,
70+
priority: TaskPriority? = nil,
71+
file: String = #fileID, line: UInt32 = #line,
72+
_ body: @Sendable @escaping (sending [JSValue]) async throws(JSException) -> JSValue
73+
) -> JSOneshotClosure {
74+
JSOneshotClosure(file: file, line: line, makeAsyncClosure(executorPreference: taskExecutor, priority: priority, body))
5275
}
5376
#endif
5477

@@ -117,7 +140,7 @@ public class JSClosure: JSFunction, JSClosureProtocol {
117140
})
118141
}
119142

120-
public init(_ body: @escaping (sending [JSValue]) -> JSValue, file: String = #fileID, line: UInt32 = #line) {
143+
public init(file: String = #fileID, line: UInt32 = #line, _ body: @escaping (sending [JSValue]) -> JSValue) {
121144
// 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`.
122145
super.init(id: 0)
123146

@@ -137,11 +160,34 @@ public class JSClosure: JSFunction, JSClosureProtocol {
137160
}
138161

139162
#if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI))
163+
/// Creates a new `JSClosure` that calls the given Swift function asynchronously.
164+
///
165+
/// - Parameters:
166+
/// - priority: The priority of the new unstructured Task created under the hood.
167+
/// - body: The Swift function to call asynchronously.
140168
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
141169
public static func async(
142-
_ body: @Sendable @escaping (sending [JSValue]) async throws(JSException) -> JSValue
170+
priority: TaskPriority? = nil,
171+
file: String = #fileID, line: UInt32 = #line,
172+
_ body: sending @escaping @isolated(any) (sending [JSValue]) async throws(JSException) -> JSValue
173+
) -> JSClosure {
174+
JSClosure(file: file, line: line, makeAsyncClosure(priority: priority, body))
175+
}
176+
177+
/// Creates a new `JSClosure` that calls the given Swift function asynchronously.
178+
///
179+
/// - Parameters:
180+
/// - taskExecutor: The executor preference of the new unstructured Task created under the hood.
181+
/// - priority: The priority of the new unstructured Task created under the hood.
182+
/// - body: The Swift function to call asynchronously.
183+
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
184+
public static func async(
185+
executorPreference taskExecutor: (any TaskExecutor)? = nil,
186+
priority: TaskPriority? = nil,
187+
file: String = #fileID, line: UInt32 = #line,
188+
_ body: sending @escaping (sending [JSValue]) async throws(JSException) -> JSValue
143189
) -> JSClosure {
144-
JSClosure(makeAsyncClosure(body))
190+
JSClosure(file: file, line: line, makeAsyncClosure(executorPreference: taskExecutor, priority: priority, body))
145191
}
146192
#endif
147193

@@ -157,6 +203,36 @@ public class JSClosure: JSFunction, JSClosureProtocol {
157203
#if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI))
158204
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
159205
private func makeAsyncClosure(
206+
priority: TaskPriority?,
207+
_ body: sending @escaping @isolated(any) (sending [JSValue]) async throws(JSException) -> JSValue
208+
) -> ((sending [JSValue]) -> JSValue) {
209+
{ arguments in
210+
JSPromise { resolver in
211+
// NOTE: The context is fully transferred to the unstructured task
212+
// isolation but the compiler can't prove it yet, so we need to
213+
// use `@unchecked Sendable` to make it compile with the Swift 6 mode.
214+
struct Context: @unchecked Sendable {
215+
let resolver: (JSPromise.Result) -> Void
216+
let arguments: [JSValue]
217+
let body: (sending [JSValue]) async throws(JSException) -> JSValue
218+
}
219+
let context = Context(resolver: resolver, arguments: arguments, body: body)
220+
Task(priority: priority) {
221+
do throws(JSException) {
222+
let result = try await context.body(context.arguments)
223+
context.resolver(.success(result))
224+
} catch {
225+
context.resolver(.failure(error.thrownValue))
226+
}
227+
}
228+
}.jsValue()
229+
}
230+
}
231+
232+
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
233+
private func makeAsyncClosure(
234+
executorPreference taskExecutor: (any TaskExecutor)?,
235+
priority: TaskPriority?,
160236
_ body: sending @escaping (sending [JSValue]) async throws(JSException) -> JSValue
161237
) -> ((sending [JSValue]) -> JSValue) {
162238
{ arguments in
@@ -170,7 +246,7 @@ private func makeAsyncClosure(
170246
let body: (sending [JSValue]) async throws(JSException) -> JSValue
171247
}
172248
let context = Context(resolver: resolver, arguments: arguments, body: body)
173-
Task {
249+
Task(executorPreference: taskExecutor, priority: priority) {
174250
do throws(JSException) {
175251
let result = try await context.body(context.arguments)
176252
context.resolver(.success(result))

0 commit comments

Comments
 (0)