Promise is an application framework that deals with a series of asynchronous operations. It can ensure that a series of asynchronous operations can be executed in sequence. When an error occurs, it can be handled by catch ing the error. Promise framework is also a good interpretation of swift's face-to-face protocol programming and functional programming
There are two types of 1Promise and 2Guarantee. Guarantee does not implement the CatchMixin protocol and cannot catch errors. It is not allowed to throw errors. The first type is commonly used for error handling. Promise is a promise to be executed and may not be executed; guarantee is a guarantee to be executed
Basic usage:
func threeRequest111() { firstly { request1(with: ["test1": "first"]) } .then { (v) -> Promise<NSDictionary> in print("🍀", v) return self.request2(para: ["test2": "second"]) } .then { (v) -> Promise<NSDictionary> in print("🍀🍀", v) return self.request3(para: ["test3": "third"]) } .map({ (dic) -> [String:String] in if let dic1 = dic as? [String:String]{ return dic1 }else{ return [String:String]() } }).done({ (dic) in print(dic) }) .catch { (error) in print(error.localizedDescription) }.finally { print("finaly") } }func request1(with parameters: [String: String]) -> Promise<(NSDictionary)> {
return Promise<NSDictionary>(resolver: { (resolver) in
Alamofire.request("https://httpbin.org/get", method: .get, parameters: parameters).validate().responseJSON() { (response) in
switch response.result {
case .success(let dict):
delay(time: 1, task: {
resolver.fulfill(dict as! NSDictionary)
})
case .failure(let error):
resolver.reject(error)
}
}
})}
func request2(para:[String:String]) -> Promise<NSDictionary> {
return request1(with: para)
}
func request3(para:[String:String]) -> Promise<NSDictionary> {
return request1(with: para)}
Source code analysis
At the beginning, I wanted to study the source code with problems
1. How to ensure the sequential execution of a series of block s
Save the external incoming thenBlock, etc. into an array, handlers.append(to). When your own task finishes executing, execute the task with the array
2. How the return value promise in a closure is related to promise in the first function
rv.pipe(to: rp.box.seal)
Two. Promise main function
1.then function
func then<U: Thenable>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> U) -> Promise<U.T> { let rp = Promise<U.T>(.pending) pipe {//Upward promise Add task to switch $0 { case .fulfilled(let value): on.async(flags: flags) { 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 then function adds the pipe closure (including the closure to be executed externally) to the previous promise.box.handlers:
handlers.append(to)
to is the pipe closure in the then function, and it is added synchronously with the fence function when adding, which ensures the sequential execution of tasks
Barrier. Sync (flags:. Barrier) {/ / fence synchronization
When the closure (external time-consuming task) in the previous promise is completed, reever calls full:
resolver.fulfill(T) -> box.seal(.fulfilled(value)) ->
override func seal(_ value: T) { var handlers: Handlers<T>! barrier.sync(flags: .barrier) { guard case .pending(let _handlers) = self.sealant else { return // already fulfilled! } handlers = _handlers self.sealant = .resolved(value) } if let handlers = handlers { handlers.bodies.forEach{ $0(value) } } } //The task in handlers.bodies (that is, the block in the next then) with the result value is actually the pipe closure added by the sky above: pipe {//Upward promise Add task to switch $0 { case .fulfilled(let value): on.async(flags: flags) { do { let rv = try body(value) guard rv !== rp else { throw PMKError.returnedSelf } rv.pipe(to: rp.box.seal)//implement rp.handlers.foreach This task is added to rv.handlers } catch { rp.box.seal(.rejected(error)) } } case .rejected(let error): rp.box.seal(.rejected(error)) } }
Another thing around here is that switch $0 is the result < T > type, while handler.bodies.foreach {$0 (value)} is called. The value passed in is the T type, and it doesn't match. Take a look around, when initializing resolve, T represents result, so it matches correctly
We know that a Promise: RV needs to be returned in the then function block, and a Promise: rp has been created in the then function, which is passed to the next then function. Next, we will simplify the understanding of then{rv1:Promise}.then{rv2:promise} with two adjacent then functions
rp1.box.seal is stored in the handler of rv1, that is, whether to execute the pipe where rv2 is located
Figure 1 above
2.catch error capture function. Why can promise go to catch no matter which link reports an error
catch is defined in the CatchMixin protocol. promise implements this protocol. catch has a default implementation:
@discardableResult func `catch`(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, policy: CatchPolicy = conf.catchPolicy, _ body: @escaping(Error) -> Void) -> PMKFinalizer { let finalizer = PMKFinalizer() pipe { switch $0 { case .rejected(let error): guard policy == .allErrors || !error.isCancelled else { fallthrough } on.async(flags: flags) { body(error) finalizer.pending.resolve(()) } case .fulfilled: finalizer.pending.resolve(()) } } return finalizer }
catch is usually at the end or finally in the promise chain, so its return value is pmkfinizer
The content of pipe is added to the handels of the previous promise through pipe
When any link in the project chain executes reject(error), execute the seal in the next project
rp.box.seal(.rejected(error))
Follow the promise chain all the way down, and each one will execute this until catch, and go to the code task in catch
3. Firsty, it's easier to understand the t-function after you look at firsty. It's different from then in that it executes first because it has no promise in front of it. It's a function rather than a promise method. The return value is a promise like then
public func firstly<U: Thenable>(execute body: () throws -> U) -> Promise<U.T> { do { let rp = Promise<U.T>(.pending) try body().pipe(to: rp.box.seal)//implement body,Establish Promise,Execute after returning pipe(The external operation may be asynchronous and time-consuming, so it will be executed first pipe(to),return rp,Then it's external.then Functions, etc. return rp } catch { return Promise(error: error) } }
4. The when function is mainly used to implement the tasks in front of them at the same time. After they are all executed, the tasks will be executed in order.
Basic usage:
//request1 and request2,Execute in parallel request3;Parameter is promise Array, multiple tasks executing at the same time when(fulfilled: [request1(with: ["para1":"hello"]),request2(para: ["para2":"nihao"])]) .then { (dic) -> Promise<NSDictionary> in self.request3(para: ["uhu":"nih"]) }.catch { (error) in print(error.localizedDescription) } //promises There are different types of results in the array promise,Up to 5 different types of Promise when(fulfilled: request1(with: ["para1":"hello"]), request4()) .then { (arg0) -> Promise<[String:String]> in let (dic, str) = arg0 return Promise<[String:String]>(resolver: { (res) in res.fulfill([str : "\(dic)"]) }) }.catch { (error) in print(error.localizedDescription) } //Check the source code. Two Int values indicate the number of completed tasks and the total number of tasks private func _when<U: Thenable>(_ thenables: [U]) -> Promise<Void> { var countdown = thenables.count guard countdown > 0 else { return .value(Void()) } let rp = Promise<Void>(.pending) #if PMKDisableProgress || os(Linux) var progress: (completedUnitCount: Int, totalUnitCount: Int) = (0, 0) #else let progress = Progress(totalUnitCount: Int64(thenables.count)) progress.isCancellable = false progress.isPausable = false #endif let barrier = DispatchQueue(label: "org.promisekit.barrier.when", attributes: .concurrent) for promise in thenables { promise.pipe { result in barrier.sync(flags: .barrier) { switch result { case .rejected(let error): //If it is pending(Wait) state, passing the error down, which ensures several parallel tasks //There is only the first task that goes wrong, and it goes wrong, because once it goes wrong, any other task is meaningless if rp.isPending { progress.completedUnitCount = progress.totalUnitCount rp.box.seal(.rejected(error)) } case .fulfilled: //This condition ensures that if one of several parallel tasks has made an error, the task that has been completed correctly will not be passed down here guard rp.isPending else { return } progress.completedUnitCount += 1 countdown -= 1 if countdown == 0 { rp.box.seal(.fulfilled(())) } } } } } return rp }
Second, take a look at other functions provided by Promise
Map Conversion function, converting the result public func map<U>(on: DispatchQueue? = default, flags: DispatchWorkItemFlags? = default, _ transform: @escaping (Self.T) throws -> U) -> PromiseKit.Promise<U> done Function, done Function sum then The difference between functions is block The return value is void,It is only executed in sequence, and the result value of the previous step is used done function func done(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(T) throws -> Void) -> Promise<Void> Get Function, he and done Almost. It will return the upper result value automatically func get(on: DispatchQueue? = conf.Q.return, flags: DispatchWorkItemFlags? = nil, _ body: @escaping (T) throws -> Void) -> Promise<T> tap Function, he will return a Result<T>,You can see the returned value here without any side effects on the whole chain func tap(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ body: @escaping(Result<T>) -> Void) -> Promise<T> asVoid Function, which returns a new promise,And last promise Connect, and the last one promise Value of is discarded func asVoid() -> Promise<Void> //There are also some functions when the result value is Sequence (i.e. array). Some principles for array are similar to some higher-order functions of array public extension Thenable where T: Sequence func mapValues<U>(on: DispatchQueue? = conf.Q.map, flags: DispatchWorkItemFlags? = nil, _ transform: @escaping(T.Iterator.Element) throws -> U) -> Promise<[U]>
Attached 1, the framework also encapsulates a KVO to implement KVOProxy, a KVO created for guarantee. Any class can add an observer. When receiving the observation attribute changes, the content of guarantee will be executed. Guarantee is a guarantee that it will be executed. Therefore, in KVOProxy, KVOProxy owns itself, retainCycle = self, and will not be released if it is not received and dropped back
extension NSObject { public func observe(_: PMKNamespacer, keyPath: String) -> Guarantee<Any?> { return Guarantee { KVOProxy(observee: self, keyPath: keyPath, resolve: $0) } } } private class KVOProxy: NSObject { var retainCycle: KVOProxy? let fulfill: (Any?) -> Void @discardableResult init(observee: NSObject, keyPath: String, resolve: @escaping (Any?) -> Void) { fulfill = resolve super.init() observee.addObserver(self, forKeyPath: keyPath, options: NSKeyValueObservingOptions.new, context: pointer) retainCycle = self //Holding creates a circular reference by itself. It will not be released if it is not received and dropped back } fileprivate override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { if let change = change, context == pointer { defer { retainCycle = nil }//Delay execution, the last execution in the scope, and release after receiving the callback fulfill(change[NSKeyValueChangeKey.newKey]) if let object = object as? NSObject, let keyPath = keyPath { object.removeObserver(self, forKeyPath: keyPath) } } } private lazy var pointer: UnsafeMutableRawPointer = { return Unmanaged<KVOProxy>.passUnretained(self).toOpaque() }() }
Attached 2, the framework has made some extension s to alamofire