Skip to content

Commit

Permalink
Refine convenience constructor in Swift
Browse files Browse the repository at this point in the history
  • Loading branch information
shoumikhin committed May 10, 2018
1 parent 08dde88 commit e10eaeb
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 24 deletions.
10 changes: 3 additions & 7 deletions Promises.playground/Contents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,14 @@ enum PlaygroundError: Error {
}

func reverse(string: String) -> Promise<String> {
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<Int> {
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 {
Expand Down
2 changes: 1 addition & 1 deletion PromisesObjC.podspec
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
2 changes: 1 addition & 1 deletion PromisesSwift.podspec
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
27 changes: 24 additions & 3 deletions Sources/Promises/Promise+Do.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
import Foundation

public extension Promise {
public typealias Do = () throws -> Value
public typealias Do<Value> = () 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<Value>(on queue: DispatchQueue = .promises, _ work: @escaping Do<Value>) {
let objCPromise = ObjCPromise<AnyObject>.__onQueue(queue) {
do {
let resolution = try work()
Expand All @@ -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<Value>(
on queue: DispatchQueue = .promises,
_ work: @escaping Do<Promise<Value>>
) {
let objCPromise = ObjCPromise<AnyObject>.__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)
}
}
2 changes: 1 addition & 1 deletion Tests/PromisesTests/Promise+AwaitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import XCTest
class PromiseAwaitTests: XCTestCase {
func testPromiseAwaitFulfill() {
// Act.
let promise = Promise<Int>(on: .global()) {
let promise = Promise<Int>(on: .global()) { () -> Int in
let minusFive = try await(Harness.negate(5))
XCTAssertEqual(minusFive, -5)
let twentyFive = try await(Harness.multiply(minusFive, minusFive))
Expand Down
10 changes: 10 additions & 0 deletions Tests/PromisesTests/Promise+DoTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ class PromiseDoTests: XCTestCase {
XCTAssertNil(promise.error)
}

func testPromiseDoReturnPromise() {
// Arrange & Act.
let promise = Promise<Int> { Promise(42) }

// Assert.
XCTAssert(waitForPromises(timeout: 10))
XCTAssertEqual(promise.value, 42)
XCTAssertNil(promise.error)
}

func testPromiseDoReject() {
// Arrange & Act.
let promise = Promise<Int> { throw Test.Error.code42 }
Expand Down
2 changes: 1 addition & 1 deletion Tests/PromisesTests/Promise+ThenTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class PromiseThenTests: XCTestCase {
var count = 0

// Act.
let promise = Promise<Int> {
let promise = Promise { () -> Int in
let number = 42
return number
}.then { value in
Expand Down
27 changes: 17 additions & 10 deletions g3doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
],
// ...
)
Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -579,7 +579,7 @@ Swift:
```swift
let promise = Promise<String> { fulfill, reject in
// Called asynchronously on the main queue by default.
// Called asynchronously on the default queue.
if success {
fulfill("Hello world.")
} else {
Expand All @@ -593,7 +593,7 @@ Objective-C:
```objectivec
FBLPromise<NSString *> *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 {
Expand All @@ -604,14 +604,15 @@ FBLPromise<NSString *> *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<String> {
// 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"
}
Expand All @@ -621,11 +622,17 @@ Objective-C:

```objectivec
FBLPromise<NSString *> *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
Expand Down

0 comments on commit e10eaeb

Please sign in to comment.