iOS中的Promise

10,054 阅读7分钟

前言

在iOS中我们一般使用delegate(代理)或者block(闭包)来进行异步操作,当要执行多个异步操作,必须将第二个嵌套在第一个的完成内,并且还要正常处理错误。这使得代码结构异常的混乱,不方便查看。

相信码过JS的同学都清楚,在es6中新增promise的语法,从此异步操作更加的灵活,它使得回调与事件进行了组合,而且写法优美。谷歌在18年上半年开源了promises库,使iOS上的异步编程更加便捷,其使用方式与JS中的promise一致,并支持OC与Swift。

介绍

Promise代表异步执行的结果或者错误信息。其拥有以下三种状态

  • pending 正在处理
  • fulfilled 处理完成得到的结果
  • rejected 发生错误的信息

当状态发生变更后,就无法再次变更其状态。同时promise不限制其订阅者,当状态发生变化后,所有的订阅者都能接收到通知。同时订阅者可以返回另一个promise对象,从而形成管道通信。

管道通信过程可以自由的使用线程

原理分析

从上图中可以得知目前支持的操作,我们可以在FBLPromise文件查找Promise的实现原理

声明状态、闭包

//三种状态类型
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;
}

完成或失败的操作

完成或失败的操作其实是遍历所有订阅者,将当前的状态和执行后的结果通知订阅者。执行完毕后改变缓存的状态值,就算后期再次调用也不会响应。达到只要状态变更就无法再次启动的效果。

- (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的原理,也是实现管道通信的原理

- (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)
}