The easiest Future and Promises framework in Swift. No magic. No boilerplate.

Overview

Promis

Build Status Version License Platform

The easiest Future and Promises framework in Swift. No magic. No boilerplate.

Overview

While starting from the Objective-C implementation of JustPromises and keeping the code minimalistic, this library adds the following:

  • conversion to Swift 4
  • usage of generics to allow great type inference that wasn't possible in Objective-C
  • overall refactoring for fresh and modern code
  • remove the unnecessary and misleading concept of Progress causing bad patterns to emerge

You can read about the theory behind Future and Promises on Wikipedia, here are the main things you should know to get started.

  • Promises represent the promise that a task will be fulfilled in the future while the future holds the state of such resolution.
  • Futures, when created are in the unresolved state and can be resolved with one of 3 states: with a result, an error, or being cancelled.
  • Futures can be chained, allowing to avoid the pyramid of doom problem, clean up asynchronous code paths and simplify error handling.

Promis brags about being/having:

  • Fully unit-tested and documented 💯
  • Thread-safe 🚦
  • Clean interface 👼
  • Support for chaining
  • Support for cancellation 🙅‍♂️
  • Queue-based block execution if needed 🚆
  • Result type provided via generics 🚀
  • Keeping the magic to the minimum, leaving the code in a readable state without going off of a tangent with fancy and unnecessary design decisions ಠ_ಠ

Alternatives

Other open-source solutions exist such as:

Promis takes inspiration from the Objective-C version of JustPromises developed by the iOS Team of Just Eat which is really concise and minimalistic, while other libraries are more weighty.

Usage

The following example should outline the main benefits of using futures via chaining.

let request = URLRequest(url: URL(string: "http://example.com")!)

// starts by hitting an API to download data
getData(request: request).thenWithResult { data in
    // continue by parsing the retrieved data
    parse(data: data)
}.thenWithResult { parsedData in
    // continue by mapping the parsed data
    map(data: parsedData)
}.onError { error in
    // executed only in case an error occurred in the chain
    print("error: " + String(describing: error))
}.finally(queue: .main) { future in
    // always executed, no matter the state of the previous future or how the chain did perform
    switch future.state {
        case .result(let value):
            print(String(describing: value))
        case .error(let err):
            print(String(describing: err))
        case .cancelled:
            print("future is in a cancelled state")
        case .unresolved:
            print("this really cannot be if any chaining block is executed")
        }
}

The functions used in the example have the following signatures:

func getData(request: URLRequest) -> Future<Data>
func parse(data: Data) -> Future<[Dictionary<String,AnyObject>]>
func map(data: [Dictionary<String,AnyObject>]) -> Future<[FooBar]>

Promises and Futures are parametrized leveraging the power of the generics, meaning that Swift can infer the type of the result compile type. This was a considerable limitation in the Objective-C world and we can now prevent lots of issues at build time thanks to the static typing nature of the language. The state of the future is an enum defined as follows:

enum FutureState<ResultType> {
    case unresolved
    case result(ResultType)
    case error(Error)
    case cancelled
}

Promises are created and resolved like so:

let promise = Promise<ResultType>()
promise.setResult(value)
// or
promise.setError(error)
// or
promise.cancel()

Continuation methods used for chaining are the following:

func then<NextResultType>(queue: DispatchQueue? = nil, task: @escaping (Future) -> Future<NextResultType>) -> Future<NextResultType>
func thenWithResult<NextResultType>(queue: DispatchQueue? = nil, continuation: @escaping (ResultType) -> Future<NextResultType>) -> Future<NextResultType> {
func onError(queue: DispatchQueue? = nil, continuation: @escaping (Error) -> Void) -> Future {
func finally(queue: DispatchQueue? = nil, block: @escaping (Future<ResultType>) -> Void)

All the functions can accept an optional DispatchQueue used to perform the continuation blocks.

Best practices

Functions wrapping async tasks should follow the below pattern:

func wrappedAsyncTask() -> Future<ResultType> {

    let promise = Promise<Data>()
    someAsyncOperation() { data, error in
        // resolve the promise according to how the async operations did go
        switch (data, error) {
        case (let data?, _):
            promise.setResult(data)
        case (nil, let error?):
            promise.setError(error)
        // etc.
        }
    }
    return promise.future
}

You could chain an onError continuation before returning the future to allow in-line error handling, which I find to be a very handy pattern.

// ...
return promise.future.onError {error in
    // handle/log error
}

Pitfalls

When using then or thenWithResult, the following should be taken in consideration.

...}.thenWithResult { data -> Future<NextResultType> in
    /**
    If a block is not trivial, Swift cannot infer the type of the closure and gives the error
    'Unable to infer complex closure return type; add explicit type to disambiguate'
    so you'll have to add `-> Future<NextResultType> to the block signature
    
    You can make the closure complex just by adding any extra statement (like a print).
    
    All the more reason to structure your code as done in the first given example :)
    */
    print("complex closure")
    return parse(data: data)
}

Please check the GettingStarted playground in the demo app to see the complete implementation of the above examples.

Installation

CocoaPods

Add Promis to your Podfile

use_frameworks!
target 'MyTarget' do
    pod 'Promis', '~> x.y.z'
end
$ pod install

Carthage

github "albertodebortoli/Promis" ~> "x.y.z"

Then on your application target Build Phases settings tab, add a "New Run Script Phase". Create a Run Script with the following content:

/usr/local/bin/carthage copy-frameworks

and add the following paths under "Input Files":

$(SRCROOT)/Carthage/Build/iOS/Promis.framework

Author

Alberto De Bortoli [email protected] Twitter: @albertodebo GitHub: albertodebortoli website: albertodebortoli.com

License

Promis is available under the Apache 2 license in respect of JustPromises which this library takes inspiration from. See the LICENSE file for more info.

You might also like...
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

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

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

Tame async code with battle-tested promises
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

Publish–subscribe design pattern implementation framework, with an ability to publish events by topic.
Publish–subscribe design pattern implementation framework, with an ability to publish events by topic.

TopicEventBus Publish–subscribe design pattern implementation framework, with ability to publish events by topic. (NotificationCenter extended alterna

A Protocol-Oriented NotificationCenter which is type safe, thread safe and with memory safety
A Protocol-Oriented NotificationCenter which is type safe, thread safe and with memory safety

A Protocol-Oriented NotificationCenter which is type safe, thread safe and with memory safety. Type Safe No more userInfo dictionary and Downcasting,

A dead-simple abstraction over the iOS BackgroundTask API to make background tasks easy to isolate, maintain and schedule
A dead-simple abstraction over the iOS BackgroundTask API to make background tasks easy to isolate, maintain and schedule

A dead-simple abstraction over the iOS BackgroundTask API to make background tasks easy to isolate, maintain and schedule. Designed to be as lightweight and flexible as possible while tightly integrating with the system APIs. And It's built with Swift Concurrency in mind.

Promise + progress + pause + cancel + retry for Swift.
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 = TaskF

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

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

Comments
  • Bump cocoapods-downloader from 1.3.0 to 1.6.3

    Bump cocoapods-downloader from 1.3.0 to 1.6.3

    Bumps cocoapods-downloader from 1.3.0 to 1.6.3.

    Release notes

    Sourced from cocoapods-downloader's releases.

    1.6.3

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124

    1.5.1

    Enhancements
    • None.
    Bug Fixes
    • Fix "can't modify frozen string" errors when pods are integrated using the branch option
      buju77 #10920

    1.5.0

    ... (truncated)

    Changelog

    Sourced from cocoapods-downloader's changelog.

    1.6.3 (2022-04-01)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2 (2022-03-28)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1 (2022-03-23)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0 (2022-03-22)

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124

    1.5.1 (2021-09-07)

    Enhancements
    • None.

    ... (truncated)

    Commits
    • c03e2ed Release 1.6.3
    • f75bccc Disable Bazaar tests due to macOS 12.3 not including python2
    • 52a0d54 Merge pull request #128 from CocoaPods/validate_before_dl
    • d27c983 Ensure that the git pre-processor doesn't accidentally bail also
    • 3adfe1f [CHANGELOG] Add empty Master section
    • 591167a Release 1.6.2
    • d2564c3 Merge pull request #127 from CocoaPods/validate_before_dl
    • 99fec61 Switches where we check for invalid input, to move it inside the download fun...
    • 96679f2 [CHANGELOG] Add empty Master section
    • 3a7c54b Release 1.6.1
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 2
  • Fix memory leak in `whenAll`

    Fix memory leak in `whenAll`

    Description

    Fix memory leak in whenAll.

    How Has This Been Tested?

    Tested with unit tests.

    Types of changes

    • [ ] Bug fix (non-breaking change which fixes an issue)
    • [ ] New feature (non-breaking change which adds functionality)
    • [x] Breaking change (fix or feature that would cause existing functionality to change)

    Checklist:

    • [x] My code follows the code style of this project.
    • [x] My change requires a change to the documentation.
    • [x] I have updated the documentation accordingly.
    opened by albertodebortoli 0
  • GettingStarted.playground produces runtime error

    GettingStarted.playground produces runtime error

    1. Using Xcode 10.2.1
    2. cloned repository and ran pod install in /Example.
    3. Open and run GettingStarted.playground
    error: GettingStarted.playground:15:5: error: use of unresolved identifier 'getData'
        getData(request: request).thenWithResult { data in
        ^~~~~~~
    
    error: GettingStarted.playground:17:9: error: use of unresolved identifier 'parse'
            parse(data: data)
            ^~~~~
    
    error: GettingStarted.playground:20:13: error: use of unresolved identifier 'map'
                map(data: parsedData)
                ^~~
    
    opened by arielelkin 4
Releases(2.3.0)
Owner
Alberto De Bortoli
Principal Software Engineer @ Just Eat Takeaway. Drive fast but think carefully.
Alberto De Bortoli
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
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
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
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
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
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
FutureLib is a pure Swift 2 library implementing Futures & Promises inspired by Scala.

FutureLib FutureLib is a pure Swift 2 library implementing Futures & Promises inspired by Scala, Promises/A+ and a cancellation concept with Cancellat

Andreas Grosam 39 Jun 3, 2021
⚡️ 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