preface

In iOS we typically use a delegate or a block for asynchronous operations. To perform multiple asynchronous operations, the second must be nested within the completion of the first, and errors must be handled normally. This makes the code structure unusually confusing and difficult to view.

Those of you who have written JS are aware of the new promise syntax in ES6, which makes the asynchronous operation more flexible. It makes the callback and event combined, and the writing style is beautiful. Google open-source Promises library in the first half of 2018 to make asynchronous programming on iOS easier. It works in the same way as PROMISE in JS and supports OC and Swift.

introduce

Promise represents the result or error message of asynchronous execution. It has the following three states

  • Pending pending
  • This is a big pity
  • Rejected Error information is generated

Once a state has changed, it cannot change its state again. At the same time, promise does not limit its subscribers, and all subscribers can receive notifications when the status changes. At the same time, the subscriber can return another Promise object, thus forming a pipe communication.

The pipeline communication process is free to use threads

The principle of analysis

As you can see from the figure above, you can look up how promises work in the FBLPromise file

Declare state, closure

Three types of state / / typedef NS_ENUM (NSInteger FBLPromiseState) {FBLPromiseStatePending = 0, FBLPromiseStateFulfilled, FBLPromiseStateRejected, }; // The closure callback type typedef void (^FBLPromiseObserver)(FBLPromiseState State, id __nullable resolution); ` ` `Copy the code

Associated cache

@implementation FBLPromise {// Current promise state fblpromisest_state; __nullable _value; NSError *__nullable _error; NSMutableArray<FBLPromiseObserver> *_observers; }Copy the code

Completed or failed operations

The completed or failed operation is essentially a walk through of all subscribers notifying them of the current status and the result of the execution. Change the state value of the cache after execution, even if later call again will not respond. To achieve the effect of not being able to restart as soon as the state changes.

- (void)fulfill:(nullable id)value {
  if ([value isKindOfClass:[NSError class]]) {
    [self reject:(NSError *)value];
  } else {
    @synchronized(self) {
      if(_state == FBLPromiseStatePending) {// State = fBLpromisestatedepressing; _value = value; _pendingObjects = nil; // Notify subscribers of subscribed informationfor (FBLPromiseObserver observer in_observers) { observer(_state, _value); } _observers = nil; dispatch_group_leave(FBLPromise.dispatchGroup); }}}}Copy the code

The subscriber

When subscribing related information, the system determines the current status type first. When the execution is complete, the system immediately calls back the execution result.

- (void)observeOnQueue:(dispatch_queue_t)queue fulfill:(FBLPromiseOnFulfillBlock)onFulfill Reject :(FBLPromiseOnRejectBlock)onReject {@synchronized(self) {// subscribe switch (_state) {case FBLPromiseStatePending: {
        if(! _observers) { _observers = [[NSMutableArray alloc] init]; } // Add a closure to preserve external variable environments [_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; }}}}Copy the code

Here is the priority of the whole PROMISE operation, and also the principle that we use most in promise, fromFBLPromise+ThenIt can be seen that the method isthenIs also the principle of pipeline communication

- (FBLPromise *)chainOnQueue:(dispatch_queue_t)queue chainedFulfill:(FBLPromiseChainedFulfillBlock)chainedFulfill ChainedReject: FBLPromiseChainedRejectBlock chainedReject {/ / create a new promise FBLPromise * promise = [[FBLPromise alloc] initPending]; // Define the closure that executes, which is the essence of piped communication, and why the next onethenCan receive currentthen__auto_type resolver = ^(id __nullable value) {// decidethenIs the return a promiseif([value isKindOfClass:[FBLPromise class]]) {// SubscribethenReturns a promise, mostly to call the next onethen
      [(FBLPromise *)value observeOnQueue:queue
          fulfill:^(id __nullable value) {
            [promise fulfill:value];
          }
          reject:^(NSError *error) {
            [promise reject:error];
          }];
    } else{// If a promise is not returned, it is immediately passed to the next onethen[promise fulfill:value]; }}; Fulfill :^(id __nullable value) {// ExecutethenThe value returned after the operation, which could be a promise or some other value value = chainedFulfill? chainedFulfill(value) : value; // call the closure resolver(value) created above; } reject:^(NSError *error) { id value = chainedReject ? chainedReject(error) : error; resolver(value); }];return promise;
}
Copy the code

Method of use

Only the syntax for SWIFT is shown here. For the use of oc, please refer to the FBL prefix class or read the official documentation directly

Create a way

This parameter is used for the main thread by default. If this parameter is used for other threads, you can set on: DispatchQueue

asynchronous

let promise = Promise<Int> { fulfill, reject in
    let vail = true
    if vail {
        fulfil(12)
    }else {
        reject(error)
    }
}
Copy the code

synchronously

// Pre-definedlet promise = Promise<Int>.pending()
...
if success {
  promise.fulfill(12)
} else{promise.reject(error)} // A way to initiate the result directlylet promise = Promise(12)
let promise = Promise(error)

Copy the code

Then use the

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
    returnnumber ?? 0}Copy the code

Catch

A catch is used to handle errors in a promise. In the Promise pipeline, when one of the promise implementations fails, the subsequent pipeline passes the error directly to the catch for processing.

let promise = Promise<AnyObject> {
    return error
}.catch { error in// Handle error}Copy the code

The operator

All

The callback parameter is not a tuple of all results in all. When one of rejected is called, an error is directly called

letpromise1 = Promise<Int? > { fulfill, _in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 1, execute: {
        fulfill(42)
    })
}
letpromise2 = Promise<Int? > { fulfill, _in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
        fulfill(13)
     })
}
letpromise3 = 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]
}
Copy the code

Always

The closure of ALWAYS is executed regardless of whether the pipeline executes successfully or if there is an error

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)
}
Copy the code

Any

Is the same as the operation type of all, but will Any return tuple data even if there is an error in Any of them? The tuple type is Maybe, containing information related to error and value

letpromise1 = Promise<Int? > { fulfill, _in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
        fulfill(42)
    })
}
letpromise2 = Promise<Int? > { fulfill, _in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
        fulfill(13)
    })
}
letpromise3 = Promise<Int? > { _, rejectin
    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) }Copy the code

Await

With this action, you can synchronize promises that await execution on different threads. This syntax is similar to the use of async/await in ES8

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
  
}
Copy the code

Delay

This action returns a predefined promise that waits until a given time to execute, or immediately executes the error

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)
})
Copy the code

Race

This operation is similar to all, but returns either a promise result or an error that completes first

letpromise1 = Promise<Any? > { fulfill, _in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
        fulfill(42)
    })
}
letpromise2 = Promise<Any? > { fulfill, _in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 3, execute: {
        fulfill("hello world")})}letpromise3 = Promise<Any? > { fulfill, _in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 2, execute: {
        fulfill([44])
    })
}
letpromise4 = Promise<Any? > { fulfill, _in
    DispatchQueue(label: "queue").asyncAfter(deadline: .now() + 4, execute: {
        fulfill(nil)
    })
}
        
race([promise1,promise2,promise3,promise4]).then { (value) in
    print(value)
}
Copy the code

Recover

This operator has the same effect as a Catch, but does not block the execution of other promises in the pipeline

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)
}
Copy the code

Reduce

Combine with other data for transformation

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)")}Copy the code

Retry

When a promise fails, try to re-execute it. If the time is not set, the reexecution is delayed for 1s by default

var count = 0
retry(attempts: 3, delay: 5, condition: { (num, error) -> Bool in
    print("\(num)" + error.localizedDescription)
    ifNum == 2 {// Execute error immediatelyreturn false
    }
    return true
}) { () -> Promise<Int> in
    return count == 3 ? Promise(42) : Promise(DYError.promise)
}.then { (value) in
    print(value)
}.catch { (error) in
    print(error)
}
Copy the code

Timeout

An error occurs if execution is not completed or an error is sent within the specified time

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) 
}
Copy the code

Validate

Determine whether it is valid, if not, it will automatically error

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)
}
Copy the code