A promises library written in Swift featuring combinators like map, flatMap, whenAll, whenAny.

Related tags

EventBus Promissum
Overview

Promissum


Promissum is a promises library written in Swift. It features some known functions from Functional Programming like, map and flatMap.

It has useful combinators for working with promises like; whenAll for doing something when multiple promises complete, and whenAny for doing something when a single one of a list of promises completes. As well as their binary counterparts: whenBoth and whenEither.

Promissum really shines when used to combine asynchronous operations from different libraries. There are currently some basic extensions to UIKit and Alamofire, contributions for extensions to other libraries are very welcome.

This library has an extensive set of regression tests, documentation, and has been used in several high profile production apps at Q42.

Example

This example demonstrates the Alamofire+Promise extension.

In this example, JSON data is loaded from the Github API. It is then parsed, and stored into a local cache. If both those succeed the result is shown to the user, if either of those fail, a description of the error is shown to the user.

let url = "https://api.github.com/repos/tomlokhorst/Promissum"
Alamofire.request(url).responseJSONPromise()
  .map(parseJson)
  .flatMap(storeInLocalCache)
  .then { project in

    // Show project name and description
    self.nameLabel.text = project.name
    self.descriptionLabel.text = project.descr

    UIView.animate(withDuration: 0.5) {
      self.detailsView.alpha = 1
    }
  }
  .trap { e in

    // Either an Alamofire error or a LocalCache error occured
    self.errorLabel.text = e.localizedDescription
    self.errorView.alpha = 1
  }

Cancellation

Promissum does not support cancellation, because cancellation does not work well with promises. Promises are future values, values can't be cancelled. If you do need cancellation (quite often useful), take a look at Tasks or Rx instead of promises. I don't have experience with any Swift Task/Rx libraries, so I can't recommend a specific one.

Although, if you're looking at adding cancellation to a PromiseSource, you could use the swift-cancellationtoken library I wrote. This is orthogonal to promises, however.

Combinators

Listed below are some of the methods and functions provided this library. More documentation is available inline.

Instance methods on Promise

  • .map(transform: Value -> NewValue)
    Returns a Promise containing the result of mapping a function over the promise value.

  • .flatMap(transform: Value -> Promise<NewValue, Error>)
    Returns the flattened result of mapping a function over the promise value.

  • .mapError(transform: Error -> NewError)
    Returns a Promise containing the result of mapping a function over the promise error.

  • .flatMapError(transform: Error -> Promise<Value, NewError>)
    Returns the flattened result of mapping a function over the promise error.

  • .dispatch(on queue: DispatchQueue) Returns a new promise that will execute all callbacks on the specified dispatch_queue. See dispatch queues

Functions for dealing with Promises

  • whenBoth(promiseA: Promise<A, Error>, _ promiseB: Promise<B, Error>)
    Creates a Promise that resolves when both arguments to whenBoth resolve.

  • whenAll(promises: [Promise<Value, Error>])
    Creates a Promise that resolves when all provided Promises resolve.

  • whenEither(promise1: Promise<Value, Error>, _ promise2: Promise<Value, Error>)
    Creates a Promise that resolves when either argument to resolves.

  • whenAny(promises: [Promise<Value, Error>])
    Creates a Promise that resolves when any of the argument Promises resolves.

Dispatch queues

Promises can call handlers on different threads or queues. Handlers are all closures supplied to methods like .then, .trap, .map, and .flatMap.

If nothing else is specified, by default, all handlers will be called on the main queue. This way, you're free to update the UI, without having to worry about manually calling dispatch_async.

However, it's easy to change the dispatch queue used by a promise. In one of two ways:

  1. Set the dispatch queue when creating a PromiseSource, e.g.:
let background = DispatchQueue.global(qos: .background)
let source = PromiseSource<Int, Never>(dispatch: .queue(background))
source.promise
  .then { x in
    // Handler is called on background queue
  }
  1. Or, create a new promise using the .dispatchOn combinator:
let background = DispatchQueue.global(qos: .background)
somePromise()
  .dispatch(on: background)
  .then { x in
    // Handler is called on background queue
  }

For convenience, there's also .dispatchMain to move back to the main queue, after doing some work on a background queue:

let background = DispatchQueue.global(qos: .background)
somePromise()
  .dispatch(on: background)
  .map { expensiveComputation($0) }
  .dispatchMain()
  .then { x in
    self.updateUi(x)
  }

Installation

Swift Package Manager

SPM is a dependency manager for Swift projects.

Once you have SPM setup, add a dependency using Xcode or by editing Package.swift:

dependencies: [
    .package(url: "https://github.com/tomlokhorst/Promissum.git", from: "7.1.0"),
]

Note: Previous versions of Promissum supported CocoaPods, this is no longer supported. If you still need pods support, you can use the 5.x.x versions of this package, while those still work.

Releases

  • 7.1.0 - 2022-01-03 - Move down async/await support to iOS 13. Requires Xcode 13.2
  • 7.0.0 - 2021-11-13 - Add async/await support. Remove PromissumUIKit extensions
  • 6.0.0 - 2021-02-21 - Move public State struct into PromiseSource. Remove CocoaPods support
  • 5.0.1 - 2020-10-07 - Pass promise method options to Alamofire
  • 5.0.0 - 2020-08-22 - Update Alamofire+Promise to Alamofire 5, requires iOS 10
  • 4.0.0 - 2019-06-10 - Swift 5.1 support, use build-in Result type
  • 3.2.0 - 2019-04-03 - Deprecate delay and related functions
  • 3.1.0 - 2018-11-17 - Allow for use in extensions
  • 3.0.0 - 2018-10-02 - Swift 4.2 support, removed CoreDataKit extension
  • 2.2.0 - 2018-02-09 - Fix occasional "execute on wrong queue" issue
  • 2.1.0 - 2018-01-12 - watchOS support
  • 2.0.0 - 2017-11-27 - Swift 4 support, threadsafe
  • 1.0.0 - 2016-09-20 - Swift 3 support, requires iOS 9 & OSX 10.11
  • 0.5.0 - 2016-01-19 - Add dispatchOn methods for dispatching on different queues
  • 0.4.0 - 2015-11-04 - Update Alamofire+Promise to Alamofire 3
  • 0.3.0 - 2015-09-11 - Swift 2 support, added custom error types
  • 0.2.4 - 2015-05-31 - Fixed examples. Updated CoreDataKit+Promise
  • 0.2.3 - 2015-04-13 - Swift 1.2 support
  • 0.2.2 - 2015-03-01 - Mac OS X support
  • 0.2.1 - 2015-02-16 - Update for new CoreDataKit version
  • 0.2.0 - 2015-02-15 - Side-effects happen in a better order. Regression tests added.
  • 0.1.1 - 2015-05-31 - whenAnyFinalized combinator added
  • 0.1.0 - 2015-01-27 - Initial public release
  • 0.0.0 - 2014-10-12 - Initial private version for project at Q42

Licence & Credits

Promissum is written by Tom Lokhorst and available under the MIT license, so feel free to use it in commercial and non-commercial projects.

Comments
  • On deinit warning, also log location where the PromiseSource was created

    On deinit warning, also log location where the PromiseSource was created

    This would be very valuable when trying to fix the error, maybe only capture/print this info in debug mode since it is a bit sensitive info.

    The magic constants like __FUNCTION__ should make this possible.

    opened by mac-cain13 3
  • Update availability checks for Xcode 13.2

    Update availability checks for Xcode 13.2

    From the Xcode 13.2 release notes:

    You can now use Swift Concurrency in applications that deploy to macOS 10.15, iOS 13, tvOS 13, and watchOS 6 or newer. This support includes async/await, actors, global actors, structured concurrency, and the task APIs. (70738378)

    opened by benlenarts 2
  • v5.0.0 - Separate targets for SPM

    v5.0.0 - Separate targets for SPM

    Implements https://github.com/tomlokhorst/Promissum/issues/43

    TODO:

    • [ ] Figure out if this doesn't lead to conflicts with CocoaPods, it now shows warnings about imports
    opened by tomlokhorst 2
  • `.mapError()` not working?

    `.mapError()` not working?

    We are using Promissum in a legacy project that we are trying to upgrade to Swift 3, but I found some issues when doing so.

    Say that I have something like:

    class Api {
      // ... 
      fileprivate func requestJSON(_ path: Path) -> Promise<SuccessResponse<AnyObject>, ApiError> {
        let url = path.url(baseUrl)
        return manager
          .request(url)
          .responseJSONPromise()
          .mapError(self.errorTransformer)
      }
      
      // MARK: Error helpers
      
      fileprivate func errorTransformer(_ error: ErrorResponse) -> ApiError {
        return .networkingError(error.result as NSError)
      }
        
      // ...
    }
    

    Where ApiError is:

    import Foundation
    import Statham
    
    public enum ApiError : Error {
      case notFound
      case networkingError(NSError)
      case jsonDecodingError(Error)
    }
    
    extension ApiError: CustomStringConvertible {
      public var description: String {
        switch self {
        case .notFound: return NSLocalizedString("Not Found", comment: "Api Error: resource not found")
        case .networkingError(let error): return error.localizedDescription
        case .jsonDecodingError(let error):
          #if DEBUG
            if let error = error as? JsonDecodeError {
              print(error.fullDescription)
            }
          #endif
          return NSLocalizedString("Server error", comment: "Api Error: Server error")
        }
      }
    }
    

    The problem comes when using .mapError(), where the compiler complains about "Argument passed to call that takes no arguments". Digging in the github, I can see that indeed there is this following method mapError() that does not take any arguments and that is a new addition since the 0.5.0 version.

    I can see however the right method here, but it seems that extension is sort of overriding the right one? Or am I missing something important here?

    opened by pablorecio 2
  • Add custom error type

    Add custom error type

    Currently (2015-04-16) Promissum only uses NSError for errors.

    By changing the type Promise<T> to Promise<Value, Error> a custom error type can be provided.

    This would allow for more type-safe, fine grained error handling.

    opened by tomlokhorst 2
  • Support dispatching on GCD queues

    Support dispatching on GCD queues

    Current (2015-01-08) behaviour is that every callback (from map, then and the like) happen synchronously on the same thread as where PromiseSource.resolve is called. Lets call this "synchronous dispatch".

    Sometimes synchronous dispatch is the correct behaviour, but most of the time it leads to unexpected results.

    Proposal: add dispatchOn

    Add support to "configure" a promise to run on a provided dispatch queue. For example:

    let myQueue = dispatch_queue_create("com.example.MyQueue", nil);
    
    getSomePromise()
      .dispatchOn(myQueue)
      .map { value in
        // This is called on my queue
        return value
      }
      .then { value in
        // This is also called on my queue
        // That means we can't do UI operations! (not UI thread)
      }
    

    The call to dispatchOn(queue: dispatch_queue_t) returns a new Promise that is correctly configured (i.e. it doesn't mutate the existing promise). This way we can also "switch" queues:

    getSomePromise()
      .dispatchOn(myQueue)
      .map { value in
        // This is called on my queue
        return value
      }
      .dispatchOn(dispatch_get_main_queue())
      .then { value in
        // This runs on main thread, so we can do UI operations
      }
    

    When nothing is configured, the main queue is chosen as a default (so users can do UI operations):

    getSomePromise()
      .then { value in
        // Default: run on main thread
      }
    

    However, if for some reason, you want to dispatch synchronously, this can be done using dispatchSync():

    getSomePromise()
      .dispatchSync()
      .then { value in
        // This now runs on whatever thread `PromiseSource.resolve` was called on.
        // This can be used if the caller of `resolve` expects the side effects from `then` to have happend.
      }
    

    Running a "sub computation" on a different queue is also possible, using the existing flatMap:

    getSomePromise()
      .dispatchOn(myQueue)
      .flatMap { value in
        return Promise(value: value)
          .dispatchOn(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0))
          .map { val in
            // This runs on a high priority queue
            return val
          }
      }
      .then { value in
        // This is also called on my queue
        // That means we can't do UI operations! (not UI thread)
      }
    
    opened by tomlokhorst 2
  • Add separate products/targets for Alamofire extensions

    Add separate products/targets for Alamofire extensions

    When importing https://github.com/ReactiveX/RxSwift/blob/master/Package.swift as SPM package from Xcode, I got the question which product to add to my project.

    Using the same setup as RxSwift we might be able to include the Alamofire dependency ONLY for that extension product.

    opened by hermanbanken 1
  • Remove APPLICATION_EXTENSION_API_ONLY declaration

    Remove APPLICATION_EXTENSION_API_ONLY declaration

    Avoid declaring APPLICATION_EXTENSION_API_ONLY when this is not the case (ie when using with Alamofire). CocoaPods will apply APPLICATION_EXTENSION_API_ONLY automatically if the target requires it.

    Fixes issue #40.

    opened by mac-cain13 1
  • Warning about Alamofire not being safe for Application Extensions

    Warning about Alamofire not being safe for Application Extensions

    When using the Promissum/Alamofire subspec you'll get the following warning:

    ld: warning: linking against a dylib which is not safe for use in application extensions: /Users/[yada-yada]-iphonesimulator/Alamofire/Alamofire.framework/Alamofire
    

    This is because Alamofire declares NOT to be safe for use in application extensions, while Promissum declares it is: https://github.com/tomlokhorst/Promissum/blob/93439490f05a5d594f6453a87206ef8bcfd04adf/Promissum.podspec#L20

    opened by mac-cain13 1
  • Result type

    Result type

    Replaces Promissum's result type with the one defined by Swift itself. The enum cases are now called success and failure, and as far as I know there is no way for users of the library to migrate to this automatically. Using @available() doesn't seem to be an option because we don't own the Result enum anymore.

    opened by mbernson 1
  • Promissum 6

    Promissum 6

    • Move public State struct to nested inside PromiseSource
    • Add Promise.init that takes function argument with a PromiseSource
    • Deprecate top level flatten function, remove previously deprecated functions
    • Remove Alamofire extension (moved to separate package PromissumAlamofire)
    • Remove CocoaPods support
    opened by tomlokhorst 0
  • Missing combinator: whenAllResult

    Missing combinator: whenAllResult

    public func whenAllResult<Value, Error>(promises: [Promise<Value, Error>]) -> Promise<[Result<Value, Error>], NoError> {
      let source = PromiseSource<[Result<Value, Error>], NoError>()
      var results = promises.map { $0.result }
      var remaining = promises.count
    
      if remaining == 0 {
        source.resolve([])
      }
    
      for (ix, promise) in promises.enumerate() {
    
        promise
          .finallyResult { result in
            results[ix] = result
            remaining = remaining - 1
    
            if remaining == 0 {
              source.resolve(results.map { $0! })
            }
        }
      }
    
      return source.promise
    }
    
    opened by tomlokhorst 0
  • Make it possible to switch deinit warning message off by default

    Make it possible to switch deinit warning message off by default

    This message: PromiseSource.deinit: WARNING: Unresolved PromiseSource deallocated, maybe retain this object? appears in our test suite log often, which makes the test results difficult to read. The warning is useful in a production context, but not while testing. It would be nice to be able to disable the warning globally. Although we could disable the warning on an instance-by-instance basis, we would prefer to not need to change the construction of promises throughout the codebase.

    opened by ghost 1
  • Print warning when promise rejects without error handlers

    Print warning when promise rejects without error handlers

    To prevent errors from silently disappearing. If no error handlers are registered, print or assert, possibly using same mechanism as https://github.com/tomlokhorst/Promissum/issues/14

    opened by tomlokhorst 1
  • Improve inline documentation

    Improve inline documentation

    Explain details of the different combinators, when supplied closures are run.

    In particular: Explain how flatMapResult runs on both values and errors.

    Also explain when registered handlers are cleaned up.

    opened by tomlokhorst 0
  • Reduce stack size when debugging

    Reduce stack size when debugging

    Suggested by @sdevos. See if it's possible to reduce the size of the call stack when debugging code related to promises.

    It might be necessary to manually inline a lot of code, making the internals of Promissum a bit uglier. Let's investigate to see if this is worth the trouble.

    opened by tomlokhorst 0
Owner
Tom Lokhorst
Software Writer
Tom Lokhorst
A Swift based Future/Promises Library for IOS and OS X.

FutureKit for Swift A Swift based Future/Promises Library for IOS and OS X. Note - The latest FutureKit is works 3.0 For Swift 2.x compatibility use v

null 759 Dec 2, 2022
⚡️ Lightweight full-featured Promises, Async & Await Library in Swift

Lightweight full-featured Promises, Async & Await Library in Swift What's this? Hydra is full-featured lightweight library which allows you to write b

Daniele Margutti 2k Dec 31, 2022
Futures and Promises library

#PureFutures A simple Futures and Promises library. ##Installation ###Carthage Add the following in your Cartfile: github "wiruzx/PureFutures" And ru

Victor Shamanov 17 Apr 5, 2019
Promises for Swift & ObjC.

Promises simplify asynchronous programming, freeing you up to focus on the more important things. They are easy to learn, easy to master and result in

Max Howell 14k Jan 5, 2023
When is a lightweight implementation of Promises in Swift.

Description When is a lightweight implementation of Promises in Swift. It doesn't include any helper functions for iOS and OSX and it's intentional, t

Vadym Markov 260 Oct 12, 2022
Write great asynchronous code in Swift using futures and promises

BrightFutures How do you leverage the power of Swift to write great asynchronous code? BrightFutures is our answer. BrightFutures implements proven fu

Thomas Visser 1.9k Dec 20, 2022
The easiest Future and Promises framework in Swift. No magic. No boilerplate.

Promis The easiest Future and Promises framework in Swift. No magic. No boilerplate. Overview While starting from the Objective-C implementation of Ju

Alberto De Bortoli 111 Dec 27, 2022
Promises is a modern framework that provides a synchronization construct for Swift and Objective-C.

Promises Promises is a modern framework that provides a synchronization construct for Objective-C and Swift to facilitate writing asynchronous code. I

Google 3.7k Dec 24, 2022
Easy Swift Futures & Promises.

❗️ Archived now ❗️ Since Apple released Combine framework, I decide to archive this repo. You still can use this repo as an example of Future/Promise

Dmytro Mishchenko 40 Sep 23, 2022
Tame async code with battle-tested promises

Then Reason - Example - Documentation - Installation fetchUserId().then { id in print("UserID : \(id)") }.onError { e in print("An error occur

Fresh 963 Jan 3, 2023
Lightweight promises for iOS, macOS, tvOS, watchOS, and Linux

Futures Futures is a cross-platform framework for simplifying asynchronous programming, written in Swift. It's lightweight, fast, and easy to understa

David Ask 60 Aug 11, 2022
A Promise library for Swift, based partially on Javascript's A+ spec

Promise A Promise library for Swift, based partially on Javascript's A+ spec. What is a Promise? A Promise is a way to represent a value that will exi

Soroush Khanlou 622 Nov 23, 2022
A library that adds a throwing unwrap operator in Swift.

ThrowingUnwrap A simple package to add a throwing unwrap operator (~!) to Optionals in Swift. Import Add this to the package-wide dependencies in Pack

Allotrope 3 Aug 27, 2022
A light-weighted Promise library for Objective-C

RWPromiseKit Desiciption A light-weighted Promise library for Objective-C About Promise The Promise object is used for deferred and asynchronous compu

Canopus 113 May 4, 2022
Promise + progress + pause + cancel + retry for Swift.

SwiftTask Promise + progress + pause + cancel + retry for Swift. How to install See ReactKit Wiki page. Example Basic // define task let task = Task<F

ReactKit 1.9k Dec 27, 2022
Promise/A+, Bluebird inspired, implementation in Swift 5

Bluebird.swift Promise/A+ compliant, Bluebird inspired, implementation in Swift 5 Features Promise/A+ Compliant Swift 5 Promise Cancellation Performan

Andrew Barba 41 Jul 11, 2022
Material para a apresentação da palestra "Implementando Interesses Transversais - um papo sobre arquitetura, DI e Design Patterns em Swift/iOS" no TDC Future 2021

--- title: Implementando Interesses Transversais - um papo sobre arquitetura, DI e Design Patterns em Swift/iOS author: Cícero Camargo date: Nov 30th

Cícero Camargo 2 Nov 30, 2021
PromiseKit 4.5.2 with changes for Swift 5

繁體中文, 简体中文 Promises simplify asynchronous programming, freeing you up to focus on the more important things. They are easy to learn, easy to master an

null 0 Dec 24, 2021
Swift µframework providing Future

Future [] (https://github.com/Carthage/Carthage) Swift µframework providing Future<T, Error>. This library is inspired by the talk of Javier Soto at S

Le Van Nghia 122 Jun 3, 2021