From 178b6cf42e360d5933f079d8cbaefa0aed2d2256 Mon Sep 17 00:00:00 2001 From: Lily Ballard Date: Thu, 21 May 2020 22:20:57 -0700 Subject: [PATCH] Change timeout's default context to .nowOr(.auto) This change means the returned promise will already be resolved if the receiver was already resolved, otherwise it behaves the same. Fixes #50. --- README.md | 3 +++ Sources/ObjC/TWLUtilities.h | 10 +++++++--- Sources/ObjC/TWLUtilities.m | 2 +- Sources/Utilities.swift | 8 ++++++-- Tests/ObjC/TWLUtilityTests.m | 9 +++++++++ Tests/UtilityTests.swift | 7 +++++++ 6 files changed, 33 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a71b8ad..b742e58 100644 --- a/README.md +++ b/README.md @@ -419,9 +419,12 @@ Unless you explicitly state otherwise, any contribution intentionally submitted - Add `Promise.Resolver.hasRequestedCancel` (`TWLResolver.cancelRequested` in Obj-C) that returns `true` if the promise has been requested to cancel or is already cancelled, or `false` if it hasn't been requested to cancel or is fulfilled or rejected. This can be used when a promise initializer takes significant time in a manner not easily interrupted by an `onRequestCancel` handler ([#47][]). +- Change `Promise.timeout`'s default context from `.auto` to `.nowOr(.auto)`. This behaves the same as `.auto` in most cases, except if the receiver has + already been resolved this will cause the returned promise to likewise already be resolved ([#50][]). [#34]: https://github.com/lilyball/Tomorrowland/issues/34 "Add a .mainImmediate context" [#47]: https://github.com/lilyball/Tomorrowland/issues/47 "Add Promise.Resolver.isCancelled property" +[#50]: https://github.com/lilyball/Tomorrowland/issues/50 "Consider changing timeout's default context to .nowOr(.auto)" ### v1.1.1 diff --git a/Sources/ObjC/TWLUtilities.h b/Sources/ObjC/TWLUtilities.h index 6ae5a9d..338fa03 100644 --- a/Sources/ObjC/TWLUtilities.h +++ b/Sources/ObjC/TWLUtilities.h @@ -212,9 +212,13 @@ NS_ASSUME_NONNULL_BEGIN /// the receiver is rejected, the returned promise will be rejected with a \c TWLTimeoutError where /// the \c .rejectedError property contains the underlying promise's rejection value. /// -/// \note This method assumes a context of .automatic, which evaluates to \c .main when -/// invoked on the main thread, otherwise .defaultQoS. If you want to specify the context, -/// use \c -timeoutOnContext:withDelay: instead. +/// \note This method assumes a context of [TWLContext nowOrContext:TWLContext.automatic], +/// where \c .automatic evaluates to \c .main when invoked on the main thread, otherwise +/// .defaultQoS. The usage of \c +nowOrContext: here means that if the receiver has already +/// been resolved when this method is called, the returned promise will likewise already be +/// resolved. If the receiver has not already resolved then this behaves the same as passing +/// .automatic. If you want to specify the context, use \c -timeoutOnContext:withDelay: +/// instead. /// /// \param delay The delay before the returned promise times out. If less than or equal to zero, the /// returned promise will be timed out at once unless the receiver is already resolved. diff --git a/Sources/ObjC/TWLUtilities.m b/Sources/ObjC/TWLUtilities.m index 5a1936c..c6d414f 100644 --- a/Sources/ObjC/TWLUtilities.m +++ b/Sources/ObjC/TWLUtilities.m @@ -153,7 +153,7 @@ - (TWLPromise *)delay:(NSTimeInterval)delay onContext:(TWLContext *)context { } - (TWLPromise *)timeoutWithDelay:(NSTimeInterval)delay { - return [self timeoutOnContext:TWLContext.automatic withDelay:delay]; + return [self timeoutOnContext:[TWLContext nowOrContext:TWLContext.automatic] withDelay:delay]; } - (TWLPromise *)timeoutOnContext:(TWLContext *)context withDelay:(NSTimeInterval)delay { diff --git a/Sources/Utilities.swift b/Sources/Utilities.swift index 5924afe..7132f7d 100644 --- a/Sources/Utilities.swift +++ b/Sources/Utilities.swift @@ -169,7 +169,11 @@ extension Promise { /// `PromiseTimeoutError.rejected(error)`. /// /// - Parameter context: The context to invoke the callback on. If not provided, defaults to - /// `.auto`, which evaluates to `.main` when invoked on the main thread, otherwise `.default`. + /// `.nowOr(.auto)`, which evaluates to `.nowOr(.main)` when invoked on the main thread, + /// otherwise `.nowOr(.default)`. The usage of `.nowOr` here means that if the receiver has + /// already been resolved when this method is called, the returned promise will likewise + /// already be resolved. If the receiver has not already resolved then this behaves the same + /// as passing `.auto`. /// /// If the promise times out, the returned promise will be rejected using the same context. In /// this event, `.immediate` is treated the same as `.auto`. If provided as `.operationQueue` @@ -179,7 +183,7 @@ extension Promise { /// zero, the returned `Promise` will be timed out at once unless the receiver is already /// resolved. /// - Returns: A new `Promise`. - public func timeout(on context: PromiseContext = .auto, delay: TimeInterval) -> Promise> { + public func timeout(on context: PromiseContext = .nowOr(.auto), delay: TimeInterval) -> Promise> { let (promise, resolver) = Promise>.makeWithResolver() let propagateCancelBlock = TWLOneshotBlock(block: { [weak _box] in _box?.propagateCancel() diff --git a/Tests/ObjC/TWLUtilityTests.m b/Tests/ObjC/TWLUtilityTests.m index 9a07c79..49da73a 100644 --- a/Tests/ObjC/TWLUtilityTests.m +++ b/Tests/ObjC/TWLUtilityTests.m @@ -368,6 +368,15 @@ - (void)testTimeoutUsingNowOrContext { [self waitForExpectations:@[expectation] timeout:0.5]; } +- (void)testTimeoutAlreadyResolvedWithDefaultContext { + // timeout on an already-resolved promise when using the default context should return an + // already-resolved promise. + __auto_type promise = [[TWLPromise newFulfilledWithValue:@42] timeoutWithDelay:0.05]; + NSNumber *value; + XCTAssertTrue([promise getValue:&value error:NULL]); + XCTAssertEqualObjects(value, @42); +} + // MARK: - - (void)testInitFulfilledAfter { diff --git a/Tests/UtilityTests.swift b/Tests/UtilityTests.swift index 318fdf5..5f24324 100644 --- a/Tests/UtilityTests.swift +++ b/Tests/UtilityTests.swift @@ -654,6 +654,13 @@ final class UtilityTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } + func testTimeoutAlreadyResolvedWithDefaultContext() { + // timeout on an already-resolved promise when using the default context should return an + // already-resolved promise. + let promise = Promise(fulfilled: 42).timeout(delay: 0.05) + XCTAssertEqual(promise.result, PromiseResult.value(42)) + } + // MARK: - func testInitFulfilledAfter() {