-
Notifications
You must be signed in to change notification settings - Fork 26
/
Copy pathPromise.swift
183 lines (169 loc) · 5.11 KB
/
Promise.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
//
// Promise.swift
// NitroModules
//
// Created by Marc Rousavy on 15.08.24.
//
import Foundation
/**
* Represents a Promise that can be passed to JS.
*
* Create a new Promise with the following APIs:
* - `Promise<T>.async { ... }` - Creates a new Promise that runs the given code in a Swift `async`/`await` Task.
* - `Promise<T>.parallel { ... }` - Creates a new Promise that runs the given code in a parallel `DispatchQueue`.
* - `Promise<T>.resolved(withResult:)` - Creates a new already resolved Promise.
* - `Promise<T>.rejected(withError:)` - Creates a new already rejected Promise.
* - `Promise<T>()` - Creates a new Promise with fully manual control over the `resolve(..)`/`reject(..)` functions.
*/
public final class Promise<T> {
private enum State {
case result(T)
case error(Error)
}
private var state: State?
private var onResolvedListeners: [(T) -> Void] = []
private var onRejectedListeners: [(Error) -> Void] = []
/**
* Create a new pending Promise.
* It can (and must) be resolved **or** rejected later.
*/
public init() {
state = nil
}
deinit {
if state == nil {
let message = "Timeouted: Promise<\(String(describing: T.self))> was destroyed!"
reject(withError: RuntimeError.error(withMessage: message))
}
}
/**
* Resolves this `Promise<T>` with the given `T` and notifies all listeners.
*/
public func resolve(withResult result: T) {
guard state == nil else {
fatalError("Failed to resolve promise with \(result) - it has already been resolved or rejected!")
}
state = .result(result)
onResolvedListeners.forEach { listener in listener(result) }
}
/**
* Rejects this `Promise<T>` with the given `Error` and notifies all listeners.
*/
public func reject(withError error: Error) {
guard state == nil else {
fatalError("Failed to reject promise with \(error) - it has already been resolved or rejected!")
}
state = .error(error)
onRejectedListeners.forEach { listener in listener(error) }
}
}
/**
* Extensions to easily create new Promises.
*/
extension Promise {
/**
* Create a new `Promise<T>` already resolved with the given `T`.
*/
public static func resolved(withResult result: T) -> Promise {
let promise = Promise()
promise.state = .result(result)
return promise
}
/**
* Create a new `Promise<T>` already rejected with the given `Error`.
*/
public static func rejected(withError error: Error) -> Promise {
let promise = Promise()
promise.state = .error(error)
return promise
}
/**
* Create a new `Promise<T>` that runs the given `async` code in a `Task`.
* This does not necessarily run the code in a different Thread, but supports Swift's `async`/`await`.
*/
public static func `async`(_ priority: TaskPriority? = nil,
_ run: @escaping () async throws -> T) -> Promise {
let promise = Promise()
Task(priority: priority) {
do {
let result = try await run()
promise.resolve(withResult: result)
} catch {
promise.reject(withError: error)
}
}
return promise
}
/**
* Create a new `Promise<T>` that runs the given `run` function on a parallel Thread/`DispatchQueue`.
*/
public static func parallel(_ queue: DispatchQueue = .global(),
_ run: @escaping () throws -> T) -> Promise {
let promise = Promise()
queue.async {
do {
let result = try run()
promise.resolve(withResult: result)
} catch {
promise.reject(withError: error)
}
}
return promise
}
}
/**
* Extensions to support then/catch syntax.
*/
extension Promise {
/**
* Add a continuation listener to this `Promise<T>`.
* Once the `Promise<T>` resolves, the `onResolvedListener` will be called.
*/
@discardableResult
public func then(_ onResolvedListener: @escaping (T) -> Void) -> Promise {
switch state {
case .result(let result):
onResolvedListener(result)
break
default:
onResolvedListeners.append(onResolvedListener)
break
}
return self
}
/**
* Add an error continuation listener to this `Promise<T>`.
* Once the `Promise<T>` rejects, the `onRejectedListener` will be called with the error.
*/
@discardableResult
public func `catch`(_ onRejectedListener: @escaping (Error) -> Void) -> Promise {
switch state {
case .error(let error):
onRejectedListener(error)
break
default:
onRejectedListeners.append(onRejectedListener)
break
}
return self
}
}
/**
* Extensions to support await syntax.
*/
extension Promise {
/**
* Asynchronously await the result of the Promise.
* If the Promise is already resolved/rejected, this will continue immediately,
* otherwise it will asynchronously wait for a result or throw on a rejection.
*/
public func `await`() async throws -> T {
return try await withUnsafeThrowingContinuation { continuation in
self.then { result in
continuation.resume(returning: result)
}.catch { error in
continuation.resume(throwing: error)
}
}
}
}