forked from mxcl/PromiseKit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Thenable.swift
389 lines (346 loc) · 12.2 KB
/
Thenable.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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
import Dispatch
/// Thenable represents an asynchronous operation that can be chained.
public protocol Thenable: class {
associatedtype T
func pipe(to: @escaping(Result<T>) -> Void)
var result: Result<T>? { get }
}
public extension Thenable {
/**
The provided closure executes when this promise resolves.
This allows chaining promises. The promise returned by the provided closure is resolved before the promise returned by this closure resolves.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The closure that executes when this promise fulfills. It must return a promise.
- Returns: A new promise that resolves when the promise returned from the provided closure resolves. For example:
firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.then { response in
transform(data: response.data)
}.done { transformation in
//…
}
*/
func then<U: Thenable>(on: DispatchQueue? = conf.Q.map, file: StaticString = #file, line: UInt = #line, _ body: @escaping(T) throws -> U) -> Promise<U.T> {
let rp = Promise<U.T>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async {
do {
let rv = try body(value)
guard rv !== rp else { throw PMKError.returnedSelf }
rv.pipe(to: rp.box.seal)
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
/**
The provided closure is executed when this promise is resolved.
This is like `then` but it requires the closure to return a non-promise.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter transform: The closure that is executed when this Promise is fulfilled. It must return a non-promise.
- Returns: A new promise that is resolved with the value returned from the provided closure. For example:
firstly {
URLSession.shared.dataTask(.promise, with: url1)
}.map { response in
response.data.length
}.done { length in
//…
}
*/
func map<U>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T) throws -> U) -> Promise<U> {
let rp = Promise<U>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async {
do {
rp.box.seal(.fulfilled(try transform(value)))
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
/**
The provided closure is executed when this promise is resolved.
In your closure return an `Optional`, if you return `nil` the resulting promise is rejected with `PMKError.compactMap`, otherwise the promise is fulfilled with the unwrapped value.
firstly {
URLSession.shared.dataTask(.promise, with: url)
}.compactMap {
try JSONSerialization.jsonObject(with: $0.data) as? [String: String]
}.done { dictionary in
//…
}.catch {
// either `PMKError.compactMap` or a `JSONError`
}
*/
func compactMap<U>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T) throws -> U?) -> Promise<U> {
let rp = Promise<U>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async {
do {
if let rv = try transform(value) {
rp.box.seal(.fulfilled(rv))
} else {
throw PMKError.compactMap(value, U.self)
}
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
/**
The provided closure is executed when this promise is resolved.
Equivalent to `map { x -> Void in`, but since we force the `Void` return Swift
is happier and gives you less hassle about your closure’s qualification.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The closure that is executed when this Promise is fulfilled.
- Returns: A new promise fulfilled as `Void`.
firstly {
URLSession.shared.dataTask(.promise, with: url)
}.done { response in
print(response.data)
}
*/
func done(on: DispatchQueue? = conf.Q.return, _ body: @escaping(T) throws -> Void) -> Promise<Void> {
let rp = Promise<Void>(.pending)
pipe {
switch $0 {
case .fulfilled(let value):
on.async {
do {
try body(value)
rp.box.seal(.fulfilled(()))
} catch {
rp.box.seal(.rejected(error))
}
}
case .rejected(let error):
rp.box.seal(.rejected(error))
}
}
return rp
}
/**
The provided closure is executed when this promise is resolved.
This is like `done` but it returns the same value that the handler is fed.
`get` immutably accesses the fulfilled value; the returned Promise maintains that value.
- Parameter on: The queue to which the provided closure dispatches.
- Parameter body: The closure that is executed when this Promise is fulfilled.
- Returns: A new promise that is resolved with the value that the handler is fed. For example:
firstly {
.value(1)
}.get { foo in
print(foo, " is 1")
}.done { foo in
print(foo, " is 1")
}.done { foo in
print(foo, " is Void")
}
*/
func get(on: DispatchQueue? = conf.Q.return, _ body: @escaping (T) throws -> Void) -> Promise<T> {
return map(on: on) {
try body($0)
return $0
}
}
/// - Returns: a new promise chained off this promise but with its value discarded.
func asVoid() -> Promise<Void> {
return map(on: nil) { _ in }
}
}
public extension Thenable {
/**
- Returns: The error with which this promise was rejected; `nil` if this promise is not rejected.
*/
var error: Error? {
switch result {
case .none:
return nil
case .some(.fulfilled):
return nil
case .some(.rejected(let error)):
return error
}
}
/**
- Returns: `true` if the promise has not yet resolved.
*/
var isPending: Bool {
return result == nil
}
/**
- Returns: `true` if the promise has resolved.
*/
var isResolved: Bool {
return !isPending
}
/**
- Returns: `true` if the promise was fulfilled.
*/
var isFulfilled: Bool {
return value != nil
}
/**
- Returns: `true` if the promise was rejected.
*/
var isRejected: Bool {
return error != nil
}
/**
- Returns: The value with which this promise was fulfilled or `nil` if this promise is pending or rejected.
*/
var value: T? {
switch result {
case .none:
return nil
case .some(.fulfilled(let value)):
return value
case .some(.rejected):
return nil
}
}
}
public extension Thenable where T: Sequence {
/**
`Promise<[T]>` => `T` -> `U` => `Promise<[U]>`
firstly {
.value([1,2,3])
}.mapValues { integer in
integer * 2
}.done {
// $0 => [2,4,6]
}
*/
func mapValues<U>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]> {
return map(on: on){ try $0.map(transform) }
}
/**
`Promise<[T]>` => `T` -> `[U]` => `Promise<[U]>`
firstly {
.value([1,2,3])
}.flatMapValues { integer in
[integer, integer]
}.done {
// $0 => [1,1,2,2,3,3]
}
*/
func flatMapValues<U: Sequence>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.Iterator.Element]> {
return map(on: on){ (foo: T) in
try foo.flatMap{ try transform($0) }
}
}
/**
`Promise<[T]>` => `T` -> `U?` => `Promise<[U]>`
firstly {
.value(["1","2","a","3"])
}.compactMapValues {
Int($0)
}.done {
// $0 => [1,2,3]
}
*/
func compactMapValues<U>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U?) -> Promise<[U]> {
return map(on: on) { foo -> [U] in
#if !swift(>=3.3) || (swift(>=4) && !swift(>=4.1))
return try foo.flatMap(transform)
#else
return try foo.compactMap(transform)
#endif
}
}
/**
`Promise<[T]>` => `T` -> `Promise<U>` => `Promise<[U]>`
firstly {
.value([1,2,3])
}.thenMap { integer in
.value(integer * 2)
}.done {
// $0 => [2,4,6]
}
*/
func thenMap<U: Thenable>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T]> {
return then(on: on) {
when(fulfilled: try $0.map(transform))
}
}
/**
`Promise<[T]>` => `T` -> `Promise<[U]>` => `Promise<[U]>`
firstly {
.value([1,2,3])
}.thenFlatMap { integer in
.value([integer, integer])
}.done {
// $0 => [1,1,2,2,3,3]
}
*/
func thenFlatMap<U: Thenable>(on: DispatchQueue? = conf.Q.map, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U.T.Iterator.Element]> where U.T: Sequence {
return then(on: on) {
when(fulfilled: try $0.map(transform))
}.map(on: nil) {
$0.flatMap{ $0 }
}
}
/**
`Promise<[T]>` => `T` -> Bool => `Promise<[U]>`
firstly {
.value([1,2,3])
}.filterValues {
$0 > 1
}.done {
// $0 => [2,3]
}
*/
func filterValues(on: DispatchQueue? = conf.Q.map, _ isIncluded: @escaping (T.Iterator.Element) -> Bool) -> Promise<[T.Iterator.Element]> {
return map(on: on) {
$0.filter(isIncluded)
}
}
}
public extension Thenable where T: Collection {
/// - Returns: a promise fulfilled with the first value of this `Collection` or, if empty, a promise rejected with PMKError.emptySequence.
var firstValue: Promise<T.Iterator.Element> {
return map(on: nil) { aa in
if let a1 = aa.first {
return a1
} else {
throw PMKError.emptySequence
}
}
}
/// - Returns: a promise fulfilled with the last value of this `Collection` or, if empty, a promise rejected with PMKError.emptySequence.
var lastValue: Promise<T.Iterator.Element> {
return map(on: nil) { aa in
if aa.isEmpty {
throw PMKError.emptySequence
} else {
let i = aa.index(aa.endIndex, offsetBy: -1)
return aa[i]
}
}
}
}
public extension Thenable where T: Sequence, T.Iterator.Element: Comparable {
/// - Returns: a promise fulfilled with the sorted values of this `Sequence`.
func sortedValues(on: DispatchQueue? = conf.Q.map) -> Promise<[T.Iterator.Element]> {
return map(on: on){ $0.sorted() }
}
}