Skip to content

Commit 382a53d

Browse files
wendyligastephencelis
authored andcommitted
Deferring Side Effect (#550)
* wip * revert unexpected changes * fix typo documentation * fix another typo * improve api * api * add test case * revert * rename to deffered * remove delay keyword entirely * remove unneccessary * Update Sources/ComposableArchitecture/Effects/Deferring.swift Co-authored-by: Stephen Celis <[email protected]>
1 parent 537baa1 commit 382a53d

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Combine
2+
3+
extension Effect {
4+
/// Returns an effect that will be executed after given `dueTime`.
5+
///
6+
/// ```swift
7+
/// case let .textChanged(text):
8+
/// return environment.search(text)
9+
/// .map(Action.searchResponse)
10+
/// .deferred(for: 0.5, scheduler: environment.mainQueue)
11+
/// ```
12+
///
13+
/// - Parameters:
14+
/// - upstream: the effect you want to defer.
15+
/// - dueTime: The duration you want to defer for.
16+
/// - scheduler: The scheduler you want to deliver the defer output to.
17+
/// - options: Scheduler options that customize the effect's delivery of elements.
18+
/// - Returns: An effect that will be executed after `dueTime`
19+
public func deferred<S: Scheduler>(
20+
for dueTime: S.SchedulerTimeType.Stride,
21+
scheduler: S,
22+
options: S.SchedulerOptions? = nil
23+
) -> Effect {
24+
Just(())
25+
.setFailureType(to: Failure.self)
26+
.delay(for: dueTime, scheduler: scheduler, options: options)
27+
.flatMap { self }
28+
.eraseToEffect()
29+
}
30+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import Combine
2+
import ComposableArchitecture
3+
import XCTest
4+
5+
final class EffectDeferredTests: XCTestCase {
6+
var cancellables: Set<AnyCancellable> = []
7+
8+
func testDeferred() {
9+
let scheduler = DispatchQueue.test
10+
var values: [Int] = []
11+
12+
func runDeferredEffect(value: Int) {
13+
Just(value)
14+
.eraseToEffect()
15+
.deferred(for: 1, scheduler: scheduler)
16+
.sink { values.append($0) }
17+
.store(in: &self.cancellables)
18+
}
19+
20+
runDeferredEffect(value: 1)
21+
22+
// Nothing emits right away.
23+
XCTAssertEqual(values, [])
24+
25+
// Waiting half the time also emits nothing
26+
scheduler.advance(by: 0.5)
27+
XCTAssertEqual(values, [])
28+
29+
// Run another deferred effect.
30+
runDeferredEffect(value: 2)
31+
32+
// Waiting half the time emits first deferred effect received.
33+
scheduler.advance(by: 0.5)
34+
XCTAssertEqual(values, [1])
35+
36+
// Run another deferred effect.
37+
runDeferredEffect(value: 3)
38+
39+
// Waiting half the time emits second deferred effect received.
40+
scheduler.advance(by: 0.5)
41+
XCTAssertEqual(values, [1, 2])
42+
43+
// Waiting the rest of the time emits the final effect value.
44+
scheduler.advance(by: 0.5)
45+
XCTAssertEqual(values, [1, 2, 3])
46+
47+
// Running out the scheduler
48+
scheduler.run()
49+
XCTAssertEqual(values, [1, 2, 3])
50+
}
51+
52+
func testDeferredIsLazy() {
53+
let scheduler = DispatchQueue.test
54+
var values: [Int] = []
55+
var effectRuns = 0
56+
57+
func runDeferredEffect(value: Int) {
58+
Deferred { () -> Just<Int> in
59+
effectRuns += 1
60+
return Just(value)
61+
}
62+
.eraseToEffect()
63+
.deferred(for: 1, scheduler: scheduler)
64+
.sink { values.append($0) }
65+
.store(in: &self.cancellables)
66+
}
67+
68+
runDeferredEffect(value: 1)
69+
70+
XCTAssertEqual(values, [])
71+
XCTAssertEqual(effectRuns, 0)
72+
73+
scheduler.advance(by: 0.5)
74+
75+
XCTAssertEqual(values, [])
76+
XCTAssertEqual(effectRuns, 0)
77+
78+
scheduler.advance(by: 0.5)
79+
80+
XCTAssertEqual(values, [1])
81+
XCTAssertEqual(effectRuns, 1)
82+
}
83+
}

0 commit comments

Comments
 (0)