You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
7.8 KiB
259 lines
7.8 KiB
//
|
|
// Promise.swift
|
|
// then
|
|
//
|
|
// Created by Sacha Durand Saint Omer on 06/02/16.
|
|
// Copyright © 2016 s4cha. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import Dispatch
|
|
|
|
private class Locker {
|
|
let lockQueueSpecificKey: DispatchSpecificKey<Void>
|
|
let lockQueue: DispatchQueue
|
|
init() {
|
|
lockQueueSpecificKey = DispatchSpecificKey<Void>()
|
|
lockQueue = DispatchQueue(label: "com.freshOS.then.lockQueue", qos: .userInitiated)
|
|
lockQueue.setSpecific(key: lockQueueSpecificKey, value: ())
|
|
}
|
|
|
|
var isOnLockQueue: Bool {
|
|
return DispatchQueue.getSpecific(key: lockQueueSpecificKey) != nil
|
|
}
|
|
}
|
|
|
|
public class Promise<T> {
|
|
|
|
// MARK: - Protected properties
|
|
|
|
internal var numberOfRetries: UInt = 0
|
|
|
|
private var threadUnsafeState: PromiseState<T>
|
|
|
|
private var threadUnsafeBlocks: PromiseBlocks<T> = PromiseBlocks<T>()
|
|
|
|
private var initialPromiseStart:(() -> Void)?
|
|
private var initialPromiseStarted = false
|
|
|
|
internal typealias ProgressCallBack = (_ resolve: @escaping ((T) -> Void),
|
|
_ reject: @escaping ((Error) -> Void),
|
|
_ progress: @escaping ((Float) -> Void)) -> Void
|
|
|
|
private var promiseProgressCallBack: ProgressCallBack?
|
|
|
|
// MARK: - Lock
|
|
|
|
private let locker = Locker()
|
|
private var lockQueue: DispatchQueue {
|
|
return locker.lockQueue
|
|
}
|
|
private func _synchronize<U>(_ action: () -> U) -> U {
|
|
if locker.isOnLockQueue {
|
|
return action()
|
|
} else {
|
|
return lockQueue.sync(execute: action)
|
|
}
|
|
}
|
|
|
|
private func _asynchronize(_ action: @escaping () -> Void) {
|
|
lockQueue.async(execute: action)
|
|
}
|
|
|
|
// MARK: - Intializers
|
|
|
|
public init() {
|
|
threadUnsafeState = .dormant
|
|
}
|
|
|
|
public init(_ value: T) {
|
|
threadUnsafeState = .fulfilled(value: value)
|
|
}
|
|
|
|
public init(error: Error) {
|
|
threadUnsafeState = PromiseState.rejected(error: error)
|
|
}
|
|
|
|
public convenience init(callback: @escaping (
|
|
_ resolve: @escaping ((T) -> Void),
|
|
_ reject: @escaping ((Error) -> Void)) -> Void) {
|
|
self.init()
|
|
promiseProgressCallBack = { resolve, reject, progress in
|
|
callback(resolve, reject)
|
|
}
|
|
}
|
|
|
|
public convenience init(callback: @escaping (
|
|
_ resolve: @escaping ((T) -> Void),
|
|
_ reject: @escaping ((Error) -> Void),
|
|
_ progress: @escaping ((Float) -> Void)) -> Void) {
|
|
self.init()
|
|
promiseProgressCallBack = { resolve, reject, progress in
|
|
callback(resolve, reject, progress)
|
|
}
|
|
}
|
|
|
|
// MARK: - Private atomic operations
|
|
|
|
private func _updateFirstPromiseStartFunctionAndState(from startBody: @escaping () -> Void, isStarted: Bool) {
|
|
_synchronize {
|
|
initialPromiseStart = startBody
|
|
initialPromiseStarted = isStarted
|
|
}
|
|
}
|
|
|
|
// MARK: - Public interfaces
|
|
|
|
public func start() {
|
|
_synchronize({ return _start() })?()
|
|
}
|
|
|
|
public func fulfill(_ value: T) {
|
|
_synchronize({ () -> (() -> Void)? in
|
|
let action = _updateState(.fulfilled(value: value))
|
|
threadUnsafeBlocks = .init()
|
|
promiseProgressCallBack = nil
|
|
return action
|
|
})?()
|
|
}
|
|
|
|
public func reject(_ anError: Error) {
|
|
_synchronize({ () -> (() -> Void)? in
|
|
let action = _updateState(.rejected(error: anError))
|
|
// Only release callbacks if no retries a registered.
|
|
if numberOfRetries == 0 {
|
|
threadUnsafeBlocks = .init()
|
|
promiseProgressCallBack = nil
|
|
}
|
|
return action
|
|
})?()
|
|
}
|
|
|
|
// MARK: - Internal interfaces
|
|
|
|
internal func synchronize<U>(
|
|
_ action: (_ currentState: PromiseState<T>, _ blocks: inout PromiseBlocks<T>) -> U) -> U {
|
|
return _synchronize {
|
|
return action(threadUnsafeState, &threadUnsafeBlocks)
|
|
}
|
|
}
|
|
|
|
internal func resetState() {
|
|
_synchronize {
|
|
threadUnsafeState = .dormant
|
|
}
|
|
}
|
|
|
|
internal func passAlongFirstPromiseStartFunctionAndStateTo<X>(_ promise: Promise<X>) {
|
|
let (startBlock, isStarted) = _synchronize {
|
|
return (self.initialPromiseStart ?? self.start, self.initialPromiseStarted)
|
|
}
|
|
promise._updateFirstPromiseStartFunctionAndState(from: startBlock, isStarted: isStarted)
|
|
}
|
|
|
|
internal func tryStartInitialPromiseAndStartIfneeded() {
|
|
var actions: [(() -> Void)?] = []
|
|
_synchronize {
|
|
actions = [
|
|
_startInitialPromiseIfNeeded(),
|
|
_start()
|
|
]
|
|
}
|
|
actions.forEach { $0?() }
|
|
}
|
|
|
|
internal func updateState(_ newState: PromiseState<T>) {
|
|
_synchronize({ return _updateState(newState) })?()
|
|
}
|
|
|
|
internal func setProgressCallBack(_ promiseProgressCallBack: @escaping ProgressCallBack) {
|
|
_synchronize {
|
|
self.promiseProgressCallBack = promiseProgressCallBack
|
|
}
|
|
}
|
|
|
|
internal func newLinkedPromise() -> Promise<T> {
|
|
let p = Promise<T>()
|
|
passAlongFirstPromiseStartFunctionAndStateTo(p)
|
|
return p
|
|
}
|
|
|
|
internal func syncStateWithCallBacks(success: @escaping ((T) -> Void),
|
|
failure: @escaping ((Error) -> Void),
|
|
progress: @escaping ((Float) -> Void)) {
|
|
_synchronize {
|
|
switch threadUnsafeState {
|
|
case let .fulfilled(value):
|
|
success(value)
|
|
case let .rejected(error):
|
|
failure(error)
|
|
case .dormant, .pending:
|
|
threadUnsafeBlocks.success.append(success)
|
|
threadUnsafeBlocks.fail.append(failure)
|
|
threadUnsafeBlocks.progress.append(progress)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Private non-atomic operations
|
|
|
|
private func _startInitialPromiseIfNeeded() -> (() -> Void)? {
|
|
guard !initialPromiseStarted else { return nil }
|
|
initialPromiseStarted = true
|
|
let body = self.initialPromiseStart
|
|
return body
|
|
}
|
|
|
|
private func _start() -> (() -> Void)? {
|
|
guard threadUnsafeState.isDormant else { return nil }
|
|
|
|
let updateAction = _updateState(.pending(progress: 0))
|
|
guard let p = promiseProgressCallBack else { return updateAction }
|
|
return {
|
|
updateAction?()
|
|
p(self.fulfill, self.reject, self.setProgress)
|
|
}
|
|
// promiseProgressCallBack = nil //Remove callba
|
|
}
|
|
|
|
private func _updateState(_ newState: PromiseState<T>) -> (() -> Void)? {
|
|
if threadUnsafeState.isPendingOrDormant {
|
|
threadUnsafeState = newState
|
|
}
|
|
return launchCallbacksIfNeeded()
|
|
}
|
|
|
|
private func launchCallbacksIfNeeded() -> (() -> Void)? {
|
|
switch threadUnsafeState {
|
|
case .dormant:
|
|
return nil
|
|
case .pending(let progress):
|
|
if progress != 0 {
|
|
return threadUnsafeBlocks.updateProgress(progress)
|
|
} else {
|
|
return nil
|
|
}
|
|
case .fulfilled(let value):
|
|
initialPromiseStart = nil
|
|
return threadUnsafeBlocks.fulfill(value: value)
|
|
case .rejected(let anError):
|
|
initialPromiseStart = nil
|
|
return threadUnsafeBlocks.reject(error: anError)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
extension Promise {
|
|
|
|
var isStarted: Bool {
|
|
return synchronize { state, _ in
|
|
switch state {
|
|
case .dormant:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
}
|