Swiftpack.co - Package - AndrewBarba/Bluebird.swift

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: "4.0.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.

Github

link
Stars: 36

Dependencies

Used By

Total: 0

Releases

v5.1.0 - Combine - 2020-01-08 01:00:36

  1. Support Combine framework with new .publisher() method

v5.0.0 - Swift 5.0 - 2019-03-26 21:03:31

  1. Built with Swift 5.0

v4.0.0 - Swift 4.2 - 2018-11-17 19:56:37

  1. Built for Swift 4.2

v3.0.0 - Unfair Lock - 2018-08-16 03:07:35

  1. On iOS 10+ and macOS 10.12+ the locking mechanism for Promise is backed by os_unfair_lock bringing a significant performance improvement. On prior versions of iOS and macOS the locking mechanism falls back to a serial DispatchQueue
  2. Expose a new Lock class that hides the implementation detail of the underlying lock, preferring os_unfair_lock when available, and falling back to a serial DispatchQueue when it's not.

v2.4.0 - Promise Cancellation - 2018-03-16 17:05:53

  1. Add Promise.cancel
  2. Add Promise { resolve, reject, onCancel in }

v2.2.0 - tapCatch - 2018-02-02 22:16:05

  1. Add Promise.tapCatch

v2.1.0 - Docs, SPM, Return - 2018-01-19 17:37:44

  1. Add Promise.return
  2. Support CocoaPods v1.4.0
  3. Support Swift Package Manager
  4. Hosted documentation generated with Jazzy

v2.0.1 - Swift Package Manager - 2017-10-17 12:26:03

  1. Add missing Foundation imports for Swift Package Manager

v2.0.0 - Swift 4 - 2017-09-26 14:21:51

  1. Swift 4 and Xcode 9
  2. Renamed catchThen to recover
  3. Added Promise/A+ test suite

v1.11.0 - Swift 3.1 - 2017-06-06 14:27:20

  1. Swift 3.1

v1.10.3 - QOS Priotity - 2017-04-12 18:50:31

1.10.3

  1. QOS on internal state queue

v1.10.2 - Open Promise - 2016-12-27 17:57:47

  1. Open Promise class

v1.10.1 - Thread Sanitizer - 2016-11-30 20:49:36

  1. Better state handling using the sync queue. Fixes any issues I found in Xcode 8's Thread Sanitizer.

v1.10.0 - Catch Then - 2016-10-13 21:50:27

  1. Introduce Promise.catchThen to recover from a rejection with another result of the same type

v1.9.0 - Timeout and Delay - 2016-10-12 03:38:46

  1. Add Promise.delay for delaying the execution of a Promise chain
  2. Add Promise.timeout to reject a Promise if it does not resolve in a given amount of time

v1.8.0 - Reflect and Defer - 2016-10-12 00:01:23

  1. Add Promise.reflect for returning a Promise that will always resolve
  2. Add Promise<Type>.defer for returning a resolver tuple that can be used to resolve a Promise outside of the default constructors

v1.7.0 - Chain map and reduce - 2016-10-08 22:28:24

  1. Add Promise.map and Promise.mapSeries, identical to the global map() and mapSeries(), but can be chained on an existing Promise that resolves to a sequence
  2. Add Promise.reduce, identical to the global reduce(), but can be chained on an existing Promise that resolves to a sequence
  3. Rename map(series:) to mapSeries for API consistency

v1.6.0 - All Platforms - 2016-10-08 17:03:28

  1. Add targets for all platforms: iOS, macOS, tvOS, watchOS
  2. Fix incorrect dispatch queue usage in all

v1.5.0 - 2016-10-06 19:31:05

  1. Add reduce function to iterate over a sequence and resolve to a single value

v1.4.1 - Generic Sequences - 2016-10-04 18:00:51

  1. Support generic sequence types in map, all, any, race

v1.4.0 - 2016-10-04 15:57:24

  1. Add a (on:queue) parameter to all functions. Defaults to .main
  2. Add a try function for beginning a Promise chain

v1.3.1 - Functional Map Series - 2016-10-04 12:31:40

A much cleaner, more readable implementation of map(series:)

v1.3.0 - Map Series - 2016-10-04 04:47:50

  1. Map over a collection that resolves each promise in series

v1.2.0 - Include `on` argument when using `finally` - 2016-10-03 21:12:54

  1. Fixes on API inconsistency where finally did not have the on argument name

v1.1.0 - Full Test Coverage - 2016-10-03 20:21:28

  1. Add full test coverage for every Promise function
  2. Convert static Promise functions (all, any, join, map) to be free standing functions because we can't add static functions on a generic class...

v1.0.2 - Full Documentation - 2016-10-03 04:25:14

  1. No API was added/changed in this release
  2. 100% documented

Todo: Unit tests! Those are next, top priority.

v1.0.1 - Initial Release - 2016-10-03 01:12:15

Initial release. Full documentation can be found on CocoaDocs