使用 Async 輕量非同步套件鏈式調用非同步行為
Async: github.com/duemunk/Async
2019 年介紹 Combine
時 2019鐵人賽介紹 Combine 系列, 對於連續的非同步事件的調用, 我們會遇到回呼地獄(Callback hell), 也就是多筆具相依的非同步步驟響應辦法.
## Async
的解法 相較於原本的 DispatchQueue 的版本:
DispatchQueue.global(qos: .userInitiated).async {
let value = 10
DispatchQueue.global(qos: .background).async {
let text = "Score: \(value)"
DispatchQueue.main.async {
label.text = text
}
}
}
Async
.userInitiated { 10 }
.background { "Score: \($0)"}
.main { label.text = $0 }
Async
很像瀑布一樣, 由上至下的思考非同步的執行流程.
Async
沒有對錯誤拋出處理 throws
在研究了一段時間發現, Async
很可惜的沒有對 Error handle
做處理, 決定研究了一下, 修改部分的程式碼, fork repo.
private class Reference<T> {
var value: T?
/*新增*/ var error:Error?
/*新增*/ var queue:GCD?
}
public struct AsyncBlock<In, Out> {
...
private static func async<O>(after seconds: Double? = nil,
block: @escaping () throws -> O,
queue: GCD) -> AsyncBlock<Void, O> {
let reference = Reference<O>()
/*新增*/ reference.queue = queue
let block = DispatchWorkItem(block: {
/*新增*/ do {
/*新增*/ reference.value = try block()
/*新增*/ }catch {
/*新增*/ reference.error = error
/*新增*/ }
})
...
}
private func chain<O>(after seconds: Double? = nil,
block chainingBlock: @escaping (Out) throws -> O,
queue: GCD) -> AsyncBlock<Out, O> {
let reference = Reference<O>()
/*新增*/ reference.queue = queue
let dispatchWorkItem = DispatchWorkItem(block: {
/*新增*/ guard let value = self.output_.value else {
/*新增*/ return reference.error = self.output_.error!
/*新增*/ }
/*新增*/ do {
/*新增*/ reference.value = try chainingBlock(value)
/*新增*/ } catch {
/*新增*/ reference.error = error
/*新增*/ }
})
...
}
...
}
如此一來, 就可以對 AsyncBlock
拓展 catch(_:)
/*新增*/
@discardableResult
public func `catch`(respondBlock: @escaping (Error) -> Void) -> AsyncBlock<In,Out> {
let queue = output_.queue!.queue
let c = {
if let error = self.output_.error {
respondBlock(error)
}
}
let item = DispatchWorkItem(block: c)
block.notify(queue: queue, execute: item)
return self
}
實作單元測試:
func testAsyncMainWithCatch() {
let expectation = self.expectation(description: "Expected on main queue")
var calledStuffAfterSinceAsync = false
Async.main {
try self.alwaysError()
}.catch { (error) in
#if targetEnvironment(simulator)
XCTAssert(Thread.isMainThread, "Should be on main thread (simulator)")
#else
XCTAssertEqual(qos_class_self(), qos_class_main())
#endif
XCTAssert(calledStuffAfterSinceAsync, "Should be async")
expectation.fulfill()
}
calledStuffAfterSinceAsync = true
waitForExpectations(timeout: timeMargin, handler: nil)
}
以上, 於 fork 的 GitHub project 內持續補上 test case! GitHub 連結: https://github.com/ytyubox/Async