Skip to content
11 changes: 4 additions & 7 deletions Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,21 +105,20 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
return eventLoop
}

@MainActor private static var didInstallGlobalExecutor = false
private nonisolated(unsafe) static var didInstallGlobalExecutor = false

/// Set JavaScript event loop based executor to be the global executor
/// Note that this should be called before any of the jobs are created.
/// This installation step will be unnecessary after custom executor are
/// introduced officially. See also [a draft proposal for custom
/// executors](https://github.com/rjmccall/swift-evolution/blob/custom-executors/proposals/0000-custom-executors.md#the-default-global-concurrent-executor)
public static func installGlobalExecutor() {
MainActor.assumeIsolated {
Self.installGlobalExecutorIsolated()
}
Self.installGlobalExecutorIsolated()
}

@MainActor private static func installGlobalExecutorIsolated() {
private static func installGlobalExecutorIsolated() {
guard !didInstallGlobalExecutor else { return }
didInstallGlobalExecutor = true

#if compiler(>=5.9)
typealias swift_task_asyncMainDrainQueue_hook_Fn = @convention(thin) (
Expand Down Expand Up @@ -188,8 +187,6 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
swift_task_enqueueMainExecutor_hook_impl,
to: UnsafeMutableRawPointer?.self
)

didInstallGlobalExecutor = true
}

private func enqueue(_ job: UnownedJob, withDelay nanoseconds: UInt64) {
Expand Down
33 changes: 19 additions & 14 deletions Sources/JavaScriptKit/BasicObjects/JSPromise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,21 +84,22 @@ public final class JSPromise: JSBridgedClass {
}
#endif

#if !hasFeature(Embedded)
/// Schedules the `success` closure to be invoked on successful completion of `self`.
@discardableResult
public func then(success: @escaping (JSValue) -> ConvertibleToJSValue) -> JSPromise {
public func then(success: @escaping (JSValue) -> JSValue) -> JSPromise {
let closure = JSOneshotClosure {
success($0[0]).jsValue
}
return JSPromise(unsafelyWrapping: jsObject.then!(closure).object!)
}

#if compiler(>=5.5)
#if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI))
/// Schedules the `success` closure to be invoked on successful completion of `self`.
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
@discardableResult
public func then(success: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue) -> JSPromise {
public func then(
success: sending @escaping (sending JSValue) async throws -> JSValue
) -> JSPromise {
let closure = JSOneshotClosure.async {
try await success($0[0]).jsValue
}
Expand All @@ -109,8 +110,8 @@ public final class JSPromise: JSBridgedClass {
/// Schedules the `success` closure to be invoked on successful completion of `self`.
@discardableResult
public func then(
success: @escaping (sending JSValue) -> ConvertibleToJSValue,
failure: @escaping (sending JSValue) -> ConvertibleToJSValue
success: @escaping (sending JSValue) -> JSValue,
failure: @escaping (sending JSValue) -> JSValue
) -> JSPromise {
let successClosure = JSOneshotClosure {
success($0[0]).jsValue
Expand All @@ -121,13 +122,13 @@ public final class JSPromise: JSBridgedClass {
return JSPromise(unsafelyWrapping: jsObject.then!(successClosure, failureClosure).object!)
}

#if compiler(>=5.5)
#if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI))
/// Schedules the `success` closure to be invoked on successful completion of `self`.
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
@discardableResult
public func then(
success: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue,
failure: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue
success: sending @escaping (sending JSValue) async throws -> JSValue,
failure: sending @escaping (sending JSValue) async throws -> JSValue
) -> JSPromise {
let successClosure = JSOneshotClosure.async {
try await success($0[0]).jsValue
Expand All @@ -141,19 +142,24 @@ public final class JSPromise: JSBridgedClass {

/// Schedules the `failure` closure to be invoked on rejected completion of `self`.
@discardableResult
public func `catch`(failure: @escaping (sending JSValue) -> ConvertibleToJSValue) -> JSPromise {
public func `catch`(
failure: @escaping (sending JSValue) -> JSValue
)
-> JSPromise
{
let closure = JSOneshotClosure {
failure($0[0]).jsValue
}
return .init(unsafelyWrapping: jsObject.catch!(closure).object!)
}

#if compiler(>=5.5)
#if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI))
/// Schedules the `failure` closure to be invoked on rejected completion of `self`.
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
@discardableResult
public func `catch`(failure: sending @escaping (sending JSValue) async throws -> ConvertibleToJSValue) -> JSPromise
{
public func `catch`(
failure: sending @escaping (sending JSValue) async throws -> JSValue
) -> JSPromise {
let closure = JSOneshotClosure.async {
try await failure($0[0]).jsValue
}
Expand All @@ -171,5 +177,4 @@ public final class JSPromise: JSBridgedClass {
}
return .init(unsafelyWrapping: jsObject.finally!(closure).object!)
}
#endif
}
9 changes: 6 additions & 3 deletions Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import _CJavaScriptKit
#if hasFeature(Embedded) && os(WASI)
import _Concurrency
#endif

/// `JSClosureProtocol` wraps Swift closure objects for use in JavaScript. Conforming types
/// are responsible for managing the lifetime of the closure they wrap, but can delegate that
Expand Down Expand Up @@ -40,7 +43,7 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol {
fatalError("JSOneshotClosure does not support dictionary literal initialization")
}

#if compiler(>=5.5) && !hasFeature(Embedded)
#if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI))
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public static func async(_ body: sending @escaping (sending [JSValue]) async throws -> JSValue) -> JSOneshotClosure
{
Expand Down Expand Up @@ -132,7 +135,7 @@ public class JSClosure: JSFunction, JSClosureProtocol {
fatalError("JSClosure does not support dictionary literal initialization")
}

#if compiler(>=5.5) && !hasFeature(Embedded)
#if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI))
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public static func async(_ body: @Sendable @escaping (sending [JSValue]) async throws -> JSValue) -> JSClosure {
JSClosure(makeAsyncClosure(body))
Expand All @@ -148,7 +151,7 @@ public class JSClosure: JSFunction, JSClosureProtocol {
#endif
}

#if compiler(>=5.5) && !hasFeature(Embedded)
#if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI))
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
private func makeAsyncClosure(
_ body: sending @escaping (sending [JSValue]) async throws -> JSValue
Expand Down
6 changes: 3 additions & 3 deletions Tests/JavaScriptEventLoopTests/JSPromiseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ final class JSPromiseTests: XCTestCase {
p1 = p1.then { value in
XCTAssertEqual(value, .null)
continuation.resume()
return JSValue.number(1.0)
return JSValue.number(1.0).jsValue
}
}
await withCheckedContinuation { continuation in
p1 = p1.then { value in
XCTAssertEqual(value, .number(1.0))
continuation.resume()
return JSPromise.resolve(JSValue.boolean(true))
return JSPromise.resolve(JSValue.boolean(true)).jsValue
}
}
await withCheckedContinuation { continuation in
Expand Down Expand Up @@ -48,7 +48,7 @@ final class JSPromiseTests: XCTestCase {
p2 = p2.then { value in
XCTAssertEqual(value, .boolean(true))
continuation.resume()
return JSPromise.reject(JSValue.number(2.0))
return JSPromise.reject(JSValue.number(2.0)).jsValue
}
}
await withCheckedContinuation { continuation in
Expand Down
4 changes: 2 additions & 2 deletions Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ final class JavaScriptEventLoopTests: XCTestCase {
}
let promise2 = promise.then { result in
try await Task.sleep(nanoseconds: 100_000_000)
return String(result.number!)
return .string(String(result.number!))
}
let thenDiff = try await measureTime {
let result = try await promise2.value
Expand All @@ -171,7 +171,7 @@ final class JavaScriptEventLoopTests: XCTestCase {
100
)
}
let failingPromise2 = failingPromise.then { _ in
let failingPromise2 = failingPromise.then { _ -> JSValue in
throw MessageError("Should not be called", file: #file, line: #line, column: #column)
} failure: { err in
return err
Expand Down