CancelForPromiseKit 1.1.0

CancelForPromiseKit 1.1.0

Doug Stein 维护。



  • 作者:
  • Doug Stein

CancelForPromiseKit

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


API 文档 CancelForPromiseKit CPKAlamofire CPKCoreLocation CPKFoundation

CancelForPromiseKit 为最优秀的 PromiseKitPromiseKit 扩展 提供了清晰简洁的取消功能。虽然 PromiseKit 包含基本的取消支持,但 CancelForPromiseKit 将其扩展为使取消承诺及其相关任务变得简单直接。

例如

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()
  • 将 Promise 和 CancellablePromise 混合以取消某些分支而不取消其他分支

在上面的例子中:如果 fetch(avatar: creds.user) 返回一个标准 Promise,那么检索不能被取消。然而,如果在检索过程中调用 cancel,那么在检索完成时,promise chain 仍然会因 PromiseCancelledError 被拒绝。不会调用 done 块,而是调用 catch(policy: .allErrors) 块。

如果 fetch 返回一个 CancellablePromise,那么当调用 cancel() 时,检索将被取消,并且立即调用 catch 块。

  • 使用 'delegate' promise

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 提供了核心可取消的承诺和 Foundation 扩展。其他扩展通过在您的 Podfile 中指定附加 subspec 可用,例如

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
}

设计目标

  • 提供了一种简化的方式来取消Promise链,这会拒绝所有关联的Promise并取消所有关联的任务。例如
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()
  • 确保Promise链被取消后,后续的代码块永远不会被调用

  • 完全支持并发,确保所有代码都是线程安全的

  • 为所有适当的PromiseKit扩展(例如 Foundation、CoreLocation、Alamofire等)提供可取消的变体

  • 支持所有的PromiseKit原语(例如 'after'、'firstly'、'when'、'race'等)的取消操作

  • 提供一种简单的方法来构建新的可取消的Promise类型

  • 确保Promise分支正确取消。例如

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

支持

如果您有任何问题或需要报告的问题,请使用我的错误追踪器