Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Sources/Error.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public enum PMKError: Error {
Also used if all values of this collection failed the test passed to `firstValue(where:)`.
*/
case emptySequence

/// no winner in `race(fulfilled:)`
case noWinner
}

extension PMKError: CustomDebugStringConvertible {
Expand All @@ -51,6 +54,8 @@ extension PMKError: CustomDebugStringConvertible {
return "The asynchronous sequence was cancelled"
case .emptySequence:
return "The first or last element was requested for an empty sequence"
case .noWinner:
return "All thenables passed to race(fulfilled:) were rejected"
}
}
}
Expand Down
45 changes: 45 additions & 0 deletions Sources/race.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Dispatch

@inline(__always)
private func _race<U: Thenable>(_ thenables: [U]) -> Promise<U.T> {
let rp = Promise<U.T>(.pending)
Expand Down Expand Up @@ -55,3 +57,46 @@ public func race<T>(_ guarantees: Guarantee<T>...) -> Guarantee<T> {
}
return rg
}

/**
Waits for one promise to fulfill

race(fulfilled: [promise1, promise2, promise3]).then { winner in
//…
}

- Returns: The promise that was fulfilled first.
- Warning: Skips all rejected promises.
- Remark: If the provided array is empty, the returned promise is rejected with `PMKError.badInput`. If there are no fulfilled promises, the returned promise is rejected with `PMKError.noWinner`.
*/
public func race<U: Thenable>(fulfilled thenables: [U]) -> Promise<U.T> {
var countdown = thenables.count
guard countdown > 0 else {
return Promise(error: PMKError.badInput)
}

let rp = Promise<U.T>(.pending)

let barrier = DispatchQueue(label: "org.promisekit.barrier.race", attributes: .concurrent)

for promise in thenables {
promise.pipe { result in
barrier.sync(flags: .barrier) {
switch result {
case .rejected:
guard rp.isPending else { return }
countdown -= 1
if countdown == 0 {
rp.box.seal(.rejected(PMKError.noWinner))
}
case .fulfilled(let value):
guard rp.isPending else { return }
countdown = 0
rp.box.seal(.fulfilled(value))
}
}
}
}

return rp
}
40 changes: 40 additions & 0 deletions Tests/CorePromise/RaceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,44 @@ class RaceTests: XCTestCase {
}
wait(for: [ex], timeout: 10)
}

func testFulfilled() {
enum Error: Swift.Error { case test1, test2, test3 }
let ex = expectation(description: "")
let promises: [Promise<Int>] = [after(seconds: 1).map { _ in throw Error.test1 }, after(seconds: 2).map { _ in throw Error.test2 }, after(seconds: 5).map { 1 }, after(seconds: 4).map { 2 }, after(seconds: 3).map { _ in throw Error.test3 }]
race(fulfilled: promises).done {
XCTAssertEqual($0, 2)
ex.fulfill()
}.catch { _ in
XCTFail()
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}

func testFulfilledEmptyArray() {
let ex = expectation(description: "")
let empty = [Promise<Int>]()
race(fulfilled: empty).catch {
guard case PMKError.badInput = $0 else { return XCTFail() }
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}

func testFulfilledWithNoWinner() {
enum Error: Swift.Error { case test1, test2 }
let ex = expectation(description: "")
let promises: [Promise<Int>] = [after(seconds: 1).map { _ in throw Error.test1 }, after(seconds: 2).map { _ in throw Error.test2 }]
race(fulfilled: promises).done { _ in
XCTFail()
ex.fulfill()
}.catch {
guard let pmkError = $0 as? PMKError else { return XCTFail() }
guard case .noWinner = pmkError else { return XCTFail() }
guard pmkError.debugDescription == "All thenables passed to race(fulfilled:) were rejected" else { return XCTFail() }
ex.fulfill()
}
wait(for: [ex], timeout: 10)
}
}
3 changes: 3 additions & 0 deletions Tests/CorePromise/XCTestManifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ extension RaceTests {
("test2", test2),
("test2Array", test2Array),
("testEmptyArray", testEmptyArray),
("testFulfilled", testFulfilled),
("testFulfilledEmptyArray", testFulfilledEmptyArray),
("testFulfilledWithNoWinner", testFulfilledWithNoWinner),
]
}

Expand Down