├── .gitignore ├── LICENSE ├── Package.swift ├── README.md └── Sources └── Promises.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .swiftpm 2 | .build 3 | xcuserdata/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2015-2021 Tibor Bödecs 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | import PackageDescription 3 | 4 | let package = Package( 5 | name: "Promises", 6 | products: [ 7 | .library(name: "Promises", targets: ["Promises"]) 8 | ], 9 | targets: [ 10 | .target(name: "Promises", path: "Sources") 11 | ] 12 | ) 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Promises 2 | 3 | It's just another Promise library that works on all the Apple operating systems plus on Linux. 4 | 5 | ## Installation 6 | 7 | ### Swift Package Manager 8 | 9 | ``` 10 | .package(url: "https://github.com/CoreKit/Promises.git", from: "1.0.1"), 11 | ``` 12 | -------------------------------------------------------------------------------- /Sources/Promises.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Promise.swift 3 | // CoreKit 4 | // 5 | // Created by Tibor Bödecs on 2017. 09. 27.. 6 | // Copyright © 2017. Tibor Bödecs. All rights reserved. 7 | // 8 | 9 | import Dispatch 10 | 11 | public struct Future { 12 | 13 | public enum Result { 14 | case success(T) 15 | case failure(Error) 16 | } 17 | 18 | fileprivate var result: Result? 19 | 20 | fileprivate init(result: Result? = nil) { 21 | self.result = result 22 | } 23 | 24 | public var value: T? { 25 | guard let result = self.result, case .success(let value) = result else { 26 | return nil 27 | } 28 | return value 29 | } 30 | 31 | public var error: Error? { 32 | guard let result = self.result, case .failure(let error) = result else { 33 | return nil 34 | } 35 | return error 36 | } 37 | 38 | public var isPending: Bool { 39 | return self.result == nil 40 | } 41 | 42 | public var isFulfilled: Bool { 43 | return self.value != nil 44 | } 45 | 46 | public var isRejected: Bool { 47 | return self.error != nil 48 | } 49 | } 50 | 51 | 52 | open class Promise { 53 | 54 | fileprivate struct Callback { 55 | 56 | fileprivate let queue: DispatchQueue 57 | fileprivate let onSuccess: ((T) -> Void) 58 | fileprivate let onFailure: ((Error) -> Void) 59 | 60 | fileprivate init(queue: DispatchQueue, 61 | onSuccess: @escaping ((T) -> Void), 62 | onFailure: @escaping ((Error) -> Void)) { 63 | self.queue = queue 64 | self.onSuccess = onSuccess 65 | self.onFailure = onFailure 66 | } 67 | } 68 | 69 | private var future: Future 70 | private var callbacks: [Callback] = [] 71 | private let lockQueue = DispatchQueue(label: "dispatch.promise.lock.queue", qos: .userInitiated) 72 | private var executionQueue: DispatchQueue 73 | 74 | public init(_ on: DispatchQueue = .global(qos: .userInitiated), _ future: Future? = nil) { 75 | self.executionQueue = on 76 | self.future = future ?? Future() 77 | } 78 | 79 | public convenience init(queue: DispatchQueue = .global(qos: .default), value: T) { 80 | self.init(queue, Future(result: .success(value))) 81 | } 82 | 83 | public convenience init(queue: DispatchQueue = .global(qos: .default), error: Error) { 84 | self.init(queue, Future(result: .failure(error))) 85 | } 86 | 87 | public convenience init(queue: DispatchQueue = .global(qos: .default), 88 | block: @escaping (_ fulfill: @escaping ((T) -> Void), 89 | _ reject: @escaping ((Error) -> Void)) throws -> Void) { 90 | self.init(queue) 91 | 92 | queue.async { 93 | do { 94 | try block(self.fulfill, self.reject) 95 | } 96 | catch { 97 | self.reject(error) 98 | } 99 | } 100 | } 101 | 102 | public convenience init(queue: DispatchQueue = .global(qos: .default), 103 | block: @escaping () throws -> T) { 104 | self.init(queue) 105 | 106 | queue.async { 107 | do { 108 | self.fulfill(try block()) 109 | } 110 | catch { 111 | self.reject(error) 112 | } 113 | } 114 | } 115 | 116 | @discardableResult 117 | fileprivate func then(queue: DispatchQueue? = nil, 118 | success: @escaping ((T) -> Void), 119 | failure: @escaping ((Error) -> Void)) -> Promise { 120 | let executionQueue = queue ?? self.executionQueue 121 | self.addCallbacks(queue: executionQueue, onFulfilled: success, onRejected: failure) 122 | return self 123 | } 124 | 125 | @discardableResult 126 | public func then(queue: DispatchQueue? = nil, _ f: @escaping ((T) throws -> Promise)) -> Promise { 127 | 128 | let executionQueue = queue ?? self.executionQueue 129 | 130 | return Promise(queue: self.executionQueue) { fulfill, reject in 131 | self.addCallbacks( 132 | queue: executionQueue, 133 | onFulfilled: { value in 134 | do { 135 | try f(value).then(queue: queue, success: fulfill, failure: reject) 136 | } 137 | catch { 138 | reject(error) 139 | } 140 | }, 141 | onRejected: reject 142 | ) 143 | } 144 | } 145 | 146 | @discardableResult 147 | public func thenMap(queue: DispatchQueue? = nil, _ f: @escaping ((T) throws -> U)) -> Promise { 148 | 149 | let executionQueue = queue ?? self.executionQueue 150 | 151 | return self.then(queue: executionQueue) { value -> Promise in 152 | do { 153 | return Promise(queue: self.executionQueue, value: try f(value)) 154 | } 155 | catch { 156 | return Promise(queue: self.executionQueue, error: error) 157 | } 158 | } 159 | } 160 | 161 | @discardableResult 162 | public func onSuccess(queue: DispatchQueue? = nil, _ success: @escaping ((T) -> Void)) -> Promise { 163 | return self.then(queue: queue, success: success, failure: { _ in }) 164 | } 165 | 166 | @discardableResult 167 | public func onFailure(queue: DispatchQueue? = nil, _ failure: @escaping ((Error) -> Void)) -> Promise { 168 | return self.then(queue: queue, success: { _ in }, failure: failure) 169 | } 170 | 171 | private func update(_ future: Future) { 172 | guard self.isPending else { 173 | return 174 | } 175 | self.lockQueue.sync { 176 | self.future = future 177 | } 178 | self.runCallbacks() 179 | } 180 | 181 | public func fulfill(_ value: T) { 182 | self.update(Future(result: .success(value))) 183 | } 184 | 185 | public func reject(_ error: Error) { 186 | self.update(Future(result: .failure(error))) 187 | } 188 | 189 | public var isPending: Bool { 190 | return !self.isFulfilled && !self.isRejected 191 | } 192 | 193 | public var isFulfilled: Bool { 194 | return self.value != nil 195 | } 196 | 197 | public var isRejected: Bool { 198 | return self.error != nil 199 | } 200 | 201 | public var value: T? { 202 | return self.lockQueue.sync { 203 | return self.future.value 204 | } 205 | } 206 | 207 | public var error: Error? { 208 | return self.lockQueue.sync { 209 | return self.future.error 210 | } 211 | } 212 | 213 | private func addCallbacks(queue: DispatchQueue, 214 | onFulfilled: @escaping ((T) -> Void), 215 | onRejected: @escaping ((Error) -> Void)) { 216 | let callback = Callback(queue: queue, onSuccess: onFulfilled, onFailure: onRejected) 217 | self.lockQueue.async { 218 | self.callbacks.append(callback) 219 | } 220 | self.runCallbacks() 221 | } 222 | 223 | private func runCallbacks() { 224 | self.lockQueue.async(execute: { 225 | guard let callback = self.callbacks.first, !self.future.isPending else { 226 | return 227 | } 228 | self.callbacks.removeFirst() 229 | 230 | let group = DispatchGroup() 231 | group.notify(queue: callback.queue) { 232 | self.runCallbacks() 233 | } 234 | switch self.future.result! { 235 | case .success(let value): 236 | callback.queue.async(group: group) { 237 | callback.onSuccess(value) 238 | } 239 | case .failure(let error): 240 | callback.queue.async(group: group) { 241 | callback.onFailure(error) 242 | } 243 | } 244 | }) 245 | } 246 | } 247 | 248 | public extension Promise { 249 | 250 | @discardableResult 251 | func tap(queue: DispatchQueue? = nil, _ block: @escaping (() -> Void)) -> Promise { 252 | return self.thenMap(queue: queue) { value -> T in 253 | block() 254 | return value 255 | } 256 | } 257 | 258 | @discardableResult 259 | func tap(queue: DispatchQueue? = nil, _ block: @escaping ((T) -> Void)) -> Promise { 260 | return self.thenMap(queue: queue) { value -> T in 261 | block(value) 262 | return value 263 | } 264 | } 265 | 266 | @discardableResult 267 | func validate(_ condition: @escaping (T) -> Bool) -> Promise { 268 | return self.thenMap { value -> T in 269 | guard condition(value) else { 270 | throw Promises.Errors.validation 271 | } 272 | return value 273 | } 274 | } 275 | 276 | @discardableResult 277 | func always(queue: DispatchQueue? = nil, _ block: @escaping () -> Void) -> Promise { 278 | return self.then(queue: queue, success: { _ in block() }, failure: { _ in block() }) 279 | } 280 | 281 | @discardableResult 282 | func timeout(_ timeout: DispatchTimeInterval) -> Promise { 283 | return Promises.race([self, Promises.timeout(timeout)]) 284 | } 285 | 286 | @discardableResult 287 | func recover(recovery: @escaping (Error) throws -> Promise) -> Promise { 288 | return Promise { fulfill, reject in 289 | self.then(success: fulfill, failure: { error in 290 | do { 291 | try recovery(error).then(success: fulfill, failure: reject) 292 | } 293 | catch { 294 | reject(error) 295 | } 296 | }) 297 | } 298 | } 299 | 300 | } 301 | 302 | public enum Promises { 303 | 304 | public enum Errors: Error { 305 | case validation 306 | case timeout 307 | } 308 | 309 | public static func first(queue: DispatchQueue? = nil, 310 | _ block: @escaping () throws -> Promise) -> Promise { 311 | return Promise(value: ()).then(queue: queue, block) 312 | } 313 | 314 | public static func first(queue: DispatchQueue? = nil, 315 | _ block: @escaping () throws -> T) -> Promise { 316 | return Promise(value: ()).thenMap(queue: queue, block) 317 | } 318 | 319 | @discardableResult 320 | public static func delay(_ delay: DispatchTimeInterval) -> Promise<()> { 321 | return Promise<()> { fulfill, _ in 322 | let time = DispatchTime.now() + delay 323 | DispatchQueue.main.asyncAfter(deadline: time) { 324 | fulfill(()) 325 | } 326 | } 327 | } 328 | 329 | @discardableResult 330 | public static func timeout(_ timeout: DispatchTimeInterval) -> Promise { 331 | return Promise { _, reject in 332 | Promises.delay(timeout) 333 | .onSuccess { 334 | reject(Promises.Errors.timeout) 335 | } 336 | } 337 | } 338 | 339 | @discardableResult 340 | public static func all(_ promises: [Promise]) -> Promise<[T]> { 341 | return Promise<[T]> { fulfill, reject in 342 | guard !promises.isEmpty else { 343 | return fulfill([]) 344 | } 345 | 346 | for promise in promises { 347 | promise.then(success: { value in 348 | if !promises.contains(where: { $0.isRejected || $0.isPending }) { 349 | fulfill(promises.compactMap { $0.value }) 350 | } 351 | }, failure: reject) 352 | } 353 | } 354 | } 355 | 356 | @discardableResult 357 | public static func wait(_ promises: [Promise]) -> Promise { 358 | return Promise { fulfill, _ in 359 | guard !promises.isEmpty else { 360 | return fulfill(()) 361 | } 362 | let complete: ((Any) -> Void) = { _ in 363 | if !promises.contains(where: { $0.isPending }) { 364 | fulfill(()) 365 | } 366 | } 367 | for promise in promises { 368 | promise.then(success: complete, failure: complete) 369 | } 370 | } 371 | } 372 | 373 | @discardableResult 374 | public static func race(_ promises: [Promise]) -> Promise { 375 | return Promise { fulfill, reject in 376 | guard !promises.isEmpty else { 377 | fatalError("Could not race empty promises array.") 378 | } 379 | for promise in promises { 380 | promise.then(success: fulfill, failure: reject) 381 | } 382 | } 383 | } 384 | 385 | @discardableResult 386 | public static func zip(_ p1: Promise, 387 | _ last: Promise) -> Promise<(T1, T2)> { 388 | return Promise<(T1, T2)> { fulfill, reject in 389 | let resolver: (Any) -> Void = { _ in 390 | if let firstValue = p1.value, let secondValue = last.value { 391 | fulfill((firstValue, secondValue)) 392 | } 393 | } 394 | p1.then(success: resolver, failure: reject) 395 | last.then(success: resolver, failure: reject) 396 | } 397 | } 398 | 399 | @discardableResult 400 | public static func zip(_ p1: Promise, 401 | _ p2: Promise, 402 | _ last: Promise) -> Promise<(T1, T2, T3)> { 403 | return Promise<(T1, T2, T3)> { (fulfill: @escaping ((T1, T2, T3)) -> Void, reject: @escaping (Error) -> Void) in 404 | let zipped: Promise<(T1, T2)> = zip(p1, p2) 405 | 406 | let resolver: (Any) -> Void = { _ in 407 | if let zippedValue = zipped.value, let lastValue = last.value { 408 | fulfill((zippedValue.0, zippedValue.1, lastValue)) 409 | } 410 | } 411 | zipped.then(success: resolver, failure: reject) 412 | last.then(success: resolver, failure: reject) 413 | } 414 | } 415 | 416 | @discardableResult 417 | public static func zip(_ p1: Promise, 418 | _ p2: Promise, 419 | _ p3: Promise, 420 | _ last: Promise) -> Promise<(T1, T2, T3, T4)> { 421 | return Promise<(T1, T2, T3, T4)> { (fulfill: @escaping ((T1, T2, T3, T4)) -> Void, reject: @escaping (Error) -> Void) in 422 | let zipped: Promise<(T1, T2, T3)> = zip(p1, p2, p3) 423 | 424 | let resolver: (Any) -> Void = { _ in 425 | if let zippedValue = zipped.value, let lastValue = last.value { 426 | fulfill((zippedValue.0, zippedValue.1, zippedValue.2, lastValue)) 427 | } 428 | } 429 | zipped.then(success: resolver, failure: reject) 430 | last.then(success: resolver, failure: reject) 431 | } 432 | } 433 | 434 | @discardableResult 435 | public static func retry(times: Int, 436 | delay: DispatchTimeInterval, 437 | generate: @escaping () -> Promise) -> Promise { 438 | if times <= 0 { 439 | return generate() 440 | } 441 | return Promise { fulfill, reject in 442 | generate().recover { _ in 443 | return Promises.delay(delay).then { _ in 444 | return self.retry(times: times - 1, delay: delay, generate: generate) 445 | } 446 | } 447 | .then(success: fulfill, failure: reject) 448 | } 449 | } 450 | } 451 | --------------------------------------------------------------------------------