Skip to content
tezvyn:
Menu

withCheckedThrowingContinuation: Bridge Callbacks to Async/Await

MediumHow cards are made

withCheckedThrowingContinuation bridges legacy completion-handler APIs into async/await. Wrap one-shot callback-based work so callers use try await instead of nested blocks. The footgun is resuming twice or never, which checked runtimes catch in debug builds.

Why it exists

Before Swift introduced native concurrency, asynchronous work was expressed through completion handlers and delegate methods. When async/await arrived, there was a massive gap because thousands of APIs already shipped with callback-based signatures that could not be awaited. withCheckedThrowingContinuation was built to close that gap mechanically, letting developers suspend an async task and resume it later from a legacy callback without rewriting the underlying framework.

The mental model

Treat the function like a single-use promise token. Your async code pauses and hands a token to an old-fashioned callback API. When that API finishes, it redeems the token exactly once, either with a successful value or a thrown error, which wakes up the awaiting caller. The checked flavor adds a safety guard. If you accidentally redeem the token twice or let it fall on the floor, Swift raises a runtime warning or fatal error in debug builds, turning a subtle concurrency bug into an obvious crash.

How it works

Inside an async throws function, you call withCheckedThrowingContinuation and receive a CheckedContinuation value in a closure. You start your legacy asynchronous operation from within that closure. In the operation's completion handler, you call continuation.resume(returning:) to deliver a value, or continuation.resume(throwing:) to deliver an error. The runtime enforces that resume happens exactly once. If you call it a second time, the program traps in debug. If the closure returns without resuming, the runtime detects the leaked continuation and logs a warning.

When to use it

Reach for this wrapper when you need to expose a one-shot callback-based API as an async throws function. Typical cases include wrapping Objective-C framework methods, older Swift libraries that use completion handlers, or C APIs that signal completion through function pointers. It shines when the result is a single value or error rather than a sequence.

When not to use it

Do not use it for multi-value streams because AsyncStream and AsyncThrowingStream are the proper tools for callbacks that fire repeatedly. Avoid it when a native async alternative already exists, because the native path avoids the manual resume bookkeeping entirely. Also skip it if the underlying API has complex ordering or stateful delegate callbacks that are easier to reason about in their original form.

One canonical example

Imagine wrapping URLSession.dataTask before the async data(from:) method existed. You enter withCheckedThrowingContinuation, create a data task, and inside the task's completion handler you call resume(returning: data) on success or resume(throwing: networkError) on failure. The caller then writes let (data, response) = try await myWrappedRequest(), getting a flat sequential flow instead of a nested closure pyramid.

Interview question

Which scenario is the best fit for withCheckedThrowingContinuation?

  • a.A timer that fires every second to update a counter
  • b.A network request that delivers a single response or error via a completion handlerCorrect
  • c.A delegate protocol that emits multiple ordered events during a user session
  • d.A modern Swift API already exposing native async/await methods
Why?

withCheckedThrowingContinuation is designed for one-shot callbacks that resume exactly once with a value or error. A delegate protocol emitting multiple ordered events is tempting to wrap but is explicitly discouraged, as complex stateful delegates are harder to reason about when flattened into a single continuation.

Just read this? Test yourself on what you have been reading.

Put your scrolling time to good use

Learn one idea, try a quiz and save useful cards for revision. Tezvyn makes it easy to learn and stay current in your tech field, a few minutes at a time.

The iPhone app is on the way

We are building it. Until it lands, nothing here is held back from you: every interview card, your saved cards, streaks and the job board all work in Safari, plus hundreds of free practice quizzes of thirty questions each. Sign in and it all carries over to the app the day it arrives.

Want it as an icon? Tap Share at the bottom of Safari, then Add to Home Screen. It opens full screen and the cards you have read stay available offline.

Get it on Google PlayiPhone app coming soon

We are hiring for this. Open roles that interview on swift — each one lists the topics its interview covers.

See open roles