@xifenglang-33250
2019-01-22T03:34:42.000000Z
字数 27814
阅读 4173
源码学习
Promise旨在链接多个异步任务,实现链式or响应式编程,能有效解决回调地狱CallBack Hell,在JavaScript应用已经比较常见,概念也趋于成熟,核心概念可了解维基文章:Promise和Future,另外一篇JavaScript Promise A+中文翻译。Google Promise的开发者之一shoumikhin提到一个概念a promise (as an asynchronous provider) and a future (as an asynchronous return object),我理解为promise是处理异步任务的容器,外部将异步任务放在promise执行,future是异步任务的返回值(NSError或其他类型对象)。promise不改变future的值,只会根据future进行鉴权改变promise自身的状态state,已经bind操作(_value or _error)。promise的状态有3种,Pending待决议、待处理,Fulfilled已处理且已通过决议,future合法,Rejected已处理但未通过决议,future非法。promise虽然是基于GCD实现异步任务容器,但是其内部的成员变量都通过@synchronized(self)加锁实现线程安全。promise的三种状态,Pending只能转变成Fulfilled或者Rejected,promise的状态变成Fulfilled或者Rejected后,其状态state、value和error都不会被再次更改。可以理解Promise是一次性的异步任务容器。
/** All states a promise can be in. */typedef NS_ENUM(NSInteger, FBLPromiseState) {FBLPromiseStatePending = 0,FBLPromiseStateFulfilled,FBLPromiseStateRejected,};
initPending实例化一个Pending的promise,通过fulfill:传入future,promise鉴权后更新自己的state,如果future是NSError类型,就会执行reject:否决,状态改成Rejected,如果是非NSError类型值(anyObject or nil),状态变成Fulfilled。而其中的for (FBLPromiseObserver observer in _observers)又是一个非常关键的代码,其它的observer(可以理解是其它promise的替身,observer是多层嵌套的block,在block底层实现里会调用 [anotherPromise fulfill:value],以此实现promise传递链nesting promises)。
- (instancetype)initPending {self = [super init];if (self) {// 默认行为 _state = FBLPromiseStatePending;dispatch_group_enter(FBLPromise.dispatchGroup);}return self;}- (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)reject:(NSError *)error {@synchronized(self) {if (_state == FBLPromiseStatePending) {_state = FBLPromiseStateRejected;_error = error;// _pendingObjects = nil;for (FBLPromiseObserver observer in _observers) {observer(_state, _error);}_observers = nil;dispatch_group_leave(FBLPromise.dispatchGroup);}}}- (void)dealloc {if (_state == FBLPromiseStatePending) {dispatch_group_leave(FBLPromise.dispatchGroup);}}
observeOnQueue: fulfill: reject:的作用是增加监听者,只不过通过block(onFulfill and onReject)实现回调传值。前面说过promise的状态一旦变成Fulfilled或者Rejected,其状态和值都不会再改变,所以会直接调用onFulfill(self.value);或者onReject(self.error);,将其状态和值传递给其它的监听者another promise obj,如果当前的promise还是Pending状态,会添加到当前promise的NSMutableArray<FBLPromiseObserver> *_observers;监听者数组中,最后在上面的fulfill:或reject:中会for循环调用observer(_state, _value); notify通知各个监听者。
另外一个需要注意的地方就是dispatch_group_enter(FBLPromise.dispatchGroup);、dispatch_group_leave(FBLPromise.dispatchGroup);,FBLPromise.dispatchGroup是个全局静态gorup,promise的异步任务都是放在dispatch_async_group执行,而前面的enter+leave等效于dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{}。而Promise的默认队列是主队列,即在主线程执行,调用onQueue开头的方法可传入自定义队列,以充分发挥多线程的作用。
- (void)observeOnQueue:(dispatch_queue_t)queuefulfill:(FBLPromiseOnFulfillBlock)onFulfillreject:(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 pipelinepromise链,可以实现类似响应式编程的样式。其核心代码在下面的方法中chainOnQueue: chainedFulfill: chainedReject:,这个方法会返回一个监听当前promose的新promise。在这个方法中首先实例化anotherPromise,通过observeOnQueue: fulfill: reject:实现对当前promise的监听,在notify通知回调block中调用[promise fulfill:value];对新的promise进行决议。在这个过程中实现2个promise的链接,以及值传递。
- (FBLPromise *)chainOnQueue:(dispatch_queue_t)queuechainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfillchainedReject:(FBLPromiseChainedRejectBlock)chainedReject {FBLPromise *promise = [[FBLPromise alloc] initPending];[self observeOnQueue:queuefulfill:^(id __nullable value) {value = chainedFulfill ? chainedFulfill(value) : value;[promise fulfill:value];}reject:^(NSError *error) {id value = chainedReject ? chainedReject(error) : error;[promise fulfill:value];}];return promise;}
FBLPromiseAsyncWorkBlock work,work放在group异步执行, 返回了2个block参数给外部调用者,外部调用fulfill(value)或者reject(error)其本质还是[promise fulfill:value]; 即向promise提供future/resolution用于决议鉴权,以此完成一个promise的使用周期。
typedef void (^FBLPromiseFulfillBlock)(Value __nullable value);typedef void (^FBLPromiseRejectBlock)(NSError *error);typedef void (^FBLPromiseAsyncWorkBlock)(FBLPromiseFulfillBlock fulfill,FBLPromiseRejectBlock reject)
@implementation FBLPromise (AsyncAdditions)+ (instancetype)onQueue:(dispatch_queue_t)queue async:(FBLPromiseAsyncWorkBlock)work {FBLPromise *promise = [[FBLPromise alloc] initPending];dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{work(^(id __nullable value) {[promise fulfill:value];},^(NSError *error) {[promise reject:error];});});return promise;}
__unused FBLPromise <NSNumber *>* numberPromise = [FBLPromise onQueue:FBLPromise.defaultDispatchQueue async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {// 通过fulfill或者reject提供future/resolutionfulfill(nil);reject([NSError errorWithDomain:NSCocoaErrorDomain code:11 userInfo:@{NSURLErrorFailingURLErrorKey:@"failed"}]);}];
do:是async:的精简版,即将fulfill()和reject()2个block整合到一个block中,这个FBLPromiseDoWorkBlock仍是传递future/resolution。核心是[promise fulfill:value];这个方法会判断future/resolution是不是NSError类型,如果是NSError类型,会调用reject:方法,不是的情况才会正常走fulfill流程。
+ (instancetype)onQueue:(dispatch_queue_t)queue do:(FBLPromiseDoWorkBlock)work {FBLPromise *promise = [[FBLPromise alloc] initPending];dispatch_group_async(FBLPromise.dispatchGroup, queue, ^{id value = work();[promise fulfill:value];});return promise;}
Promise执行点语法,但是在OC上由于Xcode联想的问题,使用上没有直接调用方法顺手,也许能通过代码块弥补Xcode类型补全的不足。
// 1.__unused FBLPromise * doPromise = [FBLPromise onQueue:FBLPromise.defaultDispatchQueue do:^id _Nullable{// 返回resolutionreturn [NSError errorWithDomain:NSCocoaErrorDomain code:12 userInfo:@{NSURLErrorFailingURLErrorKey:@"do"}];return successResolution;}];// 2.同1FBLPromise * dispatch_promise(FBLPromiseDoWorkBlock work){return [FBLPromise onQueue:FBLPromise.defaultDispatchQueue do:work];}dispatch_promise(^id _Nullable{// 返回resolutionreturn [NSError errorWithDomain:NSCocoaErrorDomain code:12 userInfo:@{NSURLErrorFailingURLErrorKey:@"do"}];return successResolution;}).then(^id _Nullable(id _Nullable value) {return value;});
用一个future/resolution快速创建一个promise
- (instancetype)initWithResolution:(nullable id)resolution {self = [super init];if (self) {if ([resolution isKindOfClass:[NSError class]]) {_state = FBLPromiseStateRejected;_error = (NSError *)resolution;} else {_state = FBLPromiseStateFulfilled;_value = resolution;}}return self;}
可用一个已知的future,也可以是直接计算返回的值,去创建一个fulfilled或者rejected promise,一般作为promise pipeline的链头。
[FBLPromise resolvedWith:[NSError errorWithDomain:NSCocoaErrorDomain code:13 userInfo:nil]];[FBLPromise resolvedWith:anURL.absoluteString]
then:是精简版的chainOnQueue: chainedFulfill: chainedReject:,then:对应的是chainedFulfill:,即fulfill()。如果前面被监听的promise 状态state是fulfilled,才会调用then对应的的FBLPromiseThenWorkBlock,即promise通过决议时才被调用。如果是rejected,则不会走then对应的block。then可以理解成用于捕获前面异步任务的成功fulfilled value
- (FBLPromise *)onQueue:(dispatch_queue_t)queue then:(FBLPromiseThenWorkBlock)work {return [self chainOnQueue:queue chainedFulfill:work chainedReject:nil];}
then中需要返回future/resolution,可以是error或者value(非NSError对象)。通过promise pipeline,chainedStringPromise的value会是then返回的future。
FBLPromise <NSString *>* chainedStringPromise = [aFulfilledPromise then:^id _Nullable(NSData * _Nullable value) {return @"@then return a new resolution";}];[[[resolvedPromise then:^id _Nullable(NSString * _Nullable value) {return @"then pipeline";}] then:^id _Nullable(id _Nullable value) {return value;}] then:^id _Nullable(id _Nullable value) {return value;}];
catch: 和 then: 有点对立的意思,then只在前面的future通过决议(fulfilled promise)时才会调用,catch是未通过决议(rejected promise)时才被调用,catch可直接理解成前面异步任务抛出的异常error。
- (FBLPromise *)onQueue:(dispatch_queue_t)queue catch:(FBLPromiseCatchWorkBlock)reject {return [self chainOnQueue:queuechainedFulfill:nilchainedReject:^id(NSError *error) {reject(error);return error;}];}
虽然catch不返回新future值,返回源码中可发现return error,即error会被传递到后面的promise。所以多个catch嵌套也可以运行。
[[doPromise catch:^(NSError * _Nonnull error) {NSLog(@"catched an error: %@",error);}] catch:^(NSError * _Nonnull error) {NSLog(@"catched second error: %@",error);}];doPromise.catch(^(NSError * _Nonnull error){NSLog(@"catched an error: %@",error);}).catch(^(NSError * _Nonnull error){NSLog(@"catched second error: %@",error);});
主要做了几件事
[[FBLPromise alloc] initWithResolution:obj],而且最终promises数组里的promise要么是fulfilled要么是rejected。[promise observeOnQueue: fulfill: reject];监听每个promise,由于promises数组的promise已经是fulfilled或者rejected,所以监听的fulfill或者reject回调会马上被调用。所以promises数组的元素有多少个,observe回调就会被调用多少次。但是promise是一次性的,状态一旦变成fulfilled或者rejected,就不能再改变状态或者绑定值。所以由all数组转换成的promises数组中有rejected promise,就会调用reject(error);,尽管可能会被多次调用,但是是一次性的原因,只有第一个rejected promise的error会生效,外部只能捕获到第一个error。便利promise的原因,reject可能会被调用多次,fulfill也会被回调多次,但是里面还有层for循环,只有promises数组的promise都是fulfilled状态,才会调用fulfill([promises valueForKey:NSStringFromSelector(@selector(value))]);,加上一次性的因素,外部的then会捕获跟all数组同值得数组。
+ (FBLPromise<NSArray *> *)onQueue:(dispatch_queue_t)queue all:(NSArray *)allPromises {NSParameterAssert(queue);NSParameterAssert(allPromises);if (allPromises.count == 0) {return [[FBLPromise alloc] initWithResolution:@[]];}NSMutableArray *promises = [allPromises mutableCopy];return [FBLPromiseonQueue:queueasync:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) {for (NSUInteger i = 0; i < promises.count; ++i) {id promise = promises[i];if ([promise isKindOfClass:self]) {continue;} else if ([promise isKindOfClass:[NSError class]]) {reject(promise);return;} else {[promises replaceObjectAtIndex:iwithObject:[[FBLPromise alloc] initWithResolution:promise]];}}for (FBLPromise *promise in promises) {// fulfill()和reject()会被调用多次,但只有第一次有效(被then或者catch捕获)[promise observeOnQueue:queuefulfill:^(id __unused _) {// Wait until all are fulfilled. 同时也是在这个地方筛选rejected promisefor (FBLPromise *promise in promises) {if (!promise.isFulfilled) {return;}}// If called multiple times, only the first one affects the result.fulfill([promises valueForKey:NSStringFromSelector(@selector(value))]);}reject:^(NSError *error) {reject(error);}];}}];}
promises的宗旨之一就是为了避免多层嵌套的promise(nesting promises 或者 callback hell回调地狱),比如下面的:
// 1.nesting promises[[self loadSomething] then:^id _Nullable(NSString * _Nullable somthing) {return [[self loadAnother] then:^id _Nullable(NSString * _Nullable another) {return [self doSomethingWith:somthing andAnother:another];}];}];
Promise提供了all来规避avoid nesting promises,同时又可以在多线程异步执行多个任务
[[FBLPromise all:@[[self loadSomething],[self loadAnother]]] then:^id _Nullable(NSArray * _Nullable value) {return [self doSomethingWith:value.firstObject andAnother:value.lastObject];}];
如果觉得all的可读性不强,换成方法嵌套,一层一层拆成独立的方法,合理的做法是一个方法包含一个then。但是这种做法还是会有多层嵌套,建议试着去简化all
[[self loadSomething] then:^id _Nullable(NSString * _Nullable value) {return [self loadAnotherWithSomething:value];}];
- (FBLPromise <NSString *>*)loadSomething {return FBLPromise.resolved(@"loadSomething");}- (FBLPromise <NSString *>*)loadAnother {return FBLPromise.resolved(@"loadAnother");}- (FBLPromise<NSString *> *)loadAnotherWithSomething:(NSString *)something {return [[self loadAnother] then:^id(NSString *another) {return [self doSomethingWith:something andAnother:another];}];}- (id)doSomethingWith:(NSString *)sth andAnother:(NSString *)ant {return [sth stringByAppendingString:ant];}
无论promise是fulfilled还是rejected,always都会被调用,只是不会带有promise绑定的数据,是个无参block
- (FBLPromise *)onQueue:(dispatch_queue_t)queue always:(FBLPromiseAlwaysWorkBlock)work {return [self chainOnQueue:queuechainedFulfill:^id(id value) {work();return value;}chainedReject:^id(NSError *error) {work();return error;}];}
regardless of fulfilled or rejected, always invoke
__unused FBLPromise * alwaysPromise = [[[[FBLPromise resolvedWith:@"always"] then:^id _Nullable(NSArray * _Nullable value) {NSLog(@"then : %@",value);return nil;}] catch:^(NSError * _Nonnull error) {NSLog(@"catch : %@",error);}] always:^{NSLog(@"alway invoke !");}];
[promises replaceObjectAtIndex: withObject:[[FBLPromise alloc] initWithResolution:promise]];将future/solution数组转成真正的promise数组。[promise observeOnQueue: fulfill: reject:],fulfill(FBLPromiseCombineValuesAndErrors(promises));更改[步骤2]实例的promise的状态(pending -> fulfilled),可以在外部调用then。
// 提取NSArray<FBLPromise *> *promises数组中的promise.value或者promise.error,重新包装成数组返回static NSArray *FBLPromiseCombineValuesAndErrors(NSArray<FBLPromise *> *promises) {NSMutableArray *combinedValuesAndErrors = [[NSMutableArray alloc] init];for (FBLPromise *promise in promises) {if (promise.isFulfilled) {[combinedValuesAndErrors addObject:promise.value ?: [NSNull null]];continue;}if (promise.isRejected) {[combinedValuesAndErrors addObject:promise.error];continue;}assert(!promise.isPending);};return combinedValuesAndErrors;}+ (FBLPromise<NSArray *> *)onQueue:(dispatch_queue_t)queue any:(NSArray *)anyPromises {NSMutableArray *promises = [anyPromises mutableCopy];return [FBLPromiseonQueue:queueasync:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) {for (NSUInteger i = 0; i < promises.count; ++i) {id promise = promises[i];if ([promise isKindOfClass:self]) {continue;} else {[promises replaceObjectAtIndex:iwithObject:[[FBLPromise alloc] initWithResolution:promise]];}}for (FBLPromise *promise in promises) {[promise observeOnQueue:queuefulfill:^(id __unused _) {// Wait until all are resolved.for (FBLPromise *promise in promises) {if (promise.isPending) {return;}}// If called multiple times, only the first one affects the result.fulfill(FBLPromiseCombineValuesAndErrors(promises));}reject:^(NSError *error) {BOOL atLeastOneIsFulfilled = NO;for (FBLPromise *promise in promises) {if (promise.isPending) {return;}if (promise.isFulfilled) {atLeastOneIsFulfilled = YES;}}if (atLeastOneIsFulfilled) {fulfill(FBLPromiseCombineValuesAndErrors(promises));} else {reject(error);}}];}}];}
上面提到了,any数组中的future/resolution要么全部非法rejected,能被catch;其它情况都可以调用then获取数组中的future,只不过需要区分error和其它值。any的应用场景非常少
allArr = @[errorResolution];[[[FBLPromise any:allArr] then:^id _Nullable(NSArray * _Nullable value) {NSLog(@"any : %@",value);// 需要解析数组里的数据,区分正常数据和errorif ([value.firstObject isKindOfClass:NSError.class]) {NSLog(@"dosomthing");} else if ([value.lastObject isKindOfClass:NSError.class]) {NSLog(@"dosomthing");}return nil;}] catch:^(NSError * _Nonnull error) {NSLog(@"any will never be catched : %@",error);}];
使用信号量机制dispatch_semaphore_t实现await,利用锁实现等待上一个promise被resolve。
id __nullable FBLPromiseAwait(FBLPromise *promise, NSError **outError) {assert(promise);static dispatch_once_t onceToken;static dispatch_queue_t queue;dispatch_once(&onceToken, ^{queue = dispatch_queue_create("com.google.FBLPromises.Await", DISPATCH_QUEUE_CONCURRENT);});dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);id __block resolution;NSError __block *blockError;[promise chainOnQueue:queuechainedFulfill:^id(id value) {resolution = value;dispatch_semaphore_signal(semaphore);return value;}chainedReject:^id(NSError *error) {blockError = error;dispatch_semaphore_signal(semaphore);return error;}];dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);if (outError) {*outError = blockError;}return resolution;}
FBLPromiseAwait内部是信号量机制,会锁当前的线程,所以FBLPromiseAwait()所在的线程和等待resolve的promise的执行线程尽量不要是同一线程,不然可能造成死锁。
如果FBLPromiseAwait在线程A上等待,FBLPromiseAwait等待的promise刚好在线程A上等待fulfill或者reject,就会导致死锁。promise的默认线程是主线程,所以要特别注意,比如下面的代码就会造成死锁,而且锁主线程。
[FBLPromise onQueue:FBLPromise.defaultDispatchQueue do:^id _Nullable{NSError * error;id result = FBLPromiseAwait([self someAsyncRoutine], &error);return error ?: result;}];// ====== Await 仿耗时任务- (FBLPromise *)someAsyncRoutine {dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);FBLPromise * promise = [FBLPromise pendingPromise];int64_t const timeToWait = (int64_t)(4 * NSEC_PER_SEC);dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeToWait),queue, ^{dispatch_async(FBLPromise.defaultDispatchQueue, ^{[promise fulfill:@"await"];});});return promise;}
所以尽量确保FBLPromiseAwait等待的所在线程和promise执行resolve的线程不是同一条,比如下面的用法。FBLPromiseAwait尽量不要在主线程执行,防止锁主线程
dispatch_queue_t asyncQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);[FBLPromise onQueue:asyncQueue do:^id _Nullable{NSError * error;id result = FBLPromiseAwait([self someAsyncRoutine], &error);return error ?: result;}];
其核心代码就是下面的dispatch_after,延迟调用[promise fulfill:value],所以外部的then会延迟响应,但是[promise reject:error];不会延迟调用,所以外部的catch会立刻调用。
- (FBLPromise *)onQueue:(dispatch_queue_t)queue delay:(NSTimeInterval)interval {FBLPromise *promise = [[FBLPromise alloc] initPending];[self observeOnQueue:queuefulfill:^(id __nullable value) {dispatch_after(dispatch_time(0, (int64_t)(interval * NSEC_PER_SEC)), queue, ^{[promise fulfill:value];});}reject:^(NSError *error) {[promise reject:error];}];return promise;}
可以实现一些延迟操作
FBLPromise * delayPromise = [[FBLPromise do:^id _Nullable{NSLog(@"开始调用%@",NSDate.date);return @"delay";}] delay:2.0];[delayPromise then:^id _Nullable(id _Nullable value) {NSLog(@"延迟2秒后调用%@",NSDate.date);return value;}];
async:里面虽然有2个for循环,功能是相同的,但由于promise是一次性的,所以race:中return的promise是会由数组中最先resolve的promise决定状态。注意:promise可能在多线程实现,最先resolve的不一定是数组第一个。
+ (instancetype)onQueue:(dispatch_queue_t)queue race:(NSArray *)racePromises {NSArray *promises = [racePromises copy];return [FBLPromise onQueue:queueasync:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock reject) {for (id promise in promises) {if (![promise isKindOfClass:self]) {fulfill(promise);return;}}// Subscribe all, but only the first one to resolve will change// the resulting promise's state.for (FBLPromise *promise in promises) {[promise observeOnQueue:queue fulfill:fulfill reject:reject];}}];}
有一种说法:多个异步任务是为了容错,比如,同时向两个URL读取用户的个人信息,只需要获得先返回的结果即可。
[[FBLPromise race:@[[FBLPromise resolvedWith:@"race1"],[FBLPromise resolvedWith:@"race2"]]] then:^id _Nullable(id _Nullable value) {NSLog(@"race: %@",value); // race: race1return value;}];
增加一个监听当前promise将要被reject的promise,即当前的promise将要被reject否决,会触发return recovery(error);,将error传给外部,并将recovery返回值继续返回给当前的promise作为future,以此实现纠错。
- (FBLPromise *)onQueue:(dispatch_queue_t)queue recover:(FBLPromiseRecoverWorkBlock)recovery {return [self chainOnQueue:queuechainedFulfill:nilchainedReject:^id(NSError *error) {return recovery(error);}];}
[[[self someErrorPromise] recover:^id _Nullable(NSError * _Nonnull error) {NSLog(@"handle some error :%@ ",error);return @"a fulfilled resolution";}] then:^id _Nullable(id _Nullable value) {NSLog(@"got a value %@",value);return value;}];
基于当前的promise和items数据源,创建一个扁平的promise链,promise <- promiseA <- promiseB <- promiseC ~。在for循环中将上一个promise的solution和当前item传入reducer(value, item),外部按照一定的规则生成新的value传给当前的promise。最终返回的promise是数组最后一个Item转换的promise。
- (FBLPromise *)onQueue:(dispatch_queue_t)queuereduce:(NSArray *)itemscombine:(FBLPromiseReducerBlock)reducer {FBLPromise *promise = self;for (id item in items) {promise = [promise chainOnQueue:queuechainedFulfill:^id(id value) {return reducer(value, item);}chainedReject:nil];}return promise;}
比如将数组拼接成一个扁平的字符串 @"0" + @[@1, @2, @3] -> @"0, 1, 2, 3"
NSArray<NSNumber *> *numbers = @[ @1, @2, @3 ];FBLPromise * aPromise = [FBLPromise resolvedWith:@"0"];[[aPromise reduce:numbers combine:^id(NSString *partialString, NSNumber *nextNumber) {// partialString == 0, return 0,1// partialString == 0,1, return 0,1,2// partialString == 0,1,2, return 0,1,2,3// 相当于转换成4个链接的promise,@0也是第一个// 0: [FBLPromise resolvedWith:@"0"] value: 0// 1-3 即数组转成成的promise链,fulfill(1 or 2 or 3)return [NSString stringWithFormat:@"%@, %@", partialString, nextNumber.stringValue];}] then:^id(NSString *string) {// then捕获的就是promises链的最后一个:@3// Final result = 0, 1, 2, 3NSLog(@"Final result = %@", string);return nil;}];
正常执行retry block一次,如果retry block返回了Error或者rejected promise,则predicate(count, value)条件允许下,延迟interval秒后启动retry重试count次。
promise:如果发生retry,会递归FBLPromiseRetryAttempt,最终调用[promise reject:value];或[promise fulfill:value];
count:retry的次数
predicate:即将发生retry前 (future是error),predicate(count, value)可以再次判断要不要retry,返回false将不发起retry,而是调用[promise reject:value]
interval: 默认延迟1秒发起retry(递归调用FBLPromiseRetryAttempt)
NSInteger const FBLPromiseRetryDefaultAttemptsCount = 1;NSTimeInterval const FBLPromiseRetryDefaultDelayInterval = 1.0;static void FBLPromiseRetryAttempt(FBLPromise *promise, dispatch_queue_t queue, NSInteger count,NSTimeInterval interval, FBLPromiseRetryPredicateBlock predicate,FBLPromiseRetryWorkBlock work) {__auto_type retrier = ^(id __nullable value) {if ([value isKindOfClass:[NSError class]]) {if (count <= 0 || (predicate && !predicate(count, value))) {[promise reject:value];} else {dispatch_after(dispatch_time(0, (int64_t)(interval * NSEC_PER_SEC)), queue, ^{FBLPromiseRetryAttempt(promise, queue, count - 1, interval, predicate, work);});}} else {[promise fulfill:value];}};id value = work();if ([value isKindOfClass:[FBLPromise class]]) {[(FBLPromise *)value observeOnQueue:queue fulfill:retrier reject:retrier];} else {retrier(value);}}+ (FBLPromise *)onQueue:(dispatch_queue_t)queueattempts:(NSInteger)countdelay:(NSTimeInterval)intervalcondition:(nullable FBLPromiseRetryPredicateBlock)predicateretry:(FBLPromiseRetryWorkBlock)work {FBLPromise *promise = [[FBLPromise alloc] initPending];FBLPromiseRetryAttempt(promise, queue, count, interval, predicate, work);return promise;}
如果第一次捕获到了error,默认重复1次,且延迟1秒后执行重复任务
NSURL *url = [NSURL URLWithString:@"https://www.json.org"];[[[FBLPromise retry:^id _Nullable{return [self fetchWithURL:url];}] then:^id _Nullable(id _Nullable value) {NSLog(@"retry then %@",value);return nil;}] catch:^(NSError * _Nonnull error) {NSLog(@"catched some error: %@",error);}];// 自定义retry的队列、次数、延迟时间、条件[[[FBLPromise onQueue:gQueue attempts:1 delay:1 condition:^BOOL(NSInteger count, NSError * _Nonnull value) {// condition 返回false直接走reject,被catch捕获,返回true,延迟后执行重复retryreturn true;} retry:^id _Nullable{return [self fetchWithURL:url];}] then:^id _Nullable(id _Nullable value) {NSLog(@"retry then %@",value);return nil;}] catch:^(NSError * _Nonnull error) {NSLog(@"catched some error: %@",error);}];;- (FBLPromise <NSArray *> *)fetchWithURL:(NSURL *)url {return [FBLPromise wrap2ObjectsOrErrorCompletion:^(FBLPromise2ObjectsOrErrorCompletion _Nonnull handler) {// [[NSURLSession.sharedSession dataTaskWithURL:url completionHandler:handler] resume];handler(nil, nil, [NSError errorWithDomain:NSCocoaErrorDomain code:100 userInfo:@{NSURLErrorFailingURLErrorKey:@"fetchWithURL Error"}]);}];}
可见在等待promise被resolve时启用了一个延迟任务,因为promise是一次性的,所以promise超时就会先触发dispatch_after,并返回一个自定义的error(FBLPromiseErrorDomain and FBLPromiseErrorCodeTimedOut)。
typedef NS_ENUM(NSInteger, FBLPromiseErrorCode) {/** Promise failed to resolve in time. */FBLPromiseErrorCodeTimedOut = 1,/** Validation predicate returned false. */FBLPromiseErrorCodeValidationFailure = 2,}- (FBLPromise *)onQueue:(dispatch_queue_t)queue timeout:(NSTimeInterval)interval {NSParameterAssert(queue);FBLPromise *promise = [[FBLPromise alloc] initPending];[self observeOnQueue:queuefulfill:^(id __nullable value) {[promise fulfill:value];}reject:^(NSError *error) {[promise reject:error];}];typeof(self) __weak weakPromise = promise;dispatch_after(dispatch_time(0, (int64_t)(interval * NSEC_PER_SEC)), queue, ^{NSError *timedOutError = [[NSError alloc] initWithDomain:FBLPromiseErrorDomaincode:FBLPromiseErrorCodeTimedOutuserInfo:nil];[weakPromise reject:timedOutError];});return promise;}
注意点:需要判断error的code,[error.domain isEqual:FBLPromiseErrorDomain],以区分正常的reject error。
// 仿耗时任务FBLPromise * promise = [[FBLPromise wrap2ObjectsOrErrorCompletion:^(FBLPromise2ObjectsOrErrorCompletion _Nonnull handler) {dispatch_after(dispatch_time(0, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{handler(nil, nil, [NSError errorWithDomain:NSCocoaErrorDomain code:10010 userInfo:@{NSURLErrorFailingURLErrorKey:@"timeout"}]);});}] timeout:5];[[promise then:^id _Nullable(id _Nullable value) {NSLog(@"timeout超时%@",value);return value;}] catch:^(NSError * _Nonnull error) {if ([error.domain isEqual:FBLPromiseErrorDomain] || error.code == FBLPromiseErrorCodeTimedOut) {NSLog(@"timeout超时:%@",error);} else {NSLog(@"timeout出错:%@",error);}}];
如果promise将被fulfill,会触发FBLPromiseValidateWorkBlock校验,如果predicate校验通过,则继续执行fulfill(value),校验不通过则fulfill(error),这个error是自定义的(FBLPromiseErrorDomain and FBLPromiseErrorCodeValidationFailure)。
typedef NS_ENUM(NSInteger, FBLPromiseErrorCode) {/** Promise failed to resolve in time. */FBLPromiseErrorCodeTimedOut = 1,/** Validation predicate returned false. */FBLPromiseErrorCodeValidationFailure = 2,}- (FBLPromise*)onQueue:(dispatch_queue_t)queue validate:(FBLPromiseValidateWorkBlock)predicate {NSParameterAssert(queue);NSParameterAssert(predicate);FBLPromiseChainedFulfillBlock chainedFulfill = ^id(id value) {return predicate(value) ? value :[[NSError alloc] initWithDomain:FBLPromiseErrorDomaincode:FBLPromiseErrorCodeValidationFailureuserInfo:nil];};return [self chainOnQueue:queue chainedFulfill:chainedFulfill chainedReject:nil];}
Validate可以理解校验是否跟预期的value一致
[[[[self getStringAtURL:url] validate:^BOOL(NSString * _Nullable value) {return [value isEqualToString:@"some str"];}] then:^id _Nullable(NSString * _Nullable value) {return [self doSomethingWith:value andAnother:value];}] catch:^(NSError * _Nonnull error) {if ([error.domain isEqual:FBLPromiseErrorDomain] || error.code == FBLPromiseErrorCodeValidationFailure) {NSLog(@"validate 出错1 :%@",error);} else {NSLog(@"validate 出错2 :%@",error);}}];
1. 外部调用onQueue:wrapCompletion:,传入work
2. 先调用onQueue:async:返回promise,异步里面的block,回传参数fulfill
3. 调用onQueue:wrapCompletion:里面的work block,回传参数:全局block ^(){}
4. 前面回传的global block即供外部调用的handler,调用时回到onQueue:wrapCompletion:,触发fulfill(nil)
5. 调用fulfill(nil)需要回到onQueue:async:看源码,触发[promise fulfill:value(nil)];,以此结束事件链,被外部的then捕获(reject在此事件链中无参与)
wrapCompletion只是wrap之一,其它的wrap跟其核心流程一致,wrap方便给promise做拓展。
+ (instancetype)onQueue:(dispatch_queue_t)queuewrapCompletion:(void (^)(FBLPromiseCompletion handler))work {NSParameterAssert(queue);NSParameterAssert(work);return [self onQueue:queueasync:^(FBLPromiseFulfillBlock fulfill, FBLPromiseRejectBlock __unused _) {work(^{fulfill(nil);});}];}
wrap方便自定义耗时任务的promise,如果网络请求,promise提供了较大的拓展空间
// 耗时任务[[[FBLPromise wrap2ObjectsOrErrorCompletion:^(FBLPromise2ObjectsOrErrorCompletion _Nonnull handler) {dispatch_after(dispatch_time(0, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{handler(nil, nil, [NSError errorWithDomain:NSCocoaErrorDomain code:10010 userInfo:@{NSURLErrorFailingURLErrorKey:@"timeout"}]);});}][[[FBLPromise wrapErrorCompletion:^(FBLPromiseErrorCompletion _Nonnull handler) {// handler([NSError errorWithDomain:NSCocoaErrorDomain code:101010 userInfo:nil]);handler(nil);}] recover:^id _Nullable(NSError * _Nonnull error) {return @"solution";}] then:^id _Nullable(id _Nullable value) {NSLog(@"wrapErrorCompletion value is nil if not recover. %@",value);return value;}];[[[FBLPromise wrapErrorOrObjectCompletion:^(FBLPromiseErrorOrObjectCompletion _Nonnull handler) {handler([NSError errorWithDomain:NSCocoaErrorDomain code:101010 userInfo:nil], @"dwadwa");}] recover:^id _Nullable(NSError * _Nonnull error) {return @"solution";}] then:^id _Nullable(id _Nullable value) {NSLog(@"wrapErrorOrObjectCompletion value %@",value);return value;}];