Promise/A+, Bluebird inspired, implementation in Swift 5

Overview

Bluebird.swift

CocoaPods Compatible Carthage Compatible Twitter

Promise/A+ compliant, Bluebird inspired, implementation in Swift 5

Features

  • Promise/A+ Compliant
  • Swift 5
  • Promise Cancellation
  • Performance
  • Lightweight
  • Unit Tests
  • 100% Documented

Documentation

https://andrewbarba.github.io/Bluebird.swift/

Requirements

  • iOS 9.0+ / macOS 10.11+ / tvOS 9.0+ / watchOS 2.0+
  • Xcode 10.2
  • Swift 5

Installation

Swift Package Manager

// swift-tools-version:5.0

import PackageDescription

let package = Package(
    name: "My App",
    dependencies: [
        .package(url: "https://github.com/AndrewBarba/Bluebird.swift.git", from: "5.1.0")
    ]
)

CocoaPods

CocoaPods 1.5.0+ is required to build Bluebird

pod 'Bluebird', '~> 5.0'

Carthage

github "AndrewBarba/Bluebird.swift" ~> 5.0

Who's Using Bluebird

Using Bluebird in production? Let me know with a Pull Request or an Issue.

Usage

Promise

Promises are generic and allow you to specify a type that they will eventually resolve to. The preferred way to create a Promise is to pass in a closure that accepts two functions, one to be called to resolve the Promise and one to be called to reject the Promise:

let promise = Promise<Int> { resolve, reject in
  // - resolve(someInt)
  // - reject(someError)
}

The resolve and reject functions can be called asynchronously or synchronously. This is a great way to wrap existing Cocoa API to resolve Promises in your own code. For example, look at an expensive function that manipulates an image:

Before Promises
func performExpensiveOperation(onImage image: UIImage, completion: @escaping (UIImage?, Error?) -> Void) {
  DispatchQueue(label: "image.operation").async {
    do {
      let image = try ...
      completion(image, nil)
    } catch {
      completion(nil, error)
    }
  }
}
After Promises
func performExpensiveOperation(onImage image: UIImage) -> Promise<UIImage> {
  return Promise<UIImage> { resolve, reject in
    DispatchQueue(label: "image.operation").async {
      do {
        let image = try ...
        resolve(image)
      } catch {
        reject(error)
      }
    }
  }
}

Okay, so the inner body of the function looks almost identical... But look at how much better the function signature looks!

No more completion handler, no more optional image, no more optional error. Optionals in the original function are a dead giveaway that you'll be guarding and unwrapping in the near future. With the Promise implementation that logic is hidden by good design. Using this new function is now a joy:

let original: UIImage = ...

performExpensiveOperation(onImage: original)
  .then { newImage in
    // do something with the new image
  }
  .catch { error in
    // something went wrong, handle the error
  }

then

You can easily perform a series of operations with the then method:

authService.login(email: email, password: password)
  .then { auth in userService.read(with: auth) }
  .then { user in favoriteService.list(for: user) }
  .then { favorites in ... }

Notice each time you return a Promise (or a value) from a then handler, the next then handler receives the resolution of that handler, waiting for the previous to fully resolve. This is extremely powerful for asynchronous control flow.

Grand Central Dispatch

Any method in Bluebird that accepts a handler also accepts a DispatchQueue so you can control what queue you want the handler to run on:

userService.read(id: "123")
  .then(on: backgroundQueue) { user -> UIImage in
    let image = UIImage(user: user)
    ... perform complex image operation ...
    return image
  }
  .then(on: .main) { image in
    self.imageView.image = image
  }

By default all handlers are run on the .main queue.

catch

Use catch to handle / recover from errors that happen in a Promise chain:

authService.login(email: email, password: password)
  .then { auth in userService.read(with: auth) }
  .then { user in favoriteService.list(for: user) }
  .then { favorites in ... }
  .catch { error in
    self.present(error: error)
  }

Above, if any then handler throws an error, or if one of the Promises returned from a handler rejects, then the final catch handler will be called.

You can also perform complex recovery when running multiple asynchronous operations:

Bluebird.try { performFirstOp().catch(handleOpError) }
  .then { performSecondOp().catch(handleOpError) }
  .then { performThirdOp().catch(handleOpError) }
  .then { performFourthOp().catch(handleOpError) }
  .then {
    // all completed
  }

tap

Useful for performing an operation in the middle of a promise chain without changing the type of the Promise:

authService.login(email: email, password: password)
  .tap { auth in print(auth) }
  .then { auth in userService.read(with: auth) }
  .tap { user in print(user) }
  .then { user in favoriteService.list(for: user) }
  .then { favorites in ... }

You can also return a Promise from the tap handler and the chain will wait for that promise to resolve:

authService.login(email: email, password: password)
  .then { auth in userService.read(with: auth) }
  .tap { user in userService.updateLastActive(for: user) }
  .then { user in favoriteService.list(for: user) }
  .then { favorites in ... }

finally

With finally you can register a handler to run at the end of a Promise chain, regardless of it's result:

spinner.startAnimating()

authService.login(email: email, password: "bad password")
  .then { auth in userService.read(with: auth) } // will not run
  .then { user in favoriteService.list(for: user) } // will not run
  .finally { // this will run!
    spinner.stopAnimating()
  }
  .catch { error in
    // handle error
  }

join

Join different types of Promises seamlessly:

join(fetchArticle(id: "123"), fetchAuthor(id: "456"))
  .then { article, author in
    // ...
  }

map

Iterate over a sequence of elements and perform an operation each:

let articles = ...

map(articles) { article in
  return favoriteService.like(article: article)
}.then { _ in
  // all articles liked successfully
}.catch { error in
  // handle error
}

You can also iterate over a sequence in series using mapSeries().

reduce

Iterate over a sequence and reduce down to a Promise that resolves to a single value:

let users = ...

reduce(users, 0) { partialTime, user in
  return userService.getActiveTime(for: user).then { time in
    return partialTime + time
  }
}.then { totalTime in
  // calculated total time spent in app
}.catch { error in
  // handle error
}

all

Wait for all promises to complete:

all([
  favoriteService.like(article: article1),
  favoriteService.like(article: article2),
  favoriteService.like(article: article3),
  favoriteService.like(article: article4),
]).then { _ in
  // all articles liked
}

any

Easily handle race conditions with any, as soon as one Promise resolves the handler is called and will never be called again:

let host1 = "https://east.us.com/file"
let host2 = "https://west.us.com/file"

any(download(host1), download(host2))
  .then { data in
    ...
  }

try

Start off a Promise chain:

// Prefix with Bluebird since try is reserved in Swift
Bluebird.try {
  authService.login(email: email, password: password)
}.then { auth in
  // handle login
}.catch { error in
  // handle error
}

Tests

Tests are continuously run on Bitrise. Since Bitrise doesn't support public test runs I can't link to them, but you can run the tests yourself by opening the Xcode project and running the tests manually from the Bluebird scheme.

Bluebird vs PromiseKit

I'd be lying if I said PromiseKit wasn't a fantastic library (it is!) but Bluebird has different goals that may or may not appeal to different developers.

Xcode 9+ / Swift 4+

PromiseKit goes to great length to maintain compatibility with Objective-C, previous versions of Swift, and previous versions of Xcode. Thats a ton of work, god bless them.

Generics & Composition

Bluebird has a more sophisticated use of generics throughout the library giving us really nice API for composing Promise chains in Swift.

Bluebird supports map, reduce, all, any with any Sequence type, not just arrays. For example, you could use Realm's List or Result types in all of those functions, you can't do this with PromiseKit.

Bluebird also supports Promise.map and Promise.reduce (same as Bluebird.js) which act just like their global equivalent, but can be chained inline on an existing Promise, greatly enhancing Promise composition.

No Extensions

PromiseKit provides many useful framework extensions that wrap core Cocoa API's in Promise style functions. I currently have no plans to provide such functionality, but if I did, it would be in a different repository so I can keep this one lean and well tested.

Bluebird API Compatible

I began using PromiseKit after heavily using Bluebird.js in my Node/JavaScript projects but became annoyed with the subtle API differences and a few things missing all together. Bluebird.swift attempts to closely follow the API of Bluebird.js:

Bluebird.js
Promise.resolve(result)
Promise.reject(error)
promise.then(handler)
promise.catch(handler)
promise.finally(() => ...)
promise.tap(value => ...)
Bluebird.swift
Promise(resolve: result)
Promise(reject: error)
promise.then(handler)
promise.catch(handler)
promise.finally { ... }
promise.tap { value in ... }
PromiseKit
Promise(value: result)
Promise(error: error)
promise.then(execute: handler)
promise.catch(execute: handler)
promise.always { ... }
promise.tap { result in
  switch result {
  case .fullfilled(let value):
    ...
  case .rejected(let error):
    ...
  }
}

These are just a few of the differences, and Bluebird.swift is certainly missing features in Bluebird.js, but my goal is to close that gap and keep maintaining an API that much more closely matches where applicable.

Comments
  • Q: How to create a pending Promise?

    Q: How to create a pending Promise?

    RT.

    I'm writing code to wrap the delegation pattern to Promise.

    Can I create a pending promise?

    I'm new to Promise/Future Programming, and I find it is not obvious to create a pending promise with Bluebird.swift.

    Some other problems: How to get value 1 & value 2 in the 3rd `then closure?

    opened by lastcc 8
  • Using with Swift package manager

    Using with Swift package manager

    I tried to import Bluebird into command line app using Swift package manager. It wouldn't compile and was giving following error:

    error: use of undeclared type 'DispatchQueue'
    

    I had to add "import Foundation" to all Bluebird's files to make it compile. Is there any reason why it was omitted? Did I miss some configuration? Meanwhile Alamofire compiled right away..

    opened by mantas 3
  • Set APPLICATION_EXTENSION_API_ONLY to YES in Library.xcconfig

    Set APPLICATION_EXTENSION_API_ONLY to YES in Library.xcconfig

    The Cocoapods podspec specifies APPLICATION_EXTENSION_API_ONLY, but the Xcode project doesn't. This means that the resulting Carthage framework isn't marked as extension safe and generates a warning when linked with extension safe targets.

    This change adds the setting to the Library.xcconfig file so that it carries through when Carthage builds the dynamic framework. The result is that there's no longer a warning.

    opened by jeffremer 2
  • Enables support for distributing Bluebird as a binary library

    Enables support for distributing Bluebird as a binary library

    This fixes #8 and adds a different solution for #6.

    Solution

    To allow frameworks to be used by multiple Swift version, we need to set:

    BUILD_LIBRARY_FOR_DISTRIBUTION = YES
    

    This isn't necessary for a Swift package (since they are build from source), but it's useful for Carthage or other means of distributing binary frameworks.

    The workaround is to use an xcconfig file and pass that as an argument to generate-xcodeproj.

    It was discovered that the Bluebird_Info.plist file is ovewritten when running generate-xcodeproj. This means we cannot manually set the CFBundleVersion there. The solution was to also add CURRENT_PROJECT_VERSION to the xcconfig file.

    Testing

    I built Bluebird.framework in Xcode 11.3.1. Then in Xcode 11.4 I created a test project and linked to the generated project. It built and linked successfully.

    opened by devioustree 2
  • Correct CFBundleVersion in Bluebird_Info.plist

    Correct CFBundleVersion in Bluebird_Info.plist

    This fixes https://github.com/AndrewBarba/Bluebird.swift/issues/6, which was causing an ITMS error when Carthage referenced Bluebird.framework is copied into App.app/Frameworks/Bluebird.framework due to having a missing CFBundleVersion key.

    It was previously missing because it referenced an un-set CURRENT_PROJECT_VERSION build setting.

    Testing:

    $ carthage build --no-skip-current
    *** xcodebuild output can be found in /var/folders/4y/7k0ckw7n69v9bgsvt6x7bnrc0000gn/T/carthage-xcodebuild.AEJZJ5.log
    *** Building scheme "Bluebird-Package" in Bluebird.xcodeproj
    
    $ plutil -convert xml1 Carthage/Build/iOS/Bluebird.framework/Info.plist -o ~/Desktop/Bluebird_Info.plist
    
    $ grep "CFBundleVersion" ~/Desktop/Bluebird_Info.plist
    	<key>CFBundleVersion</key>
    
    $
    

    Built inside app using this branch: |Framework Bundle|Info.plist| |---|---| |Screen Shot 2020-04-01 at 12 13 05 PM|Screen Shot 2020-04-01 at 12 13 13 PM|

    Fixes #6.

    opened by mattrobmattrob 1
  • Promise<Void>.resolve()

    Promise.resolve()

    Hi there! At first I'd like to say that you did a great job creating this promise framework! I'm thinking on migrating from closure-style async API to promise-style, and I need your help here.

    Sometimes needed to not pass any argument in a resolve block of promise, this is where Promise appears. Whenever I call resolve() on such promise, it requires an arguments, which doesn't make sense. What I'm asking for is: can you please add a function with signature without any parameter for void-parametrized promise? Thanks in advance.

    Related image: https://imgur.com/fGtH99j

    opened by Oleygen 1
  • Finalize catch/then semantics

    Finalize catch/then semantics

    One of the hardest challenges of a Promise implementation in a strongly typed language is the semantics of catch and how it affects the Promise chain:

    Promise/A+ 2.2.7.1 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure Resolve(promise2, x)

    With this requirement, any then called directly after a catch does not know the Type of the first argument in it's handler. If the prior Promise resolves, then it will be the resolution of that Promise, but if it rejects it will be whatever value the catch returns. This unknown makes it impossible to implement directly in a typed language and forces us to make tradeoffs either with more functions (different names that explicitly indicate which behavior they implement) or making the resolution type less strict perhaps by using an optional type or a Any and then forcing unwrapping in the then handler.

    I'm curious what the community's thoughts are on this before changing the semantics again in this library. If/when there is a good solution I will release a new version of the library as 2.0.0.

    For reference, the current implementation provides 2 functions: catch and catchThen:

    • catch allows for catching an error and continuing the promise chain as Promise<Void>. Whether the prior promise resolves/rejects, the then handler after a catch will always be run.
    • catchThen forces the handler to recover with the same result Type as the original Promise. This means that if the original Promise resolves we can pass that result down to any then handlers after the catch and if it rejects the catch will be responsible for providing an alternate resolution of the same type.
    enhancement 
    opened by AndrewBarba 1
  • Support async/await

    Support async/await

    • Supports async/await in Swift 5.5 via a new value() async method on the promise
    • Initialize a Promise with an async handler

    Async Value

    let user = await getUser().value()
    

    Async Init

    let promise = Promise<User> {
        return await getUserAsync()
    }
    

    Async chain

    getUser()
        .then { await getFavorites(for: $0) }
    
    opened by AndrewBarba 0
  • Missing support for binary frameworks with module stability

    Missing support for binary frameworks with module stability

    Bluebird.xcodeproj isn't configured to generate a .swiftinterface file which means dynamic frameworks can only be used with projects using the same version of the Swift compiler.

    It would be nice to not have to recompile the framework when a new Swift compiler is released.

    opened by devioustree 0
  • Carthage Bluebird.swift framework is missing CFBundleVersion

    Carthage Bluebird.swift framework is missing CFBundleVersion

    Carthage Bluebird.swift framework (Bluebird.framework/Info.plist) is missing CFBundleVersion.

    From CFBundleVersion:

    This key is required by the App Store and is used throughout the system to identify the version of the build. For macOS apps, increment the build version before you distribute a build.

    ITMS error with framework bundled in App.app/Frameworks via Carthage:

    ERROR ITMS-90056: "This bundle Payload/App.app/Frameworks/Bluebird.framework is invalid. The Info.plist file is missing the required key: CFBundleVersion. Please find more information about CFBundleVersion at https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion"
    
    opened by mattrobmattrob 0
Releases(6.0.0)
Owner
Andrew Barba
Swift, iOS, Node.js, MongoDB
Andrew Barba
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 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
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
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
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

Matan Abravanel 55 Nov 29, 2021
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
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
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 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
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
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
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

Tom Lokhorst 68 Aug 31, 2022
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
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
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
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
High performance and lightweight UIView, UIImage, UIImageView, UIlabel, UIButton, Promise and more.

SwiftyUI High performance and lightweight UIView, UIImage, UIImageView, UIlabel, UIButton and more. Features SwiftyView GPU rendering Image and Color

Haoking 336 Nov 26, 2022