iOS中的Promise

前言

在iOS中咱們通常使用delegate(代理)或者block(閉包)來進行異步操做,當要執行多個異步操做,必須將第二個嵌套在第一個的完成內,而且還要正常處理錯誤。這使得代碼結構異常的混亂,不方便查看。git

相信碼過JS的同窗都清楚,在es6中新增promise的語法,今後異步操做更加的靈活,它使得回調與事件進行了組合,並且寫法優美。谷歌在18年上半年開源了promises庫,使iOS上的異步編程更加便捷,其使用方式與JS中的promise一致,並支持OC與Swift。es6

介紹

Promise表明異步執行的結果或者錯誤信息。其擁有如下三種狀態github

  • pending 正在處理
  • fulfilled 處理完成獲得的結果
  • rejected 發生錯誤的信息

當狀態發生變動後,就沒法再次變動其狀態。同時promise不限制其訂閱者,當狀態發生變化後,全部的訂閱者都能接收到通知。同時訂閱者能夠返回另外一個promise對象,從而造成管道通訊。編程

管道通訊過程能夠自由的使用線程swift

原理分析

從上圖中能夠得知目前支持的操做,咱們能夠在FBLPromise文件查找Promise的實現原理api

聲明狀態、閉包

//三種狀態類型
typedef NS_ENUM(NSInteger, FBLPromiseState) {
  FBLPromiseStatePending = 0,
  FBLPromiseStateFulfilled,
  FBLPromiseStateRejected,
};
//閉包的回調類型
typedef void (^FBLPromiseObserver)(FBLPromiseState state, id __nullable resolution);```
複製代碼

相關的緩存

@implementation FBLPromise {
  //當前promise的狀態
  FBLPromiseState _state;
  //存放執行相關邏輯的結果存儲
  id __nullable _value;
  //存放執行相關邏輯的錯誤
  NSError *__nullable _error;
  //存放訂閱者的信息
  NSMutableArray<FBLPromiseObserver> *_observers;
}
複製代碼

完成或失敗的操做

完成或失敗的操做實際上是遍歷全部訂閱者,將當前的狀態和執行後的結果通知訂閱者。執行完畢後改變緩存的狀態值,就算後期再次調用也不會響應。達到只要狀態變動就沒法再次啓動的效果。promise

- (void)fulfill:(nullable id)value {
  if ([value isKindOfClass:[NSError class]]) {
    [self reject:(NSError *)value];
  } else {
    @synchronized(self) {
      if (_state == FBLPromiseStatePending) {
        //變動狀態
        _state = FBLPromiseStateFulfilled;
        _value = value;
        _pendingObjects = nil;
        //通知訂閱者所訂閱的信息
        for (FBLPromiseObserver observer in _observers) {
          observer(_state, _value);
        }
        _observers = nil;
        dispatch_group_leave(FBLPromise.dispatchGroup);
      }
    }
  }
}
複製代碼

訂閱者

訂閱相關信息時,先去判斷當前的狀態類型,當此時已經執行完畢後,會立刻回調相關的執行結果。緩存

- (void)observeOnQueue:(dispatch_queue_t)queue
               fulfill:(FBLPromiseOnFulfillBlock)onFulfill
                reject:(FBLPromiseOnRejectBlock)onReject {
  @synchronized(self) {
    //先判斷當前的狀態,只有正在執行的過程當中才能夠訂閱
    switch (_state) {
      case FBLPromiseStatePending: {
        if (!_observers) {
          _observers = [[NSMutableArray alloc] init];
        }
        //增長一個閉包,採用閉包的方式保存外部變量環境
        [_observers addObject:^(FBLPromiseState state, id __nullable resolution) {
          dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{
            switch (state) {
              case FBLPromiseStatePending:
                break;
              case FBLPromiseStateFulfilled:
                onFulfill(resolution);
                break;
              case FBLPromiseStateRejected:
                onReject(resolution);
                break;
            }
          });
        }];
        break;
      }
      case FBLPromiseStateFulfilled: {
        dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{
          onFulfill(self->_value);
        });
        break;
      }
      case FBLPromiseStateRejected: {
        dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{
          onReject(self->_error);
        });
        break;
      }
    }
  }
}
複製代碼

此處是整個promise操做的重中之重,也是咱們promise中使用最多的原理所在,從FBLPromise+Then中能夠看出該方法是then的原理,也是實現管道通訊的原理bash

- (FBLPromise *)chainOnQueue:(dispatch_queue_t)queue
              chainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfill
               chainedReject:(FBLPromiseChainedRejectBlock)chainedReject {

  //建立新的promise
  FBLPromise *promise = [[FBLPromise alloc] initPending];
  //定義執行的閉包,該閉包是管道通訊的精華,也是爲何下一個then中能收到當前then中返回promise執行結果的關鍵
  __auto_type resolver = ^(id __nullable value) {
    //判斷then返回的是不是promise
    if ([value isKindOfClass:[FBLPromise class]]) {
      //訂閱then返回的promise,主要是爲了調用下一個then
      [(FBLPromise *)value observeOnQueue:queue
          fulfill:^(id __nullable value) {
            [promise fulfill:value];
          }
          reject:^(NSError *error) {
            [promise reject:error];
          }];
    } else {
      //若返回的不是promise中,則立刻傳給下一個then
      [promise fulfill:value];
    }
  };
  //訂閱當前的promise
  [self observeOnQueue:queue
      fulfill:^(id __nullable value) {
        //執行then操做後的返回值,多是promise也多是其餘值
        value = chainedFulfill ? chainedFulfill(value) : value;
        //調用上面建立的閉包
        resolver(value);
      }
      reject:^(NSError *error) {
        id value = chainedReject ? chainedReject(error) : error;
        resolver(value);
      }];
  return promise;
}
複製代碼

使用方法

此處僅展現swift的語法,oc上的使用請參考FBL前綴的類,或者直接閱讀官方文檔閉包

建立方式

默認是在主線程中使用,如需在其餘線程中使用,能夠設置on: DispatchQueue,此處不做介紹

異步方式

let promise = Promise<Int> { fulfill, reject in
    let vail = true
    if vail {
        fulfil(12)
    }else {
        reject(error)
    }
}
複製代碼

同步方式

//採用預先定義方式
let promise = Promise<Int>.pending()
...
if success {
  promise.fulfill(12)
} else {
  promise.reject(error)
}

//直接發起結果的方式
let promise = Promise(12)
let promise = Promise(error)

複製代碼

then使用

let numberPromise = Promise { fulfill, _ in
    fulfill(42)
}
let stringPromise = numberPromise.then { number in
    return Promise { fulfill, _ in
        fulfill(String(number))
    }
}
typealias Block = (Int) -> [Int]
let blockPromise = stringPromise.then { value in
    return Promise<Block> { fulfill, _ in
        fulfill({ number in
            return [number + (Int(value) ?? 0)]
        })
    }
}
let finalPromise = blockPromise.then { (value: @escaping Block) -> Int? in
    return value(42).first
}
let postFinalPromise = finalPromise.then { number in
    return number ?? 0
}
複製代碼

Catch

catch是處理promise中出現錯誤的狀況,在promise管道中,當其中一個promise執行出現錯誤時,會忽略這個以後的管道直接將錯誤傳輸到catch中處理。

let promise = Promise<AnyObject> {
    return error
}.catch { error in
    //處理錯誤
}
複製代碼

操做符

All

只有當all中全部的promise否成功執行後纔回調,回調參數未all中全部執行結果的元組,當其中一個調用rejected時,都直接回調錯誤

let promise1 = Promise<Int?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 1, execute: {
        fulfill(42)
    })
}
let promise2 = Promise<Int?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
        fulfill(13)
     })
}
let promise3 = Promise<Int?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
        fulfill(nil)
    })
}
        
let combinedPromise = all([promise1, promise2, promise3]).then { value in
    print(value) //[Optional(42), Optional(13), nil]
}
複製代碼

Always

不管管道中是順利成功執行仍是出現錯誤,最終都會執行always的閉包

var count = 0
let promise = Promise<Void> { _, reject in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
        reject(DYError.promise)
    })
}.always {
    count += 1
}.catch { error in
    count += 1
}.always {
    print(count)
}
複製代碼

Any

all操做類型,可是Any中即使有其中的出現了錯誤也會返回元組數據嗎,其元組類型是Maybe,包含errorvalue相關信息

let promise1 = Promise<Int?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
        fulfill(42)
    })
}
let promise2 = Promise<Int?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
        fulfill(13)
    })
}
let promise3 = Promise<Int?> { _, reject in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 1, execute: {
        reject(DYError.promise)
    })
}
        
let combinedPromise = any([promise1, promise2, promise3]).then { value in
    let item = value.first
    print(value.first?.value)
}
複製代碼

Await

使用該操做,能夠同步等待在不一樣線程上執行的promise。該語法與ES8中async/await的使用時相似的

Promise<Int> {
  let minusFive = try await(calculator.negate(5))
  let twentyFive = try await(calculator.multiply(minusFive, minusFive))
  let twenty = try await(calculator.add(twentyFive, minusFive))
  let five = try await(calculator.subtract(twentyFive, twenty))
  let zero = try await(calculator.add(minusFive, five))
  return try await(calculator.multiply(zero, five))
}.then { result in
  
}.catch { error in
  
}
複製代碼

Delay

該操做返回一個預先定義的promise,等到給定的時間後執行,或者當即執行錯誤

let promise = Promise(42).delay(2)
promise.catch { err in
    print(err)
}.then { value in
    print(value)
}

DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 1, execute: {
    promise.reject(DYError.promise)
})
複製代碼

Race

該操做與all類似,可是返回的是先執行完成的promise結果或者錯誤

let promise1 = Promise<Any?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
        fulfill(42)
    })
}
let promise2 = Promise<Any?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
        fulfill("hello world")
    })
}
let promise3 = Promise<Any?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
        fulfill([44])
    })
}
let promise4 = Promise<Any?> { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
        fulfill(nil)
    })
}
        
race([promise1,promise2,promise3,promise4]).then { (value) in
    print(value)
}
複製代碼

Recover

Catch效果相同,可是該操做符不會隔斷管道中其餘promise的執行

let promise = Promise<Int> { _, reject in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
        reject(DYError.promise)
    })
}.recover { error -> Promise<Int> in
    print(error)
    return Promise { fulfill, _ in
            DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
                    fulfill(1)
             })
    }
}.catch { err in
    print(err)
}.then { value in
    print(value)
}
複製代碼

Reduce

結合其餘數據進行變換

let numbers = [1, 2, 3]
Promise("0").reduce(numbers) { partialString, nextNumber in
  Promise(partialString + ", " + String(nextNumber))
}.then { string in
  // Final result = 0, 1, 2, 3
  print("Final result = \(string)")
}
複製代碼

Retry

promise執行失敗時,嘗試從新執行。在未設置時間的前提下,默認延遲1s從新執行

var count = 0
retry(attempts: 3, delay: 5, condition: { (num, error) -> Bool in
    print("\(num)" + error.localizedDescription)
    if num == 2 {
        //立刻執行錯誤
        return false
    }
    return true
}) { () -> Promise<Int> in
    return count == 3 ? Promise(42) : Promise(DYError.promise)
}.then { (value) in
    print(value)
}.catch { (error) in
    print(error)
}
複製代碼

Timeout

在指定的時間內若是沒有執行完成或者發送錯誤,則自發錯誤

let promise = Promise { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
        fulfill(42)
    })
}.timeout(1).catch { err in
    print(err) //timedOut
}.then { value in
    print(value) 
}
複製代碼

Validate

判斷是否有效,若是無效則自發錯誤

let promise = Promise { fulfill, _ in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
        fulfill(42)
    })
}.validate { value in
    return value == 1
}.catch { err in
    print(err) //validationFailure
}.then { value in
    print(value)
}
複製代碼
相關文章
相關標籤/搜索