Skip to content

Using async and await with Alamofire

iOS, Swift, Alamofire2 min read

Introduced in Swift 5.5, async and await are language features designed to simplify asynchronous code by allowing developers to write and read asynchronous code in a more sequential and synchronous-like manner. This helps in reducing callback-based pyramid of doom commonly associated with traditional asynchronous code structures.

When a function is marked as async, it means that the function may perform asynchronous operations and that it can be called using the await keyword inside of another async function. The await keyword is used to suspend the current task until the awaited asynchronous operation is completed, providing a seamless way to work with asynchronous code in Swift.

A lot of the examples in Alamofire's documentation use the standard completion handlers pattern but if you're like me maybe you want to take advantage of asynchronous code to improve the readability of your project's code base. Thankfully Alamofire does support async/await! So let's take a look at it.

Integrating async and await with Alamofire

Let's consider an example scenario where we want to make a GET request to fetch JSON data from an API endpoint using Alamofire and then parse the response using the Codable protocol.

If you're used to the completion handlers pattern you might imagine some code that looks like this:

1func fetchPostsOld(completion: @escaping (Result<[Post], Error>) -> Void) {
2 let url = ""
4 AF.request(url).responseData { response in
5 switch response.result {
6 case .success(let data):
7 do {
8 let posts = try JSONDecoder().decode([Post].self, from: data)
9 completion(.success(posts))
10 } catch {
11 completion(.failure(error))
12 }
13 case .failure(let error):
14 completion(.failure(error))
15 }
16 }

Here's how we can achieve the same result using async and await:

1import Foundation
2import Alamofire
4struct Post: Codable {
5 let userId: Int
6 let id: Int
7 let title: String
8 let body: String
11func fetchPosts() async throws -> [Post] {
12 let url = ""
13 return try await AF.request(url).serializingDecodable([Post].self).value

In the above example, the fetchPosts function is marked as async, indicating that it performs asynchronous operations. Inside the function, we use await with AF.request(...).responseData() to make the network request and wait for the response. Once the response is received, we decode the JSON data into an array of Post objects using the JSONDecoder. As you can see we have added 'throw' after async so that errors can be thrown.

Handling errors

When working with asynchronous operations, error handling becomes an essential part of the process. With async and await, error propagation is simplified using Swift's built-in error handling mechanism.

Here's how we can handle errors when using async and await with Alamofire:

1func fetchPosts() async throws -> [Post] {
2 let url = ""
4 do {
5 return try await AF.request(url).serializingDecodable([Post].self).value
6 } catch {
7 throw error
8 }

In the code snippet above, any error thrown within the fetchPosts function can be caught and propagated using Swift's throws and try mechanisms. This allows for concise and predictable error handling when working with asynchronous tasks.

By leveraging async and await with Alamofire, we can simplify the process of making asynchronous network requests in Swift. These language features not only improve the readability of asynchronous code but also enhance the overall maintainability and robustness of our networking layer.