From 2654a09c86783e46fcacb41c0c2b2fece08409a2 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 12 May 2025 23:47:56 +0900 Subject: [PATCH 1/3] Restricting throwable exception type to JSException for closures --- .../BasicObjects/JSPromise.swift | 24 +++++++++---------- .../FundamentalObjects/JSClosure.swift | 11 +++++---- .../JavaScriptEventLoopTests.swift | 8 +++---- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift index f0ef6da9..24a9ae48 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift @@ -98,10 +98,10 @@ public final class JSPromise: JSBridgedClass { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) @discardableResult public func then( - success: sending @escaping (sending JSValue) async throws -> JSValue + success: sending @escaping (sending JSValue) async throws(JSException) -> JSValue ) -> JSPromise { - let closure = JSOneshotClosure.async { - try await success($0[0]).jsValue + let closure = JSOneshotClosure.async { arguments throws(JSException) -> JSValue in + return try await success(arguments[0]) } return JSPromise(unsafelyWrapping: jsObject.then!(closure).object!) } @@ -127,14 +127,14 @@ public final class JSPromise: JSBridgedClass { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) @discardableResult public func then( - success: sending @escaping (sending JSValue) async throws -> JSValue, - failure: sending @escaping (sending JSValue) async throws -> JSValue + success: sending @escaping (sending JSValue) async throws(JSException) -> JSValue, + failure: sending @escaping (sending JSValue) async throws(JSException) -> JSValue ) -> JSPromise { - let successClosure = JSOneshotClosure.async { - try await success($0[0]).jsValue + let successClosure = JSOneshotClosure.async { arguments throws(JSException) -> JSValue in + try await success(arguments[0]).jsValue } - let failureClosure = JSOneshotClosure.async { - try await failure($0[0]).jsValue + let failureClosure = JSOneshotClosure.async { arguments throws(JSException) -> JSValue in + try await failure(arguments[0]).jsValue } return JSPromise(unsafelyWrapping: jsObject.then!(successClosure, failureClosure).object!) } @@ -158,10 +158,10 @@ public final class JSPromise: JSBridgedClass { @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) @discardableResult public func `catch`( - failure: sending @escaping (sending JSValue) async throws -> JSValue + failure: sending @escaping (sending JSValue) async throws(JSException) -> JSValue ) -> JSPromise { - let closure = JSOneshotClosure.async { - try await failure($0[0]).jsValue + let closure = JSOneshotClosure.async { arguments throws(JSException) -> JSValue in + try await failure(arguments[0]).jsValue } return .init(unsafelyWrapping: jsObject.catch!(closure).object!) } diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift index 7aaba9ed..885a25fc 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift @@ -45,8 +45,9 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol { #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 - { + public static func async( + _ body: sending @escaping (sending [JSValue]) async throws(JSException) -> JSValue + ) -> JSOneshotClosure { JSOneshotClosure(makeAsyncClosure(body)) } #endif @@ -137,7 +138,9 @@ public class JSClosure: JSFunction, JSClosureProtocol { #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 { + public static func async( + _ body: @Sendable @escaping (sending [JSValue]) async throws(JSException) -> JSValue + ) -> JSClosure { JSClosure(makeAsyncClosure(body)) } #endif @@ -154,7 +157,7 @@ public class JSClosure: JSFunction, JSClosureProtocol { #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 + _ body: sending @escaping (sending [JSValue]) async throws(JSException) -> JSValue ) -> ((sending [JSValue]) -> JSValue) { { arguments in JSPromise { resolver in diff --git a/Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift b/Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift index 4224e2a6..8fbbd817 100644 --- a/Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift +++ b/Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift @@ -150,7 +150,7 @@ final class JavaScriptEventLoopTests: XCTestCase { ) } let promise2 = promise.then { result in - try await Task.sleep(nanoseconds: 100_000_000) + try! await Task.sleep(nanoseconds: 100_000_000) return .string(String(result.number!)) } let thenDiff = try await measureTime { @@ -172,7 +172,7 @@ final class JavaScriptEventLoopTests: XCTestCase { ) } let failingPromise2 = failingPromise.then { _ -> JSValue in - throw MessageError("Should not be called", file: #file, line: #line, column: #column) + fatalError("Should not be called") } failure: { err in return err } @@ -192,7 +192,7 @@ final class JavaScriptEventLoopTests: XCTestCase { ) } let catchPromise2 = catchPromise.catch { err in - try await Task.sleep(nanoseconds: 100_000_000) + try! await Task.sleep(nanoseconds: 100_000_000) return err } let catchDiff = try await measureTime { @@ -225,7 +225,7 @@ final class JavaScriptEventLoopTests: XCTestCase { func testAsyncJSClosure() async throws { // Test Async JSClosure let delayClosure = JSClosure.async { _ -> JSValue in - try await Task.sleep(nanoseconds: 200_000_000) + try! await Task.sleep(nanoseconds: 200_000_000) return JSValue.number(3) } let delayObject = JSObject.global.Object.function!.new() From dccffb49eac63cbe16c8b11469d2a0acdb77419b Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Mon, 12 May 2025 23:55:05 +0900 Subject: [PATCH 2/3] Add missing _Concurrency imports --- .../JavaScriptEventLoop+ExecutorFactory.swift | 1 + .../JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop+ExecutorFactory.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop+ExecutorFactory.swift index d008ea67..ed60eae7 100644 --- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop+ExecutorFactory.swift +++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop+ExecutorFactory.swift @@ -4,6 +4,7 @@ // See: https://github.com/swiftlang/swift/pull/80266 // See: https://forums.swift.org/t/pitch-2-custom-main-and-global-executors/78437 +import _Concurrency import _CJavaScriptKit #if compiler(>=6.2) diff --git a/Sources/JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift b/Sources/JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift index 54d1c5dd..bcab9a3d 100644 --- a/Sources/JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift +++ b/Sources/JavaScriptEventLoop/JavaScriptEventLoop+LegacyHooks.swift @@ -1,3 +1,4 @@ +import _Concurrency import _CJavaScriptEventLoop import _CJavaScriptKit From 9cdef51c7d70276df229e48d11fffd7a67fd2b5b Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 13 May 2025 07:51:15 +0900 Subject: [PATCH 3/3] Remove redundant catch block for `any Error` --- .../JavaScriptKit/FundamentalObjects/JSClosure.swift | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift index 885a25fc..18a40078 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift @@ -167,19 +167,15 @@ private func makeAsyncClosure( struct Context: @unchecked Sendable { let resolver: (JSPromise.Result) -> Void let arguments: [JSValue] - let body: (sending [JSValue]) async throws -> JSValue + let body: (sending [JSValue]) async throws(JSException) -> JSValue } let context = Context(resolver: resolver, arguments: arguments, body: body) Task { - do { + do throws(JSException) { let result = try await context.body(context.arguments) context.resolver(.success(result)) } catch { - if let jsError = error as? JSException { - context.resolver(.failure(jsError.thrownValue)) - } else { - context.resolver(.failure(JSError(message: String(describing: error)).jsValue)) - } + context.resolver(.failure(error.thrownValue)) } } }.jsValue()