Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift PromiseKit: Equivalent to when() which executes sequentially?

I'm using PromiseKit with Swift, which has been very handy so far. One of the functions they provide is when(), which allows you to have an array of any number of promises and execute something only once ALL of them have completed.

However, the promises in the array are executed in parallel. I have not found any function that allows me to execute them sequentially. I've tried to write a recursive function of my own, but it doesn't appear to be executing the promises in the order in which they are in the array, and I get the occasional "Promise deallocated" bug. Please help!

static func executeSequentially(promises: [Promise<Void>]) -> Promise<Void> {
    return Promise<Void> { fulfil, reject in
        var mutablePromises = promises
        if mutablePromises.count > 0 {
            mutablePromises.first!
                .then { _ -> Promise<Void> in
                    print("DEBUG: \(mutablePromises.count) promises in promise array.")
                    mutablePromises.remove(at: 0)
                    return executeSequentially(promises: mutablePromises)
                }.catch { error in
                    print("DEBUG: Promise chain rejected.")
                    reject(error)
            }
        } else {
            print("DEBUG: Promise chain fulfilled.")
            fulfil(())
        }
    }
}
like image 526
derf26 Avatar asked Jan 19 '18 22:01

derf26


2 Answers

Here's an extension that takes an array of promises and returns a new promise with all the individual promises chained together to run serially. I've modified the version found here to work with Swift 5 / PromiseKit 7 alpha: https://gist.github.com/kashifshaikh/416b9ffbd300eb680fad3641b6ec53ea

The accompanying post from the original author can be found here: https://medium.com/@peatiscoding/promisekit-chaining-3c957a8ace24

import Foundation
import PromiseKit

extension Promise {
  
  /// Returns a single Promise that you can chain to. Wraps the chains of promises passed into the array into a serial promise to execute one after another using `promise1.then { promise2 }.then ...`
  ///
  /// - Parameter promisesToExecuteSerially: promises to stitch together with `.then` and execute serially
  /// - Returns: returns an array of results from all promises
  public static func chainSerially<T>(_ promises:[() -> Promise<T>]) -> Promise<[T]> {
    // Return a single promise that is fulfilled when
    // all passed promises in the array are fulfilled serially
    return Promise<[T]> { seal in
      var outResults = [T]()
      
      if promises.count == 0 {
        seal.fulfill(outResults)
      } else {
        let finalPromise:Promise<T>? = promises.reduce(nil) { (r, n) -> Promise<T> in
          return r?.then { promiseResult -> Promise<T> in
            outResults.append(promiseResult)
            return n()
          } ?? n()
        }
        
        finalPromise?.done { result in
          outResults.append(result)
          
          seal.fulfill(outResults)
        }.catch { error in
          seal.reject(error)
        }
      }
    }
  }
}

Usage:

let serialSavePromises: [() -> Promise<Bool>] = allImages.map { image in
 return { [weak self] in
   guard let self = self else { return .value(false) }
      
   return self.saveImage(image)
 }
}
            
return Promise<[Bool]>.chainSerially(serialSavePromises)
like image 112
strangetimes Avatar answered Nov 16 '22 12:11

strangetimes


You can use when(fulfilled: array.makeIterator(), concurrently: 1). example:

var intGenerator = (1...10).makeIterator()
var current = 0

func log(_ msg: String) {
    let ts = Date().description
    print("\(ts): \(msg)")
}

let generator = AnyIterator<Promise<Void>> {
    guard let i = intGenerator.next() else {
        return nil
    }
    log("Doing \(i)")
    return after(.seconds(1)).done {
        log("Done \(i)")
        current = i
    }
}

log("Start")
when(fulfilled: generator, concurrently: 1).done { _ in
    log("Finish")
}

source: https://github.com/mxcl/PromiseKit/issues/1093

And I simply encapsulated it.

   func chainSerially<T, S>(_ datas: [T],_ iteratorHandle: @escaping (T) -> Promise<S>?) -> Promise<[S]> {
        var generator = datas.makeIterator()
        let iterator = AnyIterator<Promise<S>> {
            guard let next = generator.next() else {
                return nil
            }
            return iteratorHandle(next)
        }
        
        return when(fulfilled: iterator, concurrently: 1)
    }

use:

    func log(_ msg: String) {
        let ts = Date().description
        print("\(ts): \(msg)")
    }
    
    
    func test() {
        log("start")
        chainSerially(Array(1...10)) { (value: Int?) -> Promise<Void>? in
            guard let value = value else {return nil}

            log("Doing \(value)")
            return after(.seconds(1)).done {
                log("done \(value)")
            }
        }.done { _ in
            print("end")
        }

    }

logs:
2021-10-09 02:01:43 +0000: start
2021-10-09 02:01:43 +0000: Doing 1
2021-10-09 02:01:44 +0000: done 1
2021-10-09 02:01:44 +0000: Doing 2
2021-10-09 02:01:45 +0000: done 2
2021-10-09 02:01:45 +0000: Doing 3
2021-10-09 02:01:46 +0000: done 3
2021-10-09 02:01:46 +0000: Doing 4
2021-10-09 02:01:47 +0000: done 4
2021-10-09 02:01:47 +0000: Doing 5
2021-10-09 02:01:48 +0000: done 5
2021-10-09 02:01:48 +0000: Doing 6
2021-10-09 02:01:50 +0000: done 6
2021-10-09 02:01:50 +0000: Doing 7
2021-10-09 02:01:51 +0000: done 7
2021-10-09 02:01:51 +0000: Doing 8
2021-10-09 02:01:52 +0000: done 8
2021-10-09 02:01:52 +0000: Doing 9
2021-10-09 02:01:53 +0000: done 9
2021-10-09 02:01:53 +0000: Doing 10
2021-10-09 02:01:54 +0000: done 10
end

If you want serial, concurrently must be 1

like image 2
TW520 Avatar answered Nov 16 '22 11:11

TW520