From b9a4ba5a9cd5c38821d1a37d4a34bd15d52ffd46 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Thu, 23 Sep 2021 20:06:52 +0200 Subject: [PATCH 1/3] race_fulfilled objc --- Sources/fwd.h | 6 +++++ Sources/race.m | 26 ++++++++++++++++++++++ Sources/race.swift | 1 - Tests/CoreObjC/AnyPromiseTests.m | 38 ++++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) 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..3ea177031 100644 --- a/Sources/race.m +++ b/Sources/race.m @@ -7,3 +7,29 @@ } }]; } + +/** + 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); + } + }]; + } + }]; +} diff --git a/Sources/race.swift b/Sources/race.swift index 76ae96d06..8878bdec0 100644 --- a/Sources/race.swift +++ b/Sources/race.swift @@ -91,7 +91,6 @@ public func race(fulfilled thenables: [U]) -> Promise { } case .fulfilled(let value): guard rp.isPending else { return } - countdown = 0 rp.box.seal(.fulfilled(value)) } } 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]; }); From 5df33e94f729ad9730987175573251ff4e47acd7 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Thu, 23 Sep 2021 21:01:59 +0200 Subject: [PATCH 2/3] fix build errors --- Sources/race.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/race.m b/Sources/race.m index 3ea177031..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) { @@ -33,3 +38,5 @@ } }]; } + +#pragma GCC diagnostic pop From 6c4b01ba6c23670040f7123f7ff2a115d17e41ee Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Fri, 24 Sep 2021 17:15:45 +0200 Subject: [PATCH 3/3] reverted --- Sources/race.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/race.swift b/Sources/race.swift index 8878bdec0..76ae96d06 100644 --- a/Sources/race.swift +++ b/Sources/race.swift @@ -91,6 +91,7 @@ public func race(fulfilled thenables: [U]) -> Promise { } case .fulfilled(let value): guard rp.isPending else { return } + countdown = 0 rp.box.seal(.fulfilled(value)) } }