Promsie的概念是我在學(xué)習(xí)前端時(shí)接觸到的概念,簡而言之就是避免地獄回調(diào),在異步編程時(shí)例如網(wǎng)絡(luò)請(qǐng)求時(shí),以往對(duì)于異步任務(wù)完成時(shí)的結(jié)果往往采取回調(diào)的方式,在OC中采取block的形式,swift和JavaScript則可以采用閉包的形式,但是一旦我們的業(yè)務(wù)比較復(fù)雜,就會(huì)出現(xiàn)地獄回調(diào),如下情況所示:
Promise的初步了解
我們來考慮下面的場(chǎng)景(有夸張的成分):
- 我們需要通過一個(gè)
url1從服務(wù)器加載一個(gè)數(shù)據(jù)data1,data1中包含了下一個(gè)請(qǐng)求的url2。 - 我們需要通過
data1取出url2,從服務(wù)器加載數(shù)據(jù)data2,data2中包含了下一個(gè)請(qǐng)求的url3。 - 我們需要通過
data2取出url3,從服務(wù)器加載數(shù)據(jù)data3,data3中包含了下一個(gè)請(qǐng)求的url4。 - 發(fā)送網(wǎng)絡(luò)請(qǐng)求
url4,獲取最終的數(shù)據(jù)data4。
在前端中我們會(huì)寫出如下代碼:
$.ajax('url1',function (data1){
$.ajax(data1['url2'],function (data2){
$.ajax(data2['url3'],function (data3){
$.ajax(data3['url4'],function (data4){
console.log(data4);
})
})
})
})
接下來我們看使用Promise后的效果,ES6開始提供Promise特性的支持:
new Promise((resolve, reject) => {
$.ajax('url1',function (data1) {
resolve(data1)
})
}).then(data1 => {
return new Promise((resolve, reject) => {
$.ajax('data1[url2]',function (data2) {
resolve(data2)
})
})
}).then(data2 => {
return new Promise((resolve, reject) => {
$.ajax('data2[url3]',function (data3) {
resolve(data3)
})
})
}).then(data3 => {
return new Promise((resolve, reject) => {
$.ajax('data3[url4]',function (data4) {
console.log(data4)
})
})
})
經(jīng)過Promise操作后,復(fù)雜嵌套異步操作的回調(diào)結(jié)構(gòu)變得清晰了許多,通過resolve這個(gè)閉包將請(qǐng)求的結(jié)果返回到外層,通過鏈?zhǔn)?strong>then方法的調(diào)用拿到上層resolve返回的結(jié)果,再次通過返回一個(gè)Promise對(duì)象并對(duì)拿到的結(jié)果進(jìn)行一些需要的異步操作,這樣的結(jié)構(gòu)使得異步任務(wù)清晰了然。
Swift 中Promise的實(shí)現(xiàn)
在Swift中已有成熟的PromiseKit三方庫支持Promise,本文主要是分析此文Swift 中實(shí)現(xiàn) Promise 模式手寫的一個(gè)簡易的Promise模式的實(shí)現(xiàn),主要是利用Swift的閉包以及鏈?zhǔn)秸{(diào)用的思想來實(shí)現(xiàn),由于原文已有實(shí)現(xiàn)原理,那么廢話不多說直接上代碼進(jìn)行分析,代碼里標(biāo)有注釋,同時(shí)本文代碼對(duì)原文代碼進(jìn)行了一定的修改,因?yàn)樵牡拇a有點(diǎn)小問題:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
delay(secs: 5)
.then { (string) -> Promise<Data?> in
// 拿到結(jié)果進(jìn)行處理,并進(jìn)行向下傳遞
self.fetch(URL: URL(string: string)!)
}
.then { (data) -> Promise<String> in
// 拿到結(jié)果進(jìn)行處理,并進(jìn)行向下傳遞
self.decodeToString(data: data)
}
.startTask(success: { result in
print(result + "-------------")
}, failed: { Error in
print(Error)
})
}
func delay(secs: TimeInterval = 3) -> Promise<String> {
// 這里是swift的尾隨閉包寫法,直接調(diào)用了init初始化方法返回了一個(gè)promise對(duì)象
// 這個(gè)promise對(duì)象的self.task為傳入的閉包
return Promise<String> { (resolve, _) in
DispatchQueue.main.asyncAfter(deadline: .now() + secs) {
// 異步操作成功調(diào)用resolve回調(diào),失敗調(diào)用reject,這里只模擬成功的情況
resolve("https://www.zhihu.com1")
};
}
}
func fetch(URL: URL) -> Promise<Data?> {
return Promise<Data?> { (resolve, reject) in
let request = URLRequest(url: URL)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if (error != nil) {
reject(error!)
} else {
/* 這里的resolve其實(shí)就是{ self.resolve(result: $0) } 這個(gè)閉包里的resolove是函數(shù),會(huì)調(diào)用閉包 self.resolveCallback?(result) 而resolveCallback就是調(diào)用startTask傳入的閉包{ resolve($0) }
而最后的這個(gè)resolve就是后面調(diào)用then確定的也就是:這里的result為網(wǎng)址返回值
(result) in
let wrapped = f(result)
wrapped.success { resolve($0) }
*/
resolve(data)
}
}
task.resume()
}
}
func decodeToString(data: Data?) -> Promise<String> {
return Promise<String> { (resolve, _) in
if (data == nil) {
resolve("")
} else {
/*
這里的resolve其實(shí)就是{ self.resolve(result: $0) } 這個(gè)閉包里的resolove是函數(shù),會(huì)調(diào)用閉包 self.resolveCallback?(result) 而resolveCallback就是調(diào)用success傳入的閉包{ resolve($0) }
而最后的這個(gè)startTask確定的:這里的resolve傳入的值是String函數(shù)處理過的
(result) in
print(result + "--------------")
所以進(jìn)行了最后的打印
*/
resolve(String(data: data!, encoding: String.Encoding.utf8) ?? "")
}
}
}
}
class Promise<T> {
typealias ResolveCallback = (T) -> Void
typealias RejectCallback = (Error) -> Void
typealias AsyncTask = (@escaping ResolveCallback,@escaping RejectCallback) -> Void
let task: AsyncTask
var resolveCallback: ResolveCallback?
var rejectCallback: RejectCallback?
init(_ task: @escaping AsyncTask) {
self.task = task
}
private func resolve(result: T) {
self.resolveCallback?(result)
}
private func reject(error: Error) {
self.rejectCallback?(error)
}
func startTask(success: @escaping ResolveCallback,failed:@escaping RejectCallback) {
self.resolveCallback = success
self.rejectCallback = failed
self.task({ self.resolve(result: $0) }, { self.reject(error: $0) })
}
func then<U>(f:@escaping (T) -> Promise<U>) -> Promise<U> {
return Promise<U> { (resolve, reject) in
self.task(
{ (result) in
// result = ”“ 和String函數(shù)處理過的值
let wrapped = f(result)
// wrapped是promise對(duì)象,通過調(diào)用startTask來執(zhí)行內(nèi)部異步任務(wù),然后回調(diào)了then內(nèi)部Promise的reslove,而這個(gè)resolve和failed又會(huì)由下一個(gè)then來確定
wrapped.startTask(success: {resolve($0)}, failed: {reject($0)})
},
{ (error) in
reject(error)
})
}
}
}
代碼分析
promise類結(jié)構(gòu)分析
- 首先是創(chuàng)建了一個(gè)
promise的類,此類初始化需要傳入一個(gè)代表異步任務(wù)的閉包,異步任務(wù)的閉包有二個(gè)參數(shù),二個(gè)參數(shù)也為二個(gè)閉包,且參數(shù)都采用了泛型。 -
startTask方法用來啟動(dòng)異步任務(wù),也稱之為冷啟動(dòng),只有調(diào)用了startTask才會(huì)啟動(dòng)Promise的異步任務(wù)。 -
then方法是用來鏈?zhǔn)秸{(diào)用的,當(dāng)promise對(duì)象調(diào)用then方法時(shí)會(huì)依然返回一個(gè)promise對(duì)象,這樣可以繼續(xù)調(diào)用then方法繼而鏈?zhǔn)秸{(diào)用下去。
代碼執(zhí)行流程分析
- 在調(diào)用
delay(secs: 5)方法時(shí),返回的是一個(gè)promise對(duì)象,這個(gè)promise傳入了一個(gè)閉包代表異步任務(wù),并用self.task進(jìn)行了保存,這里異步任務(wù)采用延時(shí)的方式進(jìn)了模擬,實(shí)際開發(fā)中可能為網(wǎng)絡(luò)請(qǐng)求,同時(shí)此閉包有二個(gè)參數(shù),分別為resolve和reject,分別代表異步任務(wù)成功時(shí)和失敗時(shí)的回調(diào),也就是上面promise類結(jié)構(gòu)分析中的第一點(diǎn),且由于沒有失敗的情況,這里直接調(diào)用了resolve成功時(shí)的回調(diào)。 -
delay(secs: 5)返回的promise對(duì)象的異步任務(wù)需要傳入的resolve和reject只有在調(diào)用的時(shí)候才能確定,何時(shí)開始調(diào)用呢?其實(shí)對(duì)于promise對(duì)象來說,啟動(dòng)異步任務(wù)的方法只有一個(gè)就是調(diào)用startTask, 在鏈?zhǔn)秸{(diào)用then方法時(shí)返回的依然是一個(gè)promise對(duì)象,而這個(gè)promise對(duì)象不同點(diǎn)在于其異步任務(wù)是執(zhí)行了調(diào)用then前的promise對(duì)象的self.task并傳遞了成功時(shí)回調(diào)和失敗時(shí)回調(diào)二個(gè)閉包,而傳遞的這個(gè)成功時(shí)的閉包則很有講究,當(dāng)then之前的promise對(duì)象執(zhí)行resolve時(shí),會(huì)拿到這個(gè)promise對(duì)象異步執(zhí)行的結(jié)果,然后將這個(gè)參數(shù)順勢(shì)傳入執(zhí)行了調(diào)用then方法時(shí)傳入的閉包f并執(zhí)行,此時(shí)依然返回一個(gè)promise對(duì)象,我們想要的是執(zhí)行這個(gè)promise對(duì)象內(nèi)部的異步任務(wù),并且把異步任務(wù)的結(jié)果拋出來,怎么辦?肯定還是得調(diào)動(dòng)startTask呀,為了將結(jié)果從調(diào)用then產(chǎn)生的promise對(duì)象拋出去,這里的startTask傳入的二個(gè)回調(diào)閉包則要主動(dòng)調(diào)用調(diào)用then時(shí)初始化promsie時(shí)定義的resolve和reject。 -
then的鏈?zhǔn)秸{(diào)用就好像用炸藥開山時(shí),用引線將炸藥包首尾相連,直到最后一個(gè)炸藥包埋進(jìn)山體的深度達(dá)到了炸開山體的深度,則點(diǎn)燃第一個(gè)炸藥的引線,炸藥環(huán)環(huán)爆炸炸開山體。promise對(duì)像通過then相連,當(dāng)掛載startTask時(shí)則會(huì)執(zhí)行第一個(gè)promise對(duì)象的異步任務(wù),并將異步任務(wù)結(jié)果向下傳遞到下一個(gè)promise對(duì)象。
總結(jié)
Promise能讓多層嵌套的異步任務(wù)結(jié)構(gòu)變得更加清晰,本文是實(shí)現(xiàn)了Swift的promise方式,和ES6中的promise使用起來還是稍微麻煩點(diǎn),但是核心思想就是鏈?zhǔn)秸{(diào)用配合閉包來實(shí)現(xiàn),仔細(xì)體會(huì)會(huì)發(fā)現(xiàn)別有洞天,當(dāng)然也可以直接用三方框架簡單明了。
