diff --git a/Sources/fwd.h b/Sources/fwd.h index 480d1480e..8c049f769 100644 --- a/Sources/fwd.h +++ b/Sources/fwd.h @@ -13,6 +13,7 @@ extern NSString * __nonnull const PMKErrorDomain; #define PMKOperationFailed 8l #define PMKTaskError 9l #define PMKJoinError 10l +#define PMKNoWinnerError 11l #ifdef __cplusplus @@ -160,6 +161,11 @@ extern AnyPromise * __nonnull dispatch_promise_on(dispatch_queue_t __nonnull que */ extern AnyPromise * __nonnull PMKRace(NSArray * __nonnull promises) NS_REFINED_FOR_SWIFT; +/** + Returns a new promise that resolves with the value of the first fulfilled promise in the provided array of promises. +*/ +extern AnyPromise * __nonnull PMKRaceFulfilled(NSArray * __nonnull promises) NS_REFINED_FOR_SWIFT; + #ifdef __cplusplus } // Extern C #endif diff --git a/Sources/race.m b/Sources/race.m index cab38ec19..accf80dc9 100644 --- a/Sources/race.m +++ b/Sources/race.m @@ -1,4 +1,9 @@ #import "AnyPromise+Private.h" +#import + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +// ^^ OSAtomicDecrement32 is deprecated on watchOS AnyPromise *PMKRace(NSArray *promises) { return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) { @@ -7,3 +12,31 @@ } }]; } + +/** + Waits for one promise to fulfill + + @note If there are no fulfilled promises, the returned promise is rejected with `PMKNoWinnerError`. + @param promises The promises to fulfill. + @return The promise that was fulfilled first. +*/ +AnyPromise *PMKRaceFulfilled(NSArray *promises) { + __block int32_t countdown = (int32_t)[promises count]; + + return [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) { + for (__strong AnyPromise* promise in promises) { + [promise __pipe:^(id value) { + if (IsError(value)) { + if (OSAtomicDecrement32(&countdown) == 0) { + id err = [NSError errorWithDomain:PMKErrorDomain code:PMKNoWinnerError userInfo:@{NSLocalizedDescriptionKey: @"PMKRaceFulfilled(nil)"}]; + resolve(err); + } + } else { + resolve(value); + } + }]; + } + }]; +} + +#pragma GCC diagnostic pop diff --git a/Tests/CoreObjC/AnyPromiseTests.m b/Tests/CoreObjC/AnyPromiseTests.m index 0d3003ccf..0e6fe2993 100644 --- a/Tests/CoreObjC/AnyPromiseTests.m +++ b/Tests/CoreObjC/AnyPromiseTests.m @@ -799,6 +799,44 @@ - (void)test_race { [self waitForExpectationsWithTimeout:1 handler:nil]; } +- (void)test_race_fullfilled { + id ex = [self expectationWithDescription:@""]; + NSArray* promises = @[ + PMKAfter(1).then(^{ return dummyWithCode(1); }), + PMKAfter(2).then(^{ return dummyWithCode(2); }), + PMKAfter(5).then(^{ return @1; }), + PMKAfter(4).then(^{ return @2; }), + PMKAfter(3).then(^{ return dummyWithCode(3); }) + ]; + PMKRaceFulfilled(promises).then(^(id obj){ + XCTAssertEqual(2, [obj integerValue]); + [ex fulfill]; + }).catch(^{ + XCTFail(); + [ex fulfill]; + }); + [self waitForExpectationsWithTimeout:10 handler:nil]; +} + +- (void)test_race_fullfilled_with_no_winner { + id ex = [self expectationWithDescription:@""]; + NSArray* promises = @[ + PMKAfter(1).then(^{ return dummyWithCode(1); }), + PMKAfter(2).then(^{ return dummyWithCode(2); }), + PMKAfter(3).then(^{ return dummyWithCode(3); }) + ]; + PMKRaceFulfilled(promises).then(^(id obj){ + XCTFail(); + [ex fulfill]; + }).catch(^(NSError *e){ + XCTAssertEqual(e.domain, PMKErrorDomain); + XCTAssertEqual(e.code, PMKNoWinnerError); + XCTAssertEqualObjects(e.userInfo[NSLocalizedDescriptionKey], @"PMKRaceFulfilled(nil)"); + [ex fulfill]; + }); + [self waitForExpectationsWithTimeout:10 handler:nil]; +} + - (void)testInBackground { id ex = [self expectationWithDescription:@""]; PMKAfter(0.1).thenInBackground(^{ [ex fulfill]; });