SkillAgentSearch skills...

BrightFutures

Write great asynchronous code in Swift using futures and promises

Install / Use

/learn @Thomvis/BrightFutures
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

BrightFutures

:warning: BrightFutures has reached end-of-life. After a long period of limited development activity, Swift's Async/Await has made the library obsolete. Please consider migrating from BrightFutures to async/await. When you do so, the async get() method will prove to be useful:

// in an async context...

let userFuture = User.logIn(username, password)
let user = try await userFuture.get()

// or simply:
let posts = try await Posts.fetchPosts(user).get()

The remainder of the README has not been updated recently, but is preserved for historic reasons.


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

BrightFutures implements proven functional concepts in Swift to provide a powerful alternative to completion blocks and support typesafe error handling in asynchronous code.

The goal of BrightFutures is to be the idiomatic Swift implementation of futures and promises. Our Big Hairy Audacious Goal (BHAG) is to be copy-pasted into the Swift standard library.

The stability of BrightFutures has been proven through extensive use in production. It is currently being used in several apps, with a combined total of almost 500k monthly active users. If you use BrightFutures in production, we'd love to hear about it!

Latest news

Join the chat at https://gitter.im/Thomvis/BrightFutures GitHub Workflow tests.yml status badge Carthage compatible CocoaPods version CocoaPods

BrightFutures 8.0 is now available! This update adds Swift 5 compatibility.

Installation

CocoaPods

  1. Add the following to your Podfile:

    pod 'BrightFutures'
    
  2. Integrate your dependencies using frameworks: add use_frameworks! to your Podfile.

  3. Run pod install.

Carthage

  1. Add the following to your Cartfile:

    github "Thomvis/BrightFutures"
    
  2. Run carthage update and follow the steps as described in Carthage's README.

Documentation

  • This README covers almost all features of BrightFutures
  • The tests contain (trivial) usage examples for every feature (97% test coverage)
  • The primary author, Thomas Visser, gave a talk at the April 2015 CocoaHeadsNL meetup
  • The Highstreet Watch App was an Open Source WatchKit app that made extensive use of an earlier version of BrightFutures

Examples

We write a lot of asynchronous code. Whether we're waiting for something to come in from the network or want to perform an expensive calculation off the main thread and then update the UI, we often do the 'fire and callback' dance. Here's a typical snippet of asynchronous code:

User.logIn(username, password) { user, error in
    if !error {
        Posts.fetchPosts(user, success: { posts in
            // do something with the user's posts
        }, failure: handleError)
    } else {
        handleError(error) // handeError is a custom function to handle errors
    }
}

Now let's see what BrightFutures can do for you:

User.logIn(username, password).flatMap { user in
    Posts.fetchPosts(user)
}.onSuccess { posts in
    // do something with the user's posts
}.onFailure { error in
    // either logging in or fetching posts failed
}

Both User.logIn and Posts.fetchPosts now immediately return a Future. A future can either fail with an error or succeed with a value, which can be anything from an Int to your custom struct, class or tuple. You can keep a future around and register for callbacks for when the future succeeds or fails at your convenience.

When the future returned from User.logIn fails, e.g. the username and password did not match, flatMap and onSuccess are skipped and onFailure is called with the error that occurred while logging in. If the login attempt succeeded, the resulting user object is passed to flatMap, which 'turns' the user into an array of his or her posts. If the posts could not be fetched, onSuccess is skipped and onFailure is called with the error that occurred when fetching the posts. If the posts could be fetched successfully, onSuccess is called with the user's posts.

This is just the tip of the proverbial iceberg. A lot more examples and techniques can be found in this readme, by browsing through the tests or by checking out the official companion framework FutureProofing.

Wrapping expressions

If you already have a function (or really any expression) that you just want to execute asynchronously and have a Future to represent its result, you can easily wrap it in an asyncValue block:

DispatchQueue.global().asyncValue {
    fibonacci(50)
}.onSuccess { num in
    // value is 12586269025
}

asyncValue is defined in an extension on GCD's DispatchQueue. While this is really short and simple, it is equally limited. In many cases, you will need a way to indicate that the task failed. To do this, instead of returning the value, you can return a Result. Results can indicate either a success or a failure:

enum ReadmeError: Error {
    case RequestFailed, TimeServiceError
}

let f = DispatchQueue.global().asyncResult { () -> Result<Date, ReadmeError> in
    if let now = serverTime() {
        return .success(now)
    }
    
    return .failure(ReadmeError.TimeServiceError)
}

f.onSuccess { value in
    // value will the NSDate from the server
}

The future block needs an explicit type because the Swift compiler is not able to deduce the type of multi-statement blocks.

Instead of wrapping existing expressions, it is often a better idea to use a Future as the return type of a method so all call sites can benefit. This is explained in the next section.

Providing Futures

Now let's assume the role of an API author who wants to use BrightFutures. A Future is designed to be read-only, except for the site where the Future is created. This is achieved via an initialiser on Future that takes a closure, the completion scope, in which you can complete the Future. The completion scope has one parameter that is also a closure which is invoked to set the value (or error) in the Future.

func asyncCalculation() -> Future<String, Never> {
    return Future { complete in
        DispatchQueue.global().async {
            // do a complicated task and then hand the result to the promise:
            complete(.success("forty-two"))
        }
    }
}

Never indicates that the Future cannot fail. This is guaranteed by the type system, since Never has no initializers. As an alternative to the completion scope, you could also create a Promise, which is the writeable equivalent of a Future, and store it somewhere for later use.

Callbacks

You can be informed of the result of a Future by registering callbacks: onComplete, onSuccess and onFailure. The order in which the callbacks are executed upon completion of the future is not guaranteed, but it is guaranteed that the callbacks are executed serially. It is not safe to add a new callback from within a callback of the same future.

Chaining callbacks

Using the andThen function on a Future, the order of callbacks can be explicitly defined. The closure passed to andThen is meant to perform side-effects and does not influence the result. andThen returns a new Future with the same result as this future that completes after the closure has been executed.

var answer = 10
    
let _ = Future<Int, Never>(value: 4).andThen { result in
    switch result {
    case .success(let val):
        answer *= val
    case .failure(_):
        break
    }
}.andThen { result in
    if case .success(_) = result {
        answer += 2
    }
}

// answer will be 42 (not 48)

Functional Composition

map

map returns a new Future that contains the error from this Future if this Future failed, or the return value from the given closure that was applied to the value of this Future.

fibonacciFuture(10).map { number -> String in
    if number > 5 {
        return "large"
    }
    return "small"
}.map { sizeString in
    sizeString == "large"
}.onSuccess { numberIsLarge in
    // numberIsLarge is true
}

flatMap

flatMap is used to map the result of a future to the value of a new Future.

fibonacciFuture(10).flatMap { number in
    fibonacciFuture(number)
}.onSuccess { largeNumber in
    // largeNumber is 139583862445
}

zip

let f = Future<Int, Never>(value: 1)
let f1 = Future<Int, Never>(value: 2)

f.zip(f1).onSuccess { a, b in
    // a is 1, b is 2
}

filter

Future<Int, Never>(value: 3)
    .filter { $0 > 5 }
    .onComplete { result in
        // failed with error NoSuchElementError
    }

Future<String, Never>(value: "Swift")
    .filter { $0.hasPrefix("Sw") }
    .onComplete { result in
        // su
View on GitHub
GitHub Stars1.9k
CategoryDevelopment
Updated19d ago
Forks188

Languages

Swift

Security Score

100/100

Audited on Mar 19, 2026

No findings