Cancellation can come in many forms

In Swift’s concurrency model, cancellation is cooperative. To be a good concurrency citizen, code must periodically check if the current task has been cancelled, and react accordingly.

You can check for cancellation by calling Task.isCancelled or with try Task.checkCancellation() — the latter will exit by throwing a CancellationError if the task has been cancelled.

By convention, functions should react to cancellation by throwing a CancellationError. But this convention isn’t enforced, so callers must be aware that cancellation can manifest itself in other forms. Here are some other ways how functions might respond to cancellation:

  • Throw a different error. For example, the async networking APIs in Foundation, such as URLSession.data(from: URL), throw a URLError with the code URLError.Code.cancelled on cancellation. It’d be nice if URLSession translated this error to CancellationError, but it doesn’t.

  • Return a partial result. A function that has completed part of its work when cancellation occurs may choose to return a partial result rather than throwing the work away and aborting. In fact, this may be the best choice for a non-throwing function. But note that this behavior can be extremely surprising to callers, so be sure to document it clearly.

  • Do nothing. Functions are supposed to react promptly to cancellation, but callers must assume the worst. Even if cancelled, a function might run to completion and finish normally. Or it might eventually respond to cancellation by aborting, but not promptly because it doesn’t perform its cancellation checks often enough.

So as the caller of a function, you can’t really rely on specific cancellation behavior unless you know how the callee is implemented. Code that wants to know if its task has been cancelled should itself call Task.isCancelled, rather than counting on catching a CancellationError from a callee.