diff --git a/Sources/FoundationExtensions/Combine/Promise+Operators.swift b/Sources/FoundationExtensions/Combine/Promise+Operators.swift index 9d52cf4..7473d3c 100644 --- a/Sources/FoundationExtensions/Combine/Promise+Operators.swift +++ b/Sources/FoundationExtensions/Combine/Promise+Operators.swift @@ -292,6 +292,33 @@ extension Publishers.Promise: Publisher { setFailureType(to: E.self).flatMap(transform) } + @_disfavoredOverload + @inlinable + public func flatMap( + _ transform: @escaping (Output) -> P + ) -> AnyPublisher where P.Output == T, P.Failure == Failure { + func open(_ publisher: some Publisher) -> AnyPublisher { + publisher.map(transform).switchToLatest().eraseToAnyPublisher() + } + return open(publisher) + } + + @_disfavoredOverload + @inlinable + public func flatMap( + _ transform: @escaping (Output) -> P + ) -> AnyPublisher where P.Output == T, P.Failure == Never { + flatMap { transform($0).setFailureType(to: Failure.self) } + } + + @_disfavoredOverload + @inlinable + public func flatMap( + _ transform: @escaping (Output) -> P + ) -> AnyPublisher where P.Output == T, P.Failure == E, Failure == Never { + setFailureType(to: E.self).flatMap(transform) + } + @inlinable public func assertNoFailure( _ prefix: String = "", diff --git a/Sources/FoundationExtensions/Combine/Promise.swift b/Sources/FoundationExtensions/Combine/Promise.swift index 25c0150..6baa876 100644 --- a/Sources/FoundationExtensions/Combine/Promise.swift +++ b/Sources/FoundationExtensions/Combine/Promise.swift @@ -89,7 +89,7 @@ extension Publishers { self.publisher = erased.publisher } else { self.publisher = publisher - .first() + .prefix(1) .map { value in return { Result.success(value) } } .replaceEmpty(with: { fallback() }) .flatMap { output in diff --git a/Tests/FoundationExtensionsTests/Combine/PromiseTests.swift b/Tests/FoundationExtensionsTests/Combine/PromiseTests.swift index 421e1ee..8005bc3 100644 --- a/Tests/FoundationExtensionsTests/Combine/PromiseTests.swift +++ b/Tests/FoundationExtensionsTests/Combine/PromiseTests.swift @@ -1300,8 +1300,54 @@ final class PromiseTests: XCTestCase { XCTAssertEqual(resultExpectationPromise2ReceiveCompletion, .completed) XCTAssertEqual(resultExpectationPromise2ReceiveValue, .timedOut, "Expectation was unexpectedly fulfilled") + let expectationPromise3ReceiveCompletion = XCTestExpectation(description: "promise3 is finished") + let expectationPromise3Fallback = XCTestExpectation(description: "promise3 fallback is not called") + let expectationPromise3ReceiveValue = XCTestExpectation(description: "promise3 received value") + let expectationPromise4ReceiveCompletion = XCTestExpectation(description: "promise4 is finished") + let expectationPromise4Fallback = XCTestExpectation(description: "promise4 fallback is not called") + let expectationPromise4ReceiveValue = XCTestExpectation(description: "promise4 receive value") + + let subject3 = PassthroughSubject() + let promise3 = subject3 + .eraseToAnyPublisher() + .eraseToPromise(onEmpty: { expectationPromise3Fallback.fulfill(); return .success(()) }) + let promise4 = subject3 + .eraseToAnyPublisher() + .eraseToPromise(onEmpty: { expectationPromise4Fallback.fulfill(); return .success(()) }) + + let cancellable3 = promise3 + .sink( + receiveCompletion: { if case .finished = $0 { expectationPromise3ReceiveCompletion.fulfill() } }, + receiveValue: { expectationPromise3ReceiveValue.fulfill() } + ) + + subject3.send(()) + + let cancellable4 = promise4 + .sink( + receiveCompletion: { if case .finished = $0 { expectationPromise4ReceiveCompletion.fulfill() } }, + receiveValue: { expectationPromise4ReceiveValue.fulfill() } + ) + + subject3.send(()) + + let resultExpectationPromise3Fallback = XCTWaiter.wait(for: [expectationPromise3Fallback], timeout: 0.3) + let resultExpectationPromise3ReceiveCompletion = XCTWaiter.wait(for: [expectationPromise3ReceiveCompletion], timeout: 0.1) + let resultExpectationPromise3ReceiveValue = XCTWaiter.wait(for: [expectationPromise3ReceiveValue], timeout: 0.1) + let resultExpectationPromise4Fallback = XCTWaiter.wait(for: [expectationPromise4Fallback], timeout: 0.3) + let resultExpectationPromise4ReceiveCompletion = XCTWaiter.wait(for: [expectationPromise4ReceiveCompletion], timeout: 0.1) + let resultExpectationPromise4ReceiveValue = XCTWaiter.wait(for: [expectationPromise4ReceiveValue], timeout: 0.1) + XCTAssertEqual(resultExpectationPromise3Fallback, .timedOut, "Expectation was unexpectedly fulfilled") + XCTAssertEqual(resultExpectationPromise3ReceiveCompletion, .completed) + XCTAssertEqual(resultExpectationPromise3ReceiveValue, .completed) + XCTAssertEqual(resultExpectationPromise4Fallback, .timedOut, "Expectation was unexpectedly fulfilled") + XCTAssertEqual(resultExpectationPromise4ReceiveCompletion, .completed) + XCTAssertEqual(resultExpectationPromise4ReceiveValue, .completed) + _ = cancellable1 _ = cancellable2 + _ = cancellable3 + _ = cancellable4 } func testPromise_validStatusCode() { @@ -1708,8 +1754,45 @@ final class PromiseTests: XCTestCase { ], timeout: 0.1) XCTAssertEqual(resultExpectations4, .completed) + let expectationPromise5ReceiveCompletion = XCTestExpectation(description: "promise5 completed") + var value5 = "" + + let subject5 = PassthroughSubject() + let promise5 = subject5 + .assertPromise("testPromise_flatMap: ") + .flatMap { _ -> AnyPublisher in + Publishers.Promise(value: "42") + .eraseToAnyPublisher() + } + + let cancellable5 = promise5.sink( + receiveCompletion: { if case .finished = $0 { expectationPromise5ReceiveCompletion.fulfill() } }, + receiveValue: { value5 = $0 } + ) + + subject5.send(42) + + + let resultExpectations5 = XCTWaiter.wait(for: [expectationPromise5ReceiveCompletion], timeout: 0.1) + XCTAssertEqual(resultExpectations5, .completed) + XCTAssertEqual(value5, "42") + + let _: AnyPublisher = PassthroughSubject() + .assertPromise("testPromise_flatMap: ") + .flatMap { _ -> AnyPublisher in + Publishers.Promise(value: "42") + .eraseToAnyPublisher() + } + + let _: AnyPublisher = PassthroughSubject() + .assertPromise("testPromise_flatMap: ") + .flatMap { _ -> AnyPublisher in + Publishers.Promise(value: "42") + .eraseToAnyPublisher() + } + // Update the cancellables array at the end - _ = [cancellable1, cancellable2, cancellable3, cancellable4] + _ = [cancellable1, cancellable2, cancellable3, cancellable4, cancellable5] } }