CancelForPromiseKit

badge-pod badge-languages badge-pms badge-platforms badge-mit badge-docs Build Status


API 文档 CancelForPromiseKit CPKAlamofire CPKCoreLocation CPKFoundation

CancelForPromiseKit 为优秀的 PromiseKitPromiseKit 扩展 提供了清晰简洁的取消功能。 虽然 PromiseKit 包含对取消的基本支持,但 CancelForPromiseKit 对此进行了扩展,使取消 promises 及其关联的任务变得简单明了。

例如

UIApplication.shared.isNetworkActivityIndicatorVisible = true

let fetchImage = URLSession.shared.dataTaskCC(.promise, with: url).compactMap{ UIImage(data: $0.data) }
let fetchLocation = CLLocationManager.requestLocationCC().lastValue

let context = firstly {
    when(fulfilled: fetchImage, fetchLocation)
}.done { image, location in
    self.imageView.image = image
    self.label.text = "\(location)"
}.ensure {
    UIApplication.shared.isNetworkActivityIndicatorVisible = false
}.catch(policy: .allErrors) { error in
    /* Will be invoked with a PromiseCancelledError when cancel is called on the context.
       Use the default policy of .allErrorsExceptCancellation to ignore cancellation errors. */
    self.show(UIAlertController(for: error), sender: self)
}.cancelContext

//…

// Cancel currently active tasks and reject all cancellable promises with PromiseCancelledError
context.cancel()

/* Note: Cancellable promises can be cancelled directly.  However by holding on to
   the CancelContext rather than a promise, each promise in the chain can be
   deallocated by ARC as it is resolved. */

注意:此 README 和整个项目的格式与 PromiseKit 相同,力求可读性和简洁性。 对于所有代码示例,PromiseKit 和 CancelForPromiseKit 之间的差异以粗体突出显示。

使用 CocoaPods 快速开始

在你的 Podfile

use_frameworks!

target "Change Me!" do
  pod "PromiseKit", "~> 6.0"
  pod "CancelForPromiseKit", "~> 1.0"
end

CancelForPromiseKit 具有与 PromiseKit 相同的平台和 Xcode 支持

示例

let promise = firstly {
    /* Methods and functions with the CC (a.k.a. cancel chain) suffix initiate a
    cancellable promise chain by returning a CancellablePromise. */
    loginCC()
}.then { creds in
    /* 'fetch' in this example may return either Promise or CancellablePromise --
        If 'fetch' returns a CancellablePromise then the fetch task can be cancelled.
        If 'fetch' returns a standard Promise then the fetch task cannot be cancelled,
        however if cancel is called during the fetch then the promise chain will still be
        rejected with a PromiseCancelledError as soon as the 'fetch' task completes.
        
        Note: if 'fetch' returns a CancellablePromise then the convention is to name
        it 'fetchCC'. */
    fetch(avatar: creds.user)
}.done { image in
    self.imageView = image
}.catch(policy: .allErrors) { error in
    if error.isCancelled {
        // the chain has been cancelled!
    }
}

// …

/* 'promise' here refers to the last promise in the chain.  Calling 'cancel' on
   any promise in the chain cancels the entire chain.  Therefore cancelling the
   last promise in the chain cancels everything.
   
   Note: It may be desirable to hold on to the CancelContext directly rather than a
   promise so that the promise can be deallocated by ARC when it is resolved. */
promise.cancel()

在上面的示例中:如果 fetch(avatar: creds.user) 返回一个标准的 Promise,则无法取消该 fetch。 但是,如果在 fetch 过程中调用 cancel,则一旦 fetch 完成,promise 链仍将被拒绝并抛出 PromiseCancelledError。 不会调用 done 代码块,而是调用 catch(policy: .allErrors) 代码块。

如果 fetch 返回一个 CancellablePromise,则在调用 cancel() 时,fetch 将被取消,并且会立即调用 catch 代码块。

CancellablePromise 包装了一个 delegate Promise,可以使用 promise 属性访问。 可以按如下方式修改上述示例,以便一旦 'loginCC' 完成,就无法取消该链

let cancellablePromise = firstly {
    loginCC()
}
cancellablePromise.then { creds in
    // For this example 'fetch' returns a standard Promise
    fetch(avatar: creds.user)  
    
    /* Here, by calling 'promise.done' rather than 'done' the chain is converted from a
       cancellable promise chain to a standard Promise chain. In this case, calling
       'cancel' during the 'fetch' operation has no effect: */
}.promise.done { image in
    self.imageView = image
}.catch(policy: .allErrors) { error in
    if error.isCancelled {
        // the chain has been cancelled!
    }
}

// …

cancellablePromise.cancel()

文档

以下函数和方法是核心 CancelForPromiseKit 模块的一部分。 带有 CC 后缀的函数和方法创建一个新的 CancellablePromise,而没有 CC 后缀的函数和方法接受现有的 CancellablePromise。

这是 Jazzy 生成的 CancelForPromiseKit API 文档

Global functions (all returning CancellablePromise unless otherwise noted)
	afterCC(seconds:)
	afterCC(_ interval:)

	firstly(execute body:)       // Accepts body returning CancellableTheanble
	firstlyCC(execute body:)     // Accepts body returning Theanble

	hang(_ promise:)             // Accepts CancellablePromise
	
	race(_ thenables:)           // Accepts CancellableThenable
	race(_ guarantees:)          // Accepts CancellableGuarantee
	raceCC(_ thenables:)         // Accepts Theanable
	raceCC(_ guarantees:)        // Accepts Guarantee

	when(fulfilled thenables:)   // Accepts CancellableThenable
	when(fulfilled promiseIterator:concurrently:)   // Accepts CancellablePromise
	whenCC(fulfilled thenables:) // Accepts Thenable
	whenCC(fulfilled promiseIterator:concurrently:) // Accepts Promise

	// These functions return CancellableGuarantee
	when(resolved promises:)     // Accepts CancellablePromise
	when(_ guarantees:)          // Accepts CancellableGuarantee
	when(guarantees:)            // Accepts CancellableGuarantee
	whenCC(resolved promises:)   // Accepts Promise
	whenCC(_ guarantees:)        // Accepts Guarantee
	whenCC(guarantees:)          // Accepts Guarantee


CancellablePromise: CancellableThenable
	CancellablePromise.value(_ value:)
	init(task:resolver:)
	init(task:bridge:)
	init(task:error:)
	promise                      // The delegate Promise
	result

CancellableGuarantee: CancellableThenable
	CancellableGuarantee.value(_ value:)
	init(task:resolver:)
	init(task:bridge:)
	init(task:error:)
	guarantee                    // The delegate Guarantee
	result

CancellableThenable
	thenable                     // The delegate Thenable
	cancel(error:)               // Accepts optional Error to use for cancellation
	cancelContext                // CancelContext for the cancel chain we are a member of
	isCancelled
	cancelAttempted
	cancelledError
	appendCancellableTask(task:reject:)
	appendCancelContext(from:)
	
	then(on:_ body:)             // Accepts body returning CancellableThenable or Thenable
	map(on:_ transform:)
	compactMap(on:_ transform:)
	done(on:_ body:)
	get(on:_ body:)
	asVoid()
	
	error
	isPending
	isResolved
	isFulfilled
	isRejected
	value
	
	mapValues(on:_ transform:)
	flatMapValues(on:_ transform:)
	compactMapValues(on:_ transform:)
	thenMap(on:_ transform:)     // Accepts transform returning CancellableThenable or Thenable
	thenFlatMap(on:_ transform:) // Accepts transform returning CancellableThenable or Thenable
	filterValues(on:_ isIncluded:)
	firstValue
	lastValue
	sortedValues(on:)

CancellableCatchable
	catchable                    // The delegate Catchable
	recover(on:policy:_ body:)   // Accepts body returning CancellableThenable or Thenable
	recover(on:_ body:)          // Accepts body returning Void
	ensure(on:_ body:)
	ensureThen(on:_ body:)
	finally(_ body:)
	cauterize()

扩展

只要底层异步任务支持取消,CancelForPromiseKit 就提供与 PromiseKit 相同的扩展和函数。

默认的 CocoaPod 提供核心的可取消 promises 和 Foundation 的扩展。 其他扩展可以通过在你的 Podfile 中指定额外的子规格来获得,例如

pod "CancelForPromiseKit/MapKit"
# MKDirections().calculateCC().then { /*…*/ }

pod "CancelForPromiseKit/CoreLocation"
# CLLocationManager.requestLocationCC().then { /*…*/ }

与 PromiseKit 一样,所有扩展都是单独的存储库。 这是 CancelForPromiseKit 扩展的完整列表,列出了支持取消的特定函数(省略了没有任何函数支持取消的 PromiseKit 扩展)

Alamofire

Alamofire.DataRequest
	responseCC(_:queue:)
	responseDataCC(queue:)
	responseStringCC(queue:)
	responseJSONCC(queue:options:)
	responsePropertyListCC(queue:options:)
	responseDecodableCC(queue::decoder:)
	responseDecodableCC(_ type:queue:decoder:)

Alamofire.DownloadRequest
	responseCC(_:queue:)
	responseDataCC(queue:)

Bolts
Cloudkit
CoreLocation
Foundation

Process
	launchCC(_:)
		
URLSession
	dataTaskCC(_:with:)
	uploadTaskCC(_:with:from:)
	uploadTaskCC(_:with:fromFile:)
	downloadTaskCC(_:with:to:)

MapKit
OMGHTTPURLRQ
StoreKit
WatchConnectivity

我不需要这些扩展!

与 PromiseKit 一样,扩展是可选的

pod "CancelForPromiseKit/CorePromise", "~> 1.0"

注意 Carthage 安装默认不带任何扩展。

选择你的网络库

现在可以轻松取消 PromiseKit 支持的所有网络库扩展!

Alamofire:

// pod 'CancelForPromiseKit/Alamofire'
// # https://github.com/dougzilla32/CancelForPromiseKit-Alamofire

let context = firstly {
    Alamofire
        .request("http://example.com", method: .post, parameters: params)
        .responseDecodableCC(Foo.self, cancel: context)
}.done { foo in
    //…
}.catch { error in
    //…
}.cancelContext

//…

context.cancel()

OMGHTTPURLRQ:


// pod 'CancelForPromiseKit/OMGHTTPURLRQ'
// # https://github.com/dougzilla32/CancelForPromiseKit-OMGHTTPURLRQ

let context = firstly {
    URLSession.shared.POSTCC("http://example.com", JSON: params)
}.map {
    try JSONDecoder().decoder(Foo.self, with: $0.data)
}.done { foo in
    //…
}.catch { error in
    //…
}.cancelContext

//…

context.cancel()

并且(当然)来自 Foundation 的普通 URLSession

// pod 'CancelForPromiseKit/Foundation'
// # https://github.com/dougzilla32/CancelForPromiseKit-Foundation

let context = firstly {
    URLSession.shared.dataTaskCC(.promise, with: try makeUrlRequest())
}.map {
    try JSONDecoder().decode(Foo.self, with: $0.data)
}.done { foo in
    //…
}.catch { error in
    //…
}.cancelContext

//…

context.cancel()

func makeUrlRequest() throws -> URLRequest {
    var rq = URLRequest(url: url)
    rq.httpMethod = "POST"
    rq.addValue("application/json", forHTTPHeaderField: "Content-Type")
    rq.addValue("application/json", forHTTPHeaderField: "Accept")
    rq.httpBody = try JSONSerialization.jsonData(with: obj)
    return rq
}

设计目标

let promise = firstly {
    loginCC() // Use CC (a.k.a. cancel chain) methods or CancellablePromise to
              // initiate a cancellable promise chain
}.then { creds in
    fetch(avatar: creds.user)
}.done { image in
    self.imageView = image
}.catch(policy: .allErrors) { error in
    if error.isCancelled {
        // the chain has been cancelled!
    }
}
//…
promise.cancel()
import Alamofire
import PromiseKit
import CancelForPromiseKit

func updateWeather(forCity searchName: String) {
    refreshButton.startAnimating()
    let context = firstly {
        getForecast(forCity: searchName)
    }.done { response in
        updateUI(forecast: response)
    }.ensure {
        refreshButton.stopAnimating()
    }.catch { error in
        // Cancellation errors are ignored by default
        showAlert(error: error) 
    }.cancelContext

    //…

    // **** Cancels EVERYTHING (however the 'ensure' block always executes regardless)
    context.cancel()
}

func getForecast(forCity name: String) -> CancellablePromise<WeatherInfo> {
    return firstly {
        Alamofire.request("https://autocomplete.weather.com/\(name)")
            .responseDecodableCC(AutoCompleteCity.self)
    }.then { city in
        Alamofire.request("https://forecast.weather.com/\(city.name)")
            .responseDecodableCC(WeatherResponse.self)
    }.map { response in
        format(response)
    }
}

支持

如果您有问题或要报告问题,请使用 我的 bug 跟踪器