Skip to content

Commit ad6a345

Browse files
stephencelismluisbrown
authored andcommitted
Use publishers in Effect.debounce and Effect.deferred (#1331)
(cherry picked from commit adf7cc8e365238c03ead4c48ba66afd5d2cacadc) # Conflicts: # Sources/ComposableArchitecture/Effects/Debouncing.swift # Sources/ComposableArchitecture/Effects/Deferring.swift
1 parent 3c533b4 commit ad6a345

File tree

4 files changed

+52
-37
lines changed

4 files changed

+52
-37
lines changed

Sources/ComposableArchitecture/Effects/Cancellation.swift

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,36 @@ extension Effect {
166166

167167
/// Execute an operation with a cancellation identifier.
168168
///
169-
/// If the operation is in-flight when `Task.cancel(id:)` is called with the same identifier, the
169+
/// If the operation is in-flight when `Task.cancel(id:)` is called with the same identifier, or
170170
/// operation will be cancelled.
171171
///
172+
/// ```
173+
/// enum CancelID.self {}
174+
///
175+
/// await withTaskCancellation(id: CancelID.self) {
176+
/// // ...
177+
/// }
178+
/// ```
179+
///
180+
/// ### Debouncing tasks
181+
///
182+
/// When paired with a scheduler, this function can be used to debounce a unit of async work by
183+
/// specifying the `cancelInFlight`, which will automatically cancel any in-flight work with the
184+
/// same identifier:
185+
///
186+
/// ```swift
187+
/// enum CancelID {}
188+
///
189+
/// return .task {
190+
/// await withTaskCancellation(id: CancelID.self, cancelInFlight: true) {
191+
/// try await environment.scheduler.sleep(for: .seconds(0.3))
192+
/// return await .debouncedResponse(
193+
/// TaskResult { try await environment.request() }
194+
/// )
195+
/// }
196+
/// }
197+
/// ```
198+
///
172199
/// - Parameters:
173200
/// - id: A unique identifier for the operation.
174201
/// - cancelInFlight: Determines if any in-flight operation with the same identifier should be

Sources/ComposableArchitecture/Effects/Debouncing.swift

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,26 @@ extension Effect {
2424
/// - dueTime: The duration you want to debounce for.
2525
/// - scheduler: The scheduler you want to deliver the debounced output to.
2626
/// - Returns: An effect that publishes events only after a specified time elapses.
27+
@available(
28+
iOS,
29+
deprecated: 9999.0,
30+
message: "Use 'withTaskCancellation(id: _, cancelInFlight: true)' in 'Effect.run', instead."
31+
)
32+
@available(
33+
macOS,
34+
deprecated: 9999.0,
35+
message: "Use 'withTaskCancellation(id: _, cancelInFlight: true)' in 'Effect.run', instead."
36+
)
37+
@available(
38+
tvOS,
39+
deprecated: 9999.0,
40+
message: "Use 'withTaskCancellation(id: _, cancelInFlight: true)' in 'Effect.run', instead."
41+
)
42+
@available(
43+
watchOS,
44+
deprecated: 9999.0,
45+
message: "Use 'withTaskCancellation(id: _, cancelInFlight: true)' in 'Effect.run', instead."
46+
)
2747
public func debounce(
2848
id: AnyHashable,
2949
for dueTime: TimeInterval,
@@ -32,7 +52,7 @@ extension Effect {
3252
switch self.operation {
3353
case .none:
3454
return .none
35-
case .producer:
55+
case .producer, .run:
3656
return Self(
3757
operation: .producer(
3858
SignalProducer<Void, Never>.init(value: ())
@@ -42,23 +62,6 @@ extension Effect {
4262
)
4363
)
4464
.cancellable(id: id, cancelInFlight: true)
45-
case let .run(priority, operation):
46-
return Self(
47-
operation: .run(priority) { send in
48-
await withTaskCancellation(id: id, cancelInFlight: true) {
49-
do {
50-
try await scheduler.sleep(for: .nanoseconds(Int(dueTime * TimeInterval(NSEC_PER_SEC))))
51-
await operation(
52-
Send { output in
53-
scheduler.schedule {
54-
send(output)
55-
}
56-
}
57-
)
58-
} catch {}
59-
}
60-
}
61-
)
6265
}
6366
}
6467

Sources/ComposableArchitecture/Effects/Deferring.swift

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,14 @@ extension Effect {
2929
switch self.operation {
3030
case .none:
3131
return .none
32-
case .producer:
32+
case .producer, .run:
3333
return Self(
3434
operation: .producer(
3535
SignalProducer<Void, Never>(value: ())
3636
.delay(dueTime, on: scheduler)
3737
.flatMap(.latest) { self.producer.observe(on: scheduler) }
3838
)
3939
)
40-
case let .run(priority, operation):
41-
return Self(
42-
operation: .run(priority) { send in
43-
do {
44-
try await scheduler.sleep(for: .nanoseconds(Int(dueTime * TimeInterval(NSEC_PER_SEC))))
45-
await operation(
46-
Send { output in
47-
scheduler.schedule {
48-
send(output)
49-
}
50-
}
51-
)
52-
} catch {}
53-
}
54-
)
5540
}
5641
}
5742
}

Tests/ComposableArchitectureTests/EffectFailureTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import XCTest
1010

1111
XCTExpectFailure {
1212
$0.compactDescription == """
13-
An 'Effect.task' returned from "ComposableArchitectureTests/EffectFailureTests.swift:24" \
13+
An 'Effect.task' returned from "ComposableArchitectureTests/EffectFailureTests.swift:23" \
1414
threw an unhandled error. …
1515
1616
EffectFailureTests.Unexpected()
@@ -33,7 +33,7 @@ import XCTest
3333

3434
XCTExpectFailure {
3535
$0.compactDescription == """
36-
An 'Effect.run' returned from "ComposableArchitectureTests/EffectFailureTests.swift:47" \
36+
An 'Effect.run' returned from "ComposableArchitectureTests/EffectFailureTests.swift:46" \
3737
threw an unhandled error. …
3838
3939
EffectFailureTests.Unexpected()

0 commit comments

Comments
 (0)