From e10eaeb95488016c06f454ecd00b33b908270c91 Mon Sep 17 00:00:00 2001 From: Anthony Shoumikhin Date: Thu, 10 May 2018 11:12:20 -0700 Subject: [PATCH] Refine convenience constructor in Swift --- Promises.playground/Contents.swift | 10 +++----- PromisesObjC.podspec | 2 +- PromisesSwift.podspec | 2 +- Sources/Promises/Promise+Do.swift | 27 +++++++++++++++++--- Tests/PromisesTests/Promise+AwaitTests.swift | 2 +- Tests/PromisesTests/Promise+DoTests.swift | 10 ++++++++ Tests/PromisesTests/Promise+ThenTests.swift | 2 +- g3doc/index.md | 27 ++++++++++++-------- 8 files changed, 58 insertions(+), 24 deletions(-) diff --git a/Promises.playground/Contents.swift b/Promises.playground/Contents.swift index 271da9b..83e103c 100644 --- a/Promises.playground/Contents.swift +++ b/Promises.playground/Contents.swift @@ -23,18 +23,14 @@ enum PlaygroundError: Error { } func reverse(string: String) -> Promise { - return Promise { - Thread.sleep(forTimeInterval: 0.1) - return String(string.reversed()) - } + return Promise(String(string.reversed())).delay(0.1) } func number(from string: String) -> Promise { - return Promise { - Thread.sleep(forTimeInterval: 0.1) + return Promise { () -> Int in guard let number = Int(string) else { throw PlaygroundError.invalidNumber } return number - } + }.delay(0.1) } func square(number: Int) -> Int { diff --git a/PromisesObjC.podspec b/PromisesObjC.podspec index 2c9ded1..cf3ca07 100644 --- a/PromisesObjC.podspec +++ b/PromisesObjC.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'PromisesObjC' - s.version = '1.1.1' + s.version = '1.2' s.authors = 'Google Inc.' s.license = { :type => 'Apache', :file => 'LICENSE' } s.homepage = 'https://github.com/google/promises' diff --git a/PromisesSwift.podspec b/PromisesSwift.podspec index c4f13e4..f83bc7e 100644 --- a/PromisesSwift.podspec +++ b/PromisesSwift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'PromisesSwift' - s.version = '1.1.1' + s.version = '1.2' s.authors = 'Google Inc.' s.license = { :type => 'Apache', :file => 'LICENSE' } s.homepage = 'https://github.com/google/promises' diff --git a/Sources/Promises/Promise+Do.swift b/Sources/Promises/Promise+Do.swift index 4f54ef7..624d884 100644 --- a/Sources/Promises/Promise+Do.swift +++ b/Sources/Promises/Promise+Do.swift @@ -15,14 +15,14 @@ import Foundation public extension Promise { - public typealias Do = () throws -> Value + public typealias Do = () throws -> Value /// Creates a pending promise to be resolved with the return value of `work` block which is /// executed asynchronously on the given `queue`. /// - parameters: /// - queue: A queue to invoke the `work` block on. - /// - work: A block to perform any operations needed to resolve the promise. - public convenience init(on queue: DispatchQueue = .promises, _ work: @escaping Do) { + /// - work: A block that returns a value used to resolve the new promise. + public convenience init(on queue: DispatchQueue = .promises, _ work: @escaping Do) { let objCPromise = ObjCPromise.__onQueue(queue) { do { let resolution = try work() @@ -35,4 +35,25 @@ public extension Promise { // Keep Swift wrapper alive for chained promise until `ObjCPromise` counterpart is resolved. objCPromise.__pendingObjects?.add(self) } + + /// Creates a pending promise to be resolved with the same resolution as the promise returned from + /// `work` block which is executed asynchronously on the given `queue`. + /// - parameters: + /// - queue: A queue to invoke the `work` block on. + /// - work: A block that returns a promise used to resolve the new promise. + public convenience init( + on queue: DispatchQueue = .promises, + _ work: @escaping Do> + ) { + let objCPromise = ObjCPromise.__onQueue(queue) { + do { + return try work().objCPromise + } catch let error { + return error as NSError + } + } + self.init(objCPromise) + // Keep Swift wrapper alive for chained promise until `ObjCPromise` counterpart is resolved. + objCPromise.__pendingObjects?.add(self) + } } diff --git a/Tests/PromisesTests/Promise+AwaitTests.swift b/Tests/PromisesTests/Promise+AwaitTests.swift index 032f2a3..377e398 100644 --- a/Tests/PromisesTests/Promise+AwaitTests.swift +++ b/Tests/PromisesTests/Promise+AwaitTests.swift @@ -19,7 +19,7 @@ import XCTest class PromiseAwaitTests: XCTestCase { func testPromiseAwaitFulfill() { // Act. - let promise = Promise(on: .global()) { + let promise = Promise(on: .global()) { () -> Int in let minusFive = try await(Harness.negate(5)) XCTAssertEqual(minusFive, -5) let twentyFive = try await(Harness.multiply(minusFive, minusFive)) diff --git a/Tests/PromisesTests/Promise+DoTests.swift b/Tests/PromisesTests/Promise+DoTests.swift index 828e73d..ac34b36 100644 --- a/Tests/PromisesTests/Promise+DoTests.swift +++ b/Tests/PromisesTests/Promise+DoTests.swift @@ -27,6 +27,16 @@ class PromiseDoTests: XCTestCase { XCTAssertNil(promise.error) } + func testPromiseDoReturnPromise() { + // Arrange & Act. + let promise = Promise { Promise(42) } + + // Assert. + XCTAssert(waitForPromises(timeout: 10)) + XCTAssertEqual(promise.value, 42) + XCTAssertNil(promise.error) + } + func testPromiseDoReject() { // Arrange & Act. let promise = Promise { throw Test.Error.code42 } diff --git a/Tests/PromisesTests/Promise+ThenTests.swift b/Tests/PromisesTests/Promise+ThenTests.swift index eeb1458..cb6f0c8 100644 --- a/Tests/PromisesTests/Promise+ThenTests.swift +++ b/Tests/PromisesTests/Promise+ThenTests.swift @@ -80,7 +80,7 @@ class PromiseThenTests: XCTestCase { var count = 0 // Act. - let promise = Promise { + let promise = Promise { () -> Int in let number = 42 return number }.then { value in diff --git a/g3doc/index.md b/g3doc/index.md index 081d384..769ca55 100644 --- a/g3doc/index.md +++ b/g3doc/index.md @@ -414,7 +414,7 @@ In your `Package.swift` file, add `Promises` dependency to corresponding targets let package = Package( // ... dependencies: [ - .package(url: "https://github.com/google/promises.git", from: "1.1.1"), + .package(url: "https://github.com/google/promises.git", from: "1.2"), ], // ... ) @@ -443,13 +443,13 @@ Or, the module, if `CLANG_ENABLE_MODULES = YES`: To use `Promises` for both Swift and Objective-C, add the following to your `Podfile`: ```ruby -pod 'PromisesSwift', '~> 1.1' +pod 'PromisesSwift', '~> 1.2' ``` To use `Promises` for Objective-C only, add the following to your `Podfile`: ```ruby -pod 'PromisesObjC', '~> 1.1' +pod 'PromisesObjC', '~> 1.2' ``` Also, don't forget to `use_frameworks!` in your target. Then, run `pod install`. @@ -579,7 +579,7 @@ Swift: ```swift let promise = Promise { fulfill, reject in - // Called asynchronously on the main queue by default. + // Called asynchronously on the default queue. if success { fulfill("Hello world.") } else { @@ -593,7 +593,7 @@ Objective-C: ```objectivec FBLPromise *promise = [FBLPromise async:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) { - // Called asynchronously on the main queue by default. + // Called asynchronously on the default queue. if (success) { fulfill(@"Hello world."); } else { @@ -604,14 +604,15 @@ FBLPromise *promise = [FBLPromise async:^(FBLPromiseFulfillBlock ful ##### Do -We can make the above examples even more concise with `do` operator if the block -of code inside a promise doesn't require async fulfillment: +We can make the above examples even more concise with the `do` operator +(which is implemented as a convenience constructor in Swift) if the promise +work block doesn't require async fulfillment: Swift: ```swift -let promise = Promise { - // Called asynchronously on the main queue by default. +let promise = Promise { () -> String in + // Called asynchronously on the default queue. guard success else { throw someError } return "Hello world" } @@ -621,11 +622,17 @@ Objective-C: ```objectivec FBLPromise *promise = [FBLPromise do:^id { - // Called asynchronously on the main queue by default. + // Called asynchronously on the default queue. return success ? @"Hello world" : someError; }]; ``` +Note: In Swift the convenience constructor accepting a work block is overloaded +and can return either a value or another promise, which is eventually used to +resolve the newly created promise. In Objective-C the `do` operator return value +is not strongly typed, so you can return a value, another promise or an error +and expect the correct behavior. + ##### Pending And in case you need a pending promise without any async block of work