From 75ff9304e4fd1cd919fa4da442c4d9d6158a130d Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 16 Sep 2020 22:35:00 +0100 Subject: [PATCH 1/7] Add a generic `JSPromise` implementation --- .../BasicObjects/JSPromise.swift | 132 ++++++++++++++++++ .../JavaScriptKit/JSValueConstructible.swift | 6 + 2 files changed, 138 insertions(+) create mode 100644 Sources/JavaScriptKit/BasicObjects/JSPromise.swift diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift new file mode 100644 index 00000000..eb8e78c7 --- /dev/null +++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift @@ -0,0 +1,132 @@ +/** A wrapper around [the JavaScript `Promise` class](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise) +that exposes its functions in a type-safe and Swifty way. The `JSPromise` API is generic over both +`Success` and `Failure` types, which improves compatibility with other statically-typed APIs such +as Combine. If you don't know the exact type of your `Success` value, you should use `JSValue`, e.g. +`JSPromise`. In the rare case, where you can't guarantee that the error thrown +is of actual JavaScript `Error` type, you should use `JSPromise`. +*/ +public final class JSPromise: JSValueConvertible, JSValueConstructible +where Success: JSValueConstructible, Failure: JSValueConstructible { + /// The underlying JavaScript `Promise` object. + public let jsObject: JSObject + + private var callbacks = [JSClosure]() + + /// The underlying JavaScript `Promise` object wrapped as `JSValue`. + public func jsValue() -> JSValue { + .object(jsObject) + } + + /// This private initializer assumes that the passed object is a JavaScript `Promise` + private init(unsafe object: JSObject) { + self.jsObject = object + } + + /** Creates a new `JSPromise` instance from a given JavaScript `Promise` object. If `jsObject` + is not an instance of JavaScript `Promise`, this initializer will return `nil`. + */ + public init?(_ jsObject: JSObject) { + guard jsObject.isInstanceOf(JSObject.global.Promise.function!) else { return nil } + self.jsObject = jsObject + } + + /** Creates a new `JSPromise` instance from a given JavaScript `Promise` object. If `value` + is not an object and is not an instance of JavaScript `Promise`, this function will + return `nil`. + */ + public static func construct(from value: JSValue) -> Self? { + guard case let .object(jsObject) = value else { return nil } + return Self.init(jsObject) + } + + /** Schedules the `success` closure to be invoked on sucessful completion of `self`. + */ + public func then(success: @escaping (Success) -> ()) { + let closure = JSClosure { + success(Success.construct(from: $0[0])!) + } + callbacks.append(closure) + jsObject.then.function!(closure) + } + + /** Returns a new promise created from chaining the current `self` promise with the `success` + closure invoked on sucessful completion of `self`. The returned promise will have a new + `Success` type equal to the return type of `success`. + */ + public func then( + success: @escaping (Success) -> ResultType + ) -> JSPromise { + let closure = JSClosure { + success(Success.construct(from: $0[0])!).jsValue() + } + callbacks.append(closure) + return .init(unsafe: jsObject.then.function!(closure).object!) + } + + /** Returns a new promise created from chaining the current `self` promise with the `success` + closure invoked on sucessful completion of `self`. The returned promise will have a new type + equal to the return type of `success`. + */ + public func then( + success: @escaping (Success) -> JSPromise + ) -> JSPromise { + let closure = JSClosure { + success(Success.construct(from: $0[0])!).jsValue() + } + callbacks.append(closure) + return .init(unsafe: jsObject.then.function!(closure).object!) + } + + /** Schedules the `failure` closure to be invoked on rejected completion of `self`. + */ + public func `catch`(failure: @escaping (Failure) -> ()) { + let closure = JSClosure { + failure(Failure.construct(from: $0[0])!) + } + callbacks.append(closure) + jsObject.then.function!(JSValue.undefined, closure) + } + + /** Returns a new promise created from chaining the current `self` promise with the `failure` + closure invoked on rejected completion of `self`. The returned promise will have a new `Success` + type equal to the return type of the callback. + */ + public func `catch`( + failure: @escaping (Failure) -> ResultSuccess + ) -> JSPromise { + let closure = JSClosure { + failure(Failure.construct(from: $0[0])!).jsValue() + } + callbacks.append(closure) + return .init(unsafe: jsObject.then.function!(JSValue.undefined, closure).object!) + } + + /** Returns a new promise created from chaining the current `self` promise with the `failure` + closure invoked on rejected completion of `self`. The returned promise will have a new type + equal to the return type of `success`. + */ + public func `catch`( + failure: @escaping (Failure) -> JSPromise + ) -> JSPromise { + let closure = JSClosure { + failure(Failure.construct(from: $0[0])!).jsValue() + } + callbacks.append(closure) + return .init(unsafe: jsObject.then.function!(JSValue.undefined, closure).object!) + } + + /** Schedules the `failure` closure to be invoked on either successful or rejected completion of + `self`. + */ + public func finally(successOrFailure: @escaping () -> ()) -> Self { + let closure = JSClosure { _ in + successOrFailure() + } + callbacks.append(closure) + return .init(unsafe: jsObject.finally.function!(closure).object!) + } + + deinit { + callbacks.forEach { $0.release() } + } +} diff --git a/Sources/JavaScriptKit/JSValueConstructible.swift b/Sources/JavaScriptKit/JSValueConstructible.swift index 59a8c63d..6731891c 100644 --- a/Sources/JavaScriptKit/JSValueConstructible.swift +++ b/Sources/JavaScriptKit/JSValueConstructible.swift @@ -91,3 +91,9 @@ extension UInt64: JSValueConstructible { value.number.map(Self.init) } } + +extension Never: JSValueConstructible { + public static func construct(from value: JSValue) -> Never? { + fatalError() + } +} \ No newline at end of file From 19c643f119de21112b28fe02b67d861d649e7c08 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Wed, 16 Sep 2020 22:43:42 +0100 Subject: [PATCH 2/7] Refine `JSPromise` doc comment --- Sources/JavaScriptKit/BasicObjects/JSPromise.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift index eb8e78c7..72eadd0f 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift @@ -4,6 +4,10 @@ that exposes its functions in a type-safe and Swifty way. The `JSPromise` API is as Combine. If you don't know the exact type of your `Success` value, you should use `JSValue`, e.g. `JSPromise`. In the rare case, where you can't guarantee that the error thrown is of actual JavaScript `Error` type, you should use `JSPromise`. + +This doesn't 100% match the JavaScript API, as `then` overload with two callbacks is not available. +It's impossible to unify success and failure types from both callbacks in a single returned promise +without type erasure. You should chain `then` and `catch` in those cases to avoid type erasure. */ public final class JSPromise: JSValueConvertible, JSValueConstructible where Success: JSValueConstructible, Failure: JSValueConstructible { From c5fd9c2e850a089d01c293dafb2c7f532e89822a Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 17 Sep 2020 11:24:57 +0100 Subject: [PATCH 3/7] Add a test and more `JSPromise` initializers --- .../Sources/PrimaryTests/main.swift | 20 ++ .../JavaScriptKit/BasicObjects/JSArray.swift | 2 +- .../JavaScriptKit/BasicObjects/JSError.swift | 9 +- .../BasicObjects/JSPromise.swift | 207 ++++++++++++++---- .../BasicObjects/JSTypedArray.swift | 2 +- 5 files changed, 193 insertions(+), 47 deletions(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index bcd82270..5da72a5e 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -465,6 +465,26 @@ try test("Timer") { } } +var timer: JSTimer? +var promise: JSPromise<(), Never>? + +try test("Promise") { + let start = JSDate().valueOf() + let timeoutMilliseconds = 5.0 + + promise = JSPromise { resolve in + timer = JSTimer(millisecondsDelay: timeoutMilliseconds) { + resolve() + } + } + + promise!.then { + // verify that at least `timeoutMilliseconds` passed since the `timer` + // timer started + try! expectEqual(start + timeoutMilliseconds <= JSDate().valueOf(), true) + } +} + try test("Error") { let message = "test error" let error = JSError(message: message) diff --git a/Sources/JavaScriptKit/BasicObjects/JSArray.swift b/Sources/JavaScriptKit/BasicObjects/JSArray.swift index 4d5bf231..9210af18 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSArray.swift @@ -1,4 +1,4 @@ -/// A wrapper around [the JavaScript Array class](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array) +/// A wrapper around [the JavaScript Array class](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array) /// that exposes its properties in a type-safe and Swifty way. public class JSArray { static let classObject = JSObject.global.Array.function! diff --git a/Sources/JavaScriptKit/BasicObjects/JSError.swift b/Sources/JavaScriptKit/BasicObjects/JSError.swift index 4dff7585..b44df412 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSError.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSError.swift @@ -2,8 +2,8 @@ class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) that exposes its properties in a type-safe way. */ -public final class JSError: Error { - /// The constructor function used to create new `Error` objects. +public final class JSError: Error, JSValueConvertible { + /// The constructor function used to create new JavaScript `Error` objects. private static let constructor = JSObject.global.Error.function! /// The underlying JavaScript `Error` object. @@ -28,6 +28,11 @@ public final class JSError: Error { public var stack: String? { jsObject.stack.string } + + /// Creates a new `JSValue` from this `JSError` instance. + public func jsValue() -> JSValue { + .object(jsObject) + } } extension JSError: CustomStringConvertible { diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift index 72eadd0f..8c0968ba 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift @@ -1,4 +1,4 @@ -/** A wrapper around [the JavaScript `Promise` class](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise) +/** A wrapper around [the JavaScript `Promise` class](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise) that exposes its functions in a type-safe and Swifty way. The `JSPromise` API is generic over both `Success` and `Failure` types, which improves compatibility with other statically-typed APIs such as Combine. If you don't know the exact type of your `Success` value, you should use `JSValue`, e.g. @@ -8,9 +8,15 @@ is of actual JavaScript `Error` type, you should use `JSPromise: JSValueConvertible, JSValueConstructible -where Success: JSValueConstructible, Failure: JSValueConstructible { +public final class JSPromise: JSValueConvertible, JSValueConstructible { /// The underlying JavaScript `Promise` object. public let jsObject: JSObject @@ -45,12 +51,88 @@ where Success: JSValueConstructible, Failure: JSValueConstructible { /** Schedules the `success` closure to be invoked on sucessful completion of `self`. */ - public func then(success: @escaping (Success) -> ()) { - let closure = JSClosure { - success(Success.construct(from: $0[0])!) + public func then( + success: @escaping () -> (), + file: StaticString = #file, + line: Int = #line + ) { + let closure = JSClosure { _ in success() } + callbacks.append(closure) + _ = jsObject.then!(closure) + } + + /** Schedules the `failure` closure to be invoked on either successful or rejected completion of + `self`. + */ + public func finally(successOrFailure: @escaping () -> ()) -> Self { + let closure = JSClosure { _ in + successOrFailure() } callbacks.append(closure) - jsObject.then.function!(closure) + return .init(unsafe: jsObject.finally!(closure).object!) + } + + deinit { + callbacks.forEach { $0.release() } + } +} + +extension JSPromise where Success == (), Failure == Never { + /** Creates a new `JSPromise` instance from a given `resolver` closure. `resolver` takes + a closure that your code should call to resolve this `JSPromise` instance. + */ + public convenience init(resolver: @escaping (@escaping () -> ()) -> ()) { + let closure = JSClosure { arguments -> () in + // The arguments are always coming from the `Promise` constructor, so we should be + // safe to assume their type here + resolver { arguments[0].function!() } + } + self.init(unsafe: JSObject.global.Promise.function!.new(closure)) + callbacks.append(closure) + } +} + +extension JSPromise where Failure: JSValueConvertible { + /** Creates a new `JSPromise` instance from a given `executor` closure. `resolver` takes + two closure that your code should call to either resolve or reject this `JSPromise` instance. + */ + public convenience init(resolver: @escaping (@escaping (Result) -> ()) -> ()) { + let closure = JSClosure { arguments -> () in + // The arguments are always coming from the `Promise` constructor, so we should be + // safe to assume their type here + let resolve = arguments[0].function! + let reject = arguments[1].function! + + resolver { + switch $0 { + case .success: + resolve() + case let .failure(error): + reject(error.jsValue()) + } + } + } + self.init(unsafe: JSObject.global.Promise.function!.new(closure)) + callbacks.append(closure) + } +} + +extension JSPromise where Success: JSValueConstructible { + /** Schedules the `success` closure to be invoked on sucessful completion of `self`. + */ + public func then( + success: @escaping (Success) -> (), + file: StaticString = #file, + line: Int = #line + ) { + let closure = JSClosure { arguments -> () in + guard let result = Success.construct(from: arguments[0]) else { + fatalError("\(file):\(line): failed to unwrap success value for `then` callback") + } + success(result) + } + callbacks.append(closure) + _ = jsObject.then!(closure) } /** Returns a new promise created from chaining the current `self` promise with the `success` @@ -58,13 +140,18 @@ where Success: JSValueConstructible, Failure: JSValueConstructible { `Success` type equal to the return type of `success`. */ public func then( - success: @escaping (Success) -> ResultType + success: @escaping (Success) -> ResultType, + file: StaticString = #file, + line: Int = #line ) -> JSPromise { - let closure = JSClosure { - success(Success.construct(from: $0[0])!).jsValue() + let closure = JSClosure { arguments -> JSValue in + guard let result = Success.construct(from: arguments[0]) else { + fatalError("\(file):\(line): failed to unwrap success value for `then` callback") + } + return success(result).jsValue() } callbacks.append(closure) - return .init(unsafe: jsObject.then.function!(closure).object!) + return .init(unsafe: jsObject.then!(closure).object!) } /** Returns a new promise created from chaining the current `self` promise with the `success` @@ -72,37 +159,56 @@ where Success: JSValueConstructible, Failure: JSValueConstructible { equal to the return type of `success`. */ public func then( - success: @escaping (Success) -> JSPromise + success: @escaping (Success) -> JSPromise, + file: StaticString = #file, + line: Int = #line ) -> JSPromise { - let closure = JSClosure { - success(Success.construct(from: $0[0])!).jsValue() + let closure = JSClosure { arguments -> JSValue in + guard let result = Success.construct(from: arguments[0]) else { + fatalError("\(file):\(line): failed to unwrap success value for `then` callback") + } + return success(result).jsValue() } callbacks.append(closure) - return .init(unsafe: jsObject.then.function!(closure).object!) + return .init(unsafe: jsObject.then!(closure).object!) } +} - /** Schedules the `failure` closure to be invoked on rejected completion of `self`. +extension JSPromise where Failure: JSValueConstructible { + /** Returns a new promise created from chaining the current `self` promise with the `failure` + closure invoked on rejected completion of `self`. The returned promise will have a new `Success` + type equal to the return type of the callback, while the `Failure` type becomes `Never`. */ - public func `catch`(failure: @escaping (Failure) -> ()) { - let closure = JSClosure { - failure(Failure.construct(from: $0[0])!) + public func `catch`( + failure: @escaping (Failure) -> ResultSuccess, + file: StaticString = #file, + line: Int = #line + ) -> JSPromise { + let closure = JSClosure { arguments -> JSValue in + guard let error = Failure.construct(from: arguments[0]) else { + fatalError("\(file):\(line): failed to unwrap error value for `catch` callback") + } + return failure(error).jsValue() } callbacks.append(closure) - jsObject.then.function!(JSValue.undefined, closure) + return .init(unsafe: jsObject.then!(JSValue.undefined, closure).object!) } - /** Returns a new promise created from chaining the current `self` promise with the `failure` - closure invoked on rejected completion of `self`. The returned promise will have a new `Success` - type equal to the return type of the callback. + /** Schedules the `failure` closure to be invoked on rejected completion of `self`. */ - public func `catch`( - failure: @escaping (Failure) -> ResultSuccess - ) -> JSPromise { - let closure = JSClosure { - failure(Failure.construct(from: $0[0])!).jsValue() + public func `catch`( + failure: @escaping (Failure) -> (), + file: StaticString = #file, + line: Int = #line + ) { + let closure = JSClosure { arguments -> () in + guard let error = Failure.construct(from: arguments[0]) else { + fatalError("\(file):\(line): failed to unwrap error value for `catch` callback") + } + failure(error) } callbacks.append(closure) - return .init(unsafe: jsObject.then.function!(JSValue.undefined, closure).object!) + _ = jsObject.then!(JSValue.undefined, closure) } /** Returns a new promise created from chaining the current `self` promise with the `failure` @@ -110,27 +216,42 @@ where Success: JSValueConstructible, Failure: JSValueConstructible { equal to the return type of `success`. */ public func `catch`( - failure: @escaping (Failure) -> JSPromise + failure: @escaping (Failure) -> JSPromise, + file: StaticString = #file, + line: Int = #line ) -> JSPromise { - let closure = JSClosure { - failure(Failure.construct(from: $0[0])!).jsValue() + let closure = JSClosure { arguments -> JSValue in + guard let error = Failure.construct(from: arguments[0]) else { + fatalError("\(file):\(line): failed to unwrap error value for `catch` callback") + } + return failure(error).jsValue() } callbacks.append(closure) - return .init(unsafe: jsObject.then.function!(JSValue.undefined, closure).object!) + return .init(unsafe: jsObject.then!(JSValue.undefined, closure).object!) } +} - /** Schedules the `failure` closure to be invoked on either successful or rejected completion of - `self`. +extension JSPromise where Success: JSValueConvertible, Failure: JSError { + /** Creates a new `JSPromise` instance from a given `executor` closure. `executor` takes + a closure that your code should call to either resolve or reject this `JSPromise` instance. */ - public func finally(successOrFailure: @escaping () -> ()) -> Self { - let closure = JSClosure { _ in - successOrFailure() + public convenience init(resolver: @escaping (@escaping (Result) -> ()) -> ()) { + let closure = JSClosure { arguments -> () in + // The arguments are always coming from the `Promise` constructor, so we should be + // safe to assume their type here + let resolve = arguments[0].function! + let reject = arguments[1].function! + + resolver { + switch $0 { + case let .success(success): + resolve(success.jsValue()) + case let .failure(error): + reject(error.jsValue()) + } + } } + self.init(unsafe: JSObject.global.Promise.function!.new(closure)) callbacks.append(closure) - return .init(unsafe: jsObject.finally.function!(closure).object!) - } - - deinit { - callbacks.forEach { $0.release() } } } diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index 1217d699..fc03cfe0 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -8,7 +8,7 @@ public protocol TypedArrayElement: JSValueConvertible, JSValueConstructible { static var typedArrayClass: JSFunction { get } } -/// A wrapper around [the JavaScript TypedArray class](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) +/// A wrapper around [the JavaScript TypedArray class](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) /// that exposes its properties in a type-safe and Swifty way. public class JSTypedArray: JSValueConvertible, ExpressibleByArrayLiteral where Element: TypedArrayElement { let ref: JSObject From bde2e8944441cf3fb75c8586203204c54d27d15f Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 17 Sep 2020 11:26:44 +0100 Subject: [PATCH 4/7] Refine wording in test comment --- IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 5da72a5e..77d5bfd0 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -479,8 +479,7 @@ try test("Promise") { } promise!.then { - // verify that at least `timeoutMilliseconds` passed since the `timer` - // timer started + // verify that at least `timeoutMilliseconds` passed since the timer started try! expectEqual(start + timeoutMilliseconds <= JSDate().valueOf(), true) } } From 1b025e8f04e33694a5b5c12f8806006b24a71dd2 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 17 Sep 2020 11:29:55 +0100 Subject: [PATCH 5/7] Reorder JSPromise.init overloads to group logically --- .../BasicObjects/JSPromise.swift | 56 +++++++++---------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift index 8c0968ba..c1895873 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift @@ -51,11 +51,7 @@ public final class JSPromise: JSValueConvertible, JSValueConst /** Schedules the `success` closure to be invoked on sucessful completion of `self`. */ - public func then( - success: @escaping () -> (), - file: StaticString = #file, - line: Int = #line - ) { + public func then(success: @escaping () -> ()) { let closure = JSClosure { _ in success() } callbacks.append(closure) _ = jsObject.then!(closure) @@ -117,6 +113,31 @@ extension JSPromise where Failure: JSValueConvertible { } } +extension JSPromise where Success: JSValueConvertible, Failure: JSError { + /** Creates a new `JSPromise` instance from a given `executor` closure. `executor` takes + a closure that your code should call to either resolve or reject this `JSPromise` instance. + */ + public convenience init(resolver: @escaping (@escaping (Result) -> ()) -> ()) { + let closure = JSClosure { arguments -> () in + // The arguments are always coming from the `Promise` constructor, so we should be + // safe to assume their type here + let resolve = arguments[0].function! + let reject = arguments[1].function! + + resolver { + switch $0 { + case let .success(success): + resolve(success.jsValue()) + case let .failure(error): + reject(error.jsValue()) + } + } + } + self.init(unsafe: JSObject.global.Promise.function!.new(closure)) + callbacks.append(closure) + } +} + extension JSPromise where Success: JSValueConstructible { /** Schedules the `success` closure to be invoked on sucessful completion of `self`. */ @@ -230,28 +251,3 @@ extension JSPromise where Failure: JSValueConstructible { return .init(unsafe: jsObject.then!(JSValue.undefined, closure).object!) } } - -extension JSPromise where Success: JSValueConvertible, Failure: JSError { - /** Creates a new `JSPromise` instance from a given `executor` closure. `executor` takes - a closure that your code should call to either resolve or reject this `JSPromise` instance. - */ - public convenience init(resolver: @escaping (@escaping (Result) -> ()) -> ()) { - let closure = JSClosure { arguments -> () in - // The arguments are always coming from the `Promise` constructor, so we should be - // safe to assume their type here - let resolve = arguments[0].function! - let reject = arguments[1].function! - - resolver { - switch $0 { - case let .success(success): - resolve(success.jsValue()) - case let .failure(error): - reject(error.jsValue()) - } - } - } - self.init(unsafe: JSObject.global.Promise.function!.new(closure)) - callbacks.append(closure) - } -} From 121e1c2b1f4ef4f023f6326adfe5e12fa4643ec1 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 17 Sep 2020 11:30:58 +0100 Subject: [PATCH 6/7] Rename `executor` to `resolver` in `JSPromise` --- Sources/JavaScriptKit/BasicObjects/JSPromise.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift index c1895873..a98945cd 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift @@ -89,7 +89,7 @@ extension JSPromise where Success == (), Failure == Never { } extension JSPromise where Failure: JSValueConvertible { - /** Creates a new `JSPromise` instance from a given `executor` closure. `resolver` takes + /** Creates a new `JSPromise` instance from a given `resolver` closure. `resolver` takes two closure that your code should call to either resolve or reject this `JSPromise` instance. */ public convenience init(resolver: @escaping (@escaping (Result) -> ()) -> ()) { @@ -114,7 +114,7 @@ extension JSPromise where Failure: JSValueConvertible { } extension JSPromise where Success: JSValueConvertible, Failure: JSError { - /** Creates a new `JSPromise` instance from a given `executor` closure. `executor` takes + /** Creates a new `JSPromise` instance from a given `resolver` closure. `resolver` takes a closure that your code should call to either resolve or reject this `JSPromise` instance. */ public convenience init(resolver: @escaping (@escaping (Result) -> ()) -> ()) { From 5dc13b9ccb9001ab2c47aba26284febe8ba90498 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Thu, 17 Sep 2020 11:33:21 +0100 Subject: [PATCH 7/7] Remove unused Never: JSValueConstructible conformance --- Sources/JavaScriptKit/JSValueConstructible.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Sources/JavaScriptKit/JSValueConstructible.swift b/Sources/JavaScriptKit/JSValueConstructible.swift index 6731891c..59a8c63d 100644 --- a/Sources/JavaScriptKit/JSValueConstructible.swift +++ b/Sources/JavaScriptKit/JSValueConstructible.swift @@ -91,9 +91,3 @@ extension UInt64: JSValueConstructible { value.number.map(Self.init) } } - -extension Never: JSValueConstructible { - public static func construct(from value: JSValue) -> Never? { - fatalError() - } -} \ No newline at end of file