@@ -2,6 +2,109 @@ import Combine
22import SwiftUI
33
44#if compiler(>=5.5)
5+ @available ( iOS 15 , macOS 12 , tvOS 15 , watchOS 8 , * )
6+ extension Effect {
7+ /// Wraps an asynchronous unit of work in an effect.
8+ ///
9+ /// This function is useful for executing work in an asynchronous context and capture the result
10+ /// in an ``Effect`` so that the reducer, a non-asynchronous context, can process it.
11+ ///
12+ /// ```swift
13+ /// Effect.task {
14+ /// guard case let .some((data, _)) = try? await URLSession.shared
15+ /// .data(from: .init(string: "http://numbersapi.com/42")!)
16+ /// else {
17+ /// return "Could not load"
18+ /// }
19+ ///
20+ /// return String(decoding: data, as: UTF8.self)
21+ /// }
22+ /// ```
23+ ///
24+ /// Note that due to the lack of tools to control the execution of asynchronous work in Swift,
25+ /// it is not recommended to use this function in reducers directly. Doing so will introduce
26+ /// thread hops into your effects that will make testing difficult. You will be responsible for
27+ /// adding explicit expectations to wait for small amounts of time so that effects can deliver
28+ /// their output.
29+ ///
30+ /// Instead, this function is most helpful for calling `async`/`await` functions from the live
31+ /// implementation of dependencies, such as `URLSession.data`, `MKLocalSearch.start` and more.
32+ ///
33+ /// - Parameters:
34+ /// - priority: Priority of the underlying task. If `nil`, the priority will come from
35+ /// `Task.currentPriority`.
36+ /// - operation: The operation to execute.
37+ /// - Returns: An effect wrapping the given asynchronous work.
38+ public static func task(
39+ priority: TaskPriority ? = nil ,
40+ operation: @escaping @Sendable ( ) async -> Output
41+ ) -> Self where Failure == Never {
42+ var task : Task < Void , Never > ?
43+ return . future { callback in
44+ task = Task ( priority: priority) {
45+ guard !Task. isCancelled else { return }
46+ let output = await operation ( )
47+ guard !Task. isCancelled else { return }
48+ callback ( . success( output) )
49+ }
50+ }
51+ . handleEvents ( receiveCancel: { task? . cancel ( ) } )
52+ . eraseToEffect ( )
53+ }
54+
55+ /// Wraps an asynchronous unit of work in an effect.
56+ ///
57+ /// This function is useful for executing work in an asynchronous context and capture the result
58+ /// in an ``Effect`` so that the reducer, a non-asynchronous context, can process it.
59+ ///
60+ /// ```swift
61+ /// Effect.task {
62+ /// let (data, _) = try await URLSession.shared
63+ /// .data(from: .init(string: "http://numbersapi.com/42")!)
64+ ///
65+ /// return String(decoding: data, as: UTF8.self)
66+ /// }
67+ /// ```
68+ ///
69+ /// Note that due to the lack of tools to control the execution of asynchronous work in Swift,
70+ /// it is not recommended to use this function in reducers directly. Doing so will introduce
71+ /// thread hops into your effects that will make testing difficult. You will be responsible for
72+ /// adding explicit expectations to wait for small amounts of time so that effects can deliver
73+ /// their output.
74+ ///
75+ /// Instead, this function is most helpful for calling `async`/`await` functions from the live
76+ /// implementation of dependencies, such as `URLSession.data`, `MKLocalSearch.start` and more.
77+ ///
78+ /// - Parameters:
79+ /// - priority: Priority of the underlying task. If `nil`, the priority will come from
80+ /// `Task.currentPriority`.
81+ /// - operation: The operation to execute.
82+ /// - Returns: An effect wrapping the given asynchronous work.
83+ public static func task(
84+ priority: TaskPriority ? = nil ,
85+ operation: @escaping @Sendable ( ) async throws -> Output
86+ ) -> Self where Failure == Error {
87+ Deferred < Publishers . HandleEvents < PassthroughSubject < Output , Failure > > > {
88+ let subject = PassthroughSubject < Output , Failure > ( )
89+ let task = Task ( priority: priority) {
90+ do {
91+ try Task . checkCancellation ( )
92+ let output = try await operation ( )
93+ try Task . checkCancellation ( )
94+ subject. send ( output)
95+ subject. send ( completion: . finished)
96+ } catch is CancellationError {
97+ subject. send ( completion: . finished)
98+ } catch {
99+ subject. send ( completion: . failure( error) )
100+ }
101+ }
102+ return subject. handleEvents ( receiveCancel: task. cancel)
103+ }
104+ . eraseToEffect ( )
105+ }
106+ }
107+
5108 @available ( iOS 15 , macOS 12 , tvOS 15 , watchOS 8 , * )
6109 extension ViewStore {
7110 /// Sends an action into the store and then suspends while a piece of state is `true`.
0 commit comments