

Async/Await and the Future of Combine
source link: https://benscheirman.com/2021/06/async-await-and-the-future-of-combine/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Async/Await and the Future of Combine
Swift 5.5 is here with Xcode 13 Beta and with it comes my favorite new addition to Swift: Async/Await.
Async/await is a high level, structured model for concurrency in Swift that allows you to write expressive async code in shockingly little ceremony.
A code sample is probably best for this, so consider this example where we want to fetch an image showing the current weather conditions for the user’s location.
To do this requires multiple steps:
- First get the user’s location. If that gives an error, fail early.
- Then use that to get the weather conditions
- Look in that response to get an image URL for the current condition
The callback way
Using traditional callbacks we might have these methods:
func getLocation(completion: @escaping (Result<CLLocation, Error>) -> Void) {
// ...
}
func getWeather(for location: CLLocation, completion: @escaping (Result<WeatherCondition, Error>) -> Void) {
// ...
}
Putting these together gives you:
getLocation { result in
switch result {
case .failed(let error):
// do something with location error
case .success(let location):
getWeather(for: location) { result in
switch result {
case .failed(let error):
// do something with weather error
case .success(let conditions):
// fetch image
let imageTask = URLSession.shared.dataTask(with: conditions.imageURL) { (data, response, error) in
// you get the idea
}
}
}
}
Nested completion callbacks each with their own failure path is a giant pile of yuck. If we fail to call back with a failure from a previous step we might end up with an application that seems to get stuck (maybe showing a spinner forever). There’s nothing the compiler does here to help us. And if we have to ensure to call back any completion handler on a specific queue, there’s quite a lot of boilerplate which makes the above code even worse.
Of course, Combine makes this much nicer, but more on that in a minute.
The Async/Await Way
Now with Swift 5.5 this gets a whole lot better.
func userLocation() async throws -> CLLocation {
// ...
}
func weatherConditions(for location: CLLocation) async throws -> WeatherConditions {
// ...
}
Notice how these functions return actual values. They also indicate that they are async
and they can throw
errors.
There’s also lots of built-in SDK functions that have been adapted to be async, like URLSession.data(for:)
.
Putting this all together:
let location = try await userLocation()
let conditions = try await weatherConditions(for: location)
let (imageData, response) = try await URLSession.shared.data(from: conditions.imageURL)
if (response as? HTTPURLResponse)?.statusCode == 200 {
let image = UIImage(data: imageData)
} else {
// failed
}
Notice now the code reads from top to bottom. When we get to the await
keyword, our execution is suspended without blocking the thread, so the system is free to do other work.
There’s a lot of nuance here that I’m hand-waving over, but the key point is this is objectively better:
- It’s much easier to write
- It’s much easier to read
- The compiler can enforce we handle all execution paths and errors
So this looks like the future, right? What about Combine?
What about Combine?
Back in iOS 13 we saw the release of Combine, Apple’s take on functional reactive programming. With Combine you can leverage Publishers that produces values over time, and then transform those values using Operators.
Combine provides a ton of power, but comes at a steep learning curve. This is what led me to create a full course on Combine. So you can imagine that many of us were wondering what would be added to Combine this year in light of all the async/await stuff we’ve seen in Swift Evolution over the past few months.
But searching the API diffs for anything Combine related yielded 0 results. Nothing.
😭 #WWDC21 pic.twitter.com/OlXlXvo2ar
— Casey Liss (@caseyliss) June 7, 2021
Searching the API diffs fro Combine yields nada
Of course Apple will not tell us what the future plans are, but if we read the tea leaves a bit, it seems to me that this is pretty telling.
Combine’s strengths lie in the fact that it is modeled after streams of events or values. The assumption is that a Publisher can (and often does) emit multiple values before finishing. Many Publishers fire once and complete, but that is an implementation detail.
Contrast that with your typical completion callback or the async example above. Each of these produce a single value and return it.
So what does async/await do for multiple values?
Meet AsyncSequence
AsyncSequence is a new type that allows you to concurrently iterate over a series of async values. If you look at the API docs, it sure does have a lot of the same operations you’d find in a Combine pipeline:
- filter
- prefix
- contains
- reduce
- flatMap
I’ve yet to play around with AsyncSequence
(at the time of writing this the session is not yet available), but it certainly appears that this is shaping up to be an eventual replacement for Combine.
Looking Ahead at AsyncStream
The best way to get an idea of what is coming is to take a look at Swift Evolution. Here we can see a new proposal, SE-0314 AsyncStream and AsyncThrowingStream.
The continuation types added in SE-0300 act as adaptors for synchronous code that signals completion by calling a delegate method or callback function. For code that instead yields multiple values over time, this proposal adds new types to support implementing an AsyncSequence interface.
The key point that this proposal suggests is to bridge the async/await continuation model - which currently assumes a single result or error - into working with multiple values over time.
So it certainly seems that Apple is creating an async model capable of potentially replacing Combine, at least for the majority of use cases.
Is Combine Going Away?
My previous stance was basically: “You should use Combine because it makes complex async code easier to write, reason about, and Apple will support it”.
I’m now second-guessing that statement somewhat.
Combine won’t go away (at least, not for a long while) but I think it’s use may be limited to the more Functional Reactive programming style and most of the async ergonomics we get from Swift 5.5 will be preferred by most developers. Things like back-pressure and demand management are important concepts for a fully reactive programming framework, but not every async pipeline needs that complexity.
Also, SwiftUI currently depends on Combine for reacting to state changes in your view models.
Maybe next year we will see some Publisher additions that adopt some of the async/await features to make working with Combine simpler. Or perhaps we’ll see AsyncStream take over the majority of these use-cases.
So should you avoid learning Combine? No! I think it’s extremely useful still, and will be more available than async/await since that currently requires macOS 12 / iOS 15. So for my projects that need to support older versions, I’ll still be leaning on Combine for my async needs.
And in the meantime, if you'd like to learn Combine, I'd love for you to check out my course.
Recommend
-
13
Introduction JavaScript touts asynchronous programming as a feature. This means that, if any action takes a while, your program can continue doing other things while the action completes. Once that action is done, you can do somethin...
-
11
The danger of async/await and .Result in one picture Sync over async in .NET is always b...
-
13
Callback, Promise and Async/Await by Example in JavaScript This post is going to show, by way of code examples, how to take a callback based API, modify it to use Promises and then use the Async/Await syntax. This post won't go...
-
6
Swift 5.5 Brings Async/Await and Actor Support Jun 13, 2021 2...
-
9
One of the core concepts of JavaScript is asynchronicity, which means doing many things simultaneously. It's a solution for avoiding your code being blocked by a time-intensive operation (like an HTTP request). In this article, you're going t...
-
19
本文内容来自Writing an OS in Rust博客。 多任务处理 几乎所有操作系统的基本功能都包含多任务处理,即并发执行多个任务的能力(multitasking)。例如,你可能边...
-
10
When and How to use Async/Await?Krunal ShahNov 19, 20194 min readLast Updated May 02, 2022
-
15
Async and Await in simple Imagine you are a student who needs to study...
-
6
聊聊 Combine 和 async/await 之间的合作在 Xcode 13.2 中,苹果完成了 async/await 的向前部署(Back-deploying)工作,将最低的系统要求降低到了 iOS 13(macOS Catalina),这一举动鼓舞了越来越多的人开始尝试使用 async/await 进行开发。当...
-
16
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK