Bluebird.swift

CocoaPods Compatible Carthage Compatible Twitter

符合 Promise/A+ 规范,灵感来源于 Bluebird,使用 Swift 5 实现

特性

文档

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

要求

安装

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

构建 Bluebird 需要 CocoaPods 1.5.0+

pod 'Bluebird', '~> 5.0'

Carthage

github "AndrewBarba/Bluebird.swift" ~> 5.0

谁在使用 Bluebird

在生产环境中使用 Bluebird?请通过 Pull Request 或 Issue 告知我。

用法

Promise

Promises 是泛型的,允许您指定它们最终将解析成的类型。 创建 Promise 的首选方法是传入一个闭包,该闭包接受两个函数,一个用于解析 Promise,另一个用于拒绝 Promise

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

resolvereject 函数可以异步或同步调用。 这是一种很棒的方式来包装现有的 Cocoa API,以便在您自己的代码中解析 Promises。 例如,看一个操作图像的昂贵的函数

使用 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)
    }
  }
}
使用 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)
      }
    }
  }
}

好吧,所以函数内部的主体看起来几乎相同... 但是看看函数签名看起来好多了!

没有更多的完成处理程序,没有更多的可选图像,没有更多的可选错误。 原始函数中的可选参数是一个明显的标志,表明您将在不久的将来进行保护和解包。 通过 Promise 实现,该逻辑被良好的设计所隐藏。 现在使用这个新函数是一种乐趣

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

您可以使用 then 方法轻松地执行一系列操作

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

请注意,每次您从 then 处理程序返回 Promise(或值)时,下一个 then 处理程序都会收到该处理程序的解析结果,并等待前一个处理程序完全解析。 这对于异步控制流非常强大。

Grand Central Dispatch

Bluebird 中接受处理程序的任何方法也接受 DispatchQueue,因此您可以控制希望处理程序在哪个队列上运行

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
  }

默认情况下,所有处理程序都在 .main 队列上运行。

catch

使用 catch 来处理/从 Promise 链中发生的错误中恢复

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)
  }

上面,如果任何 then 处理程序 throw 抛出一个错误,或者如果从处理程序返回的 Promise 之一拒绝,则将调用最终的 catch 处理程序。

您还可以在运行多个异步操作时执行复杂的恢复

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

tap

适用于在 Promise 链中间执行操作而不更改 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 ... }

您还可以从 tap 处理程序返回 Promise,并且链将等待该 Promise 解析

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

使用 finally,您可以注册一个处理程序以在 Promise 链的末尾运行,无论其结果如何

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

无缝地连接不同类型的 Promises

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

map

迭代一个元素序列并执行每个操作

let articles = ...

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

您还可以使用 mapSeries() 串行迭代序列。

reduce

迭代一个序列并减少到解析为单个值的 Promise

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

等待所有 promise 完成

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

any

使用 any 轻松处理竞争条件,一旦一个 Promise 解析,处理程序就会被调用,并且永远不会再次被调用

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

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

try

启动 Promise 链

// 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
}

测试

测试在 Bitrise 上持续运行。 由于 Bitrise 不支持公共测试运行,我无法链接到它们,但您可以通过打开 Xcode 项目并从 Bluebird scheme 手动运行测试来自己运行测试。

Bluebird vs PromiseKit

如果我说 PromiseKit 不是一个很棒的库,那我就是在撒谎(它确实很棒!),但 Bluebird 有不同的目标,这些目标可能对不同的开发人员有吸引力,也可能没有吸引力。

Xcode 9+ / Swift 4+

PromiseKit 花费大量精力来维护与 Objective-C、以前版本的 Swift 和以前版本的 Xcode 的兼容性。 那是一堆工作,上帝保佑他们。

泛型 & 组合

Bluebird 在整个库中更复杂地使用了泛型,为我们提供了非常好的 API,用于在 Swift 中组合 Promise 链。

Bluebird 支持 mapreduceallany,支持任何 Sequence 类型,而不仅仅是数组。 例如,您可以在所有这些函数中使用 RealmListResult 类型,您不能使用 PromiseKit 执行此操作。

Bluebird 还支持 Promise.mapPromise.reduce(与 Bluebird.js 相同),它们的行为与它们的全局等效项相同,但可以在现有 Promise 上以内联方式链接,从而大大增强了 Promise 组合。

没有扩展

PromiseKit 提供了许多有用的框架扩展,这些扩展将核心 Cocoa API 包装在 Promise 风格的函数中。 我目前没有计划提供这样的功能,但如果我这样做,它将在不同的存储库中,以便我可以保持这个存储库精简且经过良好测试。

Bluebird API 兼容

我在 Node/JavaScript 项目中大量使用 Bluebird.js 之后开始使用 PromiseKit,但对细微的 API 差异以及完全缺少的一些东西感到恼火。 Bluebird.swift 尝试紧密遵循 Bluebird.js 的 API

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):
    ...
  }
}

这些只是一些差异,并且 Bluebird.swift 肯定缺少 Bluebird.js 中的功能,但我的目标是缩小差距并继续维护一个 API,使其在适用的情况下更紧密地匹配。