Skip to content

Commit 3602b10

Browse files
committed
Add catchToEffect() with mapping function (#705)
Also add a couple of tests to testEraseToEffectWithError
1 parent 24382d3 commit 3602b10

File tree

2 files changed

+65
-16
lines changed

2 files changed

+65
-16
lines changed

Sources/ComposableArchitecture/Effect.swift

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,14 @@ extension Effect {
8787
/// Note that you can only deliver a single value to the `callback`. If you send more they will be
8888
/// discarded:
8989
///
90-
/// ```swift
90+
/// ```swift
9191
/// Effect<Int, Never>.future { callback in
9292
/// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
9393
/// callback(.success(42))
9494
/// callback(.success(1729)) // Will not be emitted by the effect
9595
/// }
96-
/// }
97-
/// ```
96+
/// }
97+
/// ```
9898
///
9999
/// - Parameter attemptToFulfill: A closure that takes a `callback` as an argument which can be
100100
/// used to feed it `Result<Output, Failure>` values.
@@ -114,36 +114,63 @@ extension Effect {
114114
}
115115
}
116116

117-
/// Turns any publisher into an ``Effect`` that cannot fail by wrapping its output and failure in
117+
/// Turns any `SignalProducer` into an ``Effect`` that cannot fail by wrapping its output and failure in
118118
/// a result.
119119
///
120120
/// This can be useful when you are working with a failing API but want to deliver its data to an
121121
/// action that handles both success and failure.
122122
///
123-
/// ```swift
124-
/// case .buttonTapped:
125-
/// return fetchUser(id: 1)
126-
/// .catchToEffect()
127-
/// .map(ProfileAction.userResponse)
128-
/// ```
123+
/// ```swift
124+
/// case .buttonTapped:
125+
/// return fetchUser(id: 1)
126+
/// .catchToEffect()
127+
/// .map(ProfileAction.userResponse)
128+
/// ```
129129
///
130130
/// - Returns: An effect that wraps `self`.
131131
public func catchToEffect() -> Effect<Result<Value, Error>, Never> {
132132
self.map(Result<Value, Error>.success)
133133
.flatMapError { Effect<Result<Value, Error>, Never>(value: Result.failure($0)) }
134134
}
135135

136-
/// Turns any `SignalProducer` into an ``Effect`` for any output and failure type by ignoring all output
136+
/// Turns any `SignalProducer` into an ``Effect`` that cannot fail by wrapping its output and failure into
137+
/// result and then applying passed in function to it.
138+
///
139+
/// This is a convenience operator for writing `catchToEffect()` followed by a `map()` .
140+
///
141+
/// ```swift
142+
/// case .buttonTapped:
143+
/// return fetchUser(id: 1)
144+
/// .catchToEffect {
145+
/// switch $0 {
146+
/// case let .success(response):
147+
/// return ProfileAction.updatedUser(response)
148+
/// case let .failure(error):
149+
/// return ProfileAction.failedUserUpdate(error)
150+
/// }
151+
/// }
152+
/// ```
153+
///
154+
/// - Parameters:
155+
/// - f: A mapping function that converts `Result<Output,Failure>` to another type.
156+
/// - Returns: An effect that wraps `self`.
157+
public func catchToEffect<T>(_ f: @escaping (Result<Value, Error>) -> T) -> Effect<T,Never> {
158+
self
159+
.catchToEffect()
160+
.map(f)
161+
}
162+
163+
/// Turns any publisher into an ``Effect`` for any output and failure type by ignoring all output
137164
/// and any failure.
138165
///
139166
/// This is useful for times you want to fire off an effect but don't want to feed any data back
140167
/// into the system. It can automatically promote an effect to your reducer's domain.
141168
///
142-
/// ```swift
143-
/// case .buttonTapped:
144-
/// return analyticsClient.track("Button Tapped")
145-
/// .fireAndForget()
146-
/// ```
169+
/// ```swift
170+
/// case .buttonTapped:
171+
/// return analyticsClient.track("Button Tapped")
172+
/// .fireAndForget()
173+
/// ```
147174
///
148175
/// - Parameters:
149176
/// - outputType: An output type.

Tests/ComposableArchitectureTests/EffectTests.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,28 @@ final class EffectTests: XCTestCase {
1717

1818
SignalProducer<Int, Never>(result: .success(42))
1919
.startWithResult { XCTAssertEqual($0, .success(42)) }
20+
21+
SignalProducer<Int, Never>(result: .success(42))
22+
.catchToEffect {
23+
switch $0 {
24+
case let .success(val):
25+
return val
26+
case .failure:
27+
return -1
28+
}
29+
}
30+
.startWithValues { XCTAssertEqual($0, 42) }
31+
32+
SignalProducer<Int, Error>(result: .failure(Error()))
33+
.catchToEffect {
34+
switch $0 {
35+
case let .success(val):
36+
return val
37+
case .failure:
38+
return -1
39+
}
40+
}
41+
.startWithValues { XCTAssertEqual($0, -1) }
2042
}
2143

2244
func testConcatenate() {

0 commit comments

Comments
 (0)