Documentation:
Afluent is a Swift library that lives between swift-async-algorithms and foundation, adding reactive operators to async/await and AsyncSequence. The goal of Afluent is to provide a reactive friendly operator style API to enhance Apple's offerings. As a consequence, Afluent will add features that Apple has either already built or is actively building. While async/await has simplified asynchronous code, it doesn't offer the full suite of operations for transforming, combining, and error-handling that Combine does. Afluent deliberately keeps as much of the Combine API as makes sense to make moving from Combine to Afluent much easier. As a consequence, you may have some minor symbol collisions when you import both Combine and Afluent in the same file.
- Fluent, chainable interface
- A rich set of built-in methods like map,flatMap,catch,retry, and many more
- Built to work seamlessly with Swift's new async/awaitsyntax
- Test utilities to facilitate common async/awaittesting needs
Add the Afluent package to your target dependencies in Package.swift:
dependencies: [
    .package(url: "https://github.com/Tyler-Keith-Thompson/Afluent.git", from: "1.0.0")
]Then, add the Afluent target as a dependency to your package targets:
.target(name: "MyLib",
        dependencies: [
            .product(name: "Afluent", package: "Afluent"),
        ])For test targets where you'd like to use Afluent's testing utilities, add the AfluentTesting target as a dependency:
.testTarget(name: "MyLibTests",
            dependencies: [
                .product(name: "AfluentTesting", package: "Afluent"),
            ])While Combine always deals with sequences, async/await offers tasks. Tasks differ from sequences in that they will only ever emit one value eventually, a sequence may emit zero or more values eventually. This means instead of the Combine approach of dealing with a sequence of one element, you can use tasks for a more guaranteed execution. This is very ergonomic when working with network requests, check out the following example:
struct Post: Codable {
    let userId: UInt
    let id: UInt
    let title: String
    let body: String
}
let posts = try await DeferredTask {
    try await URLSession.shared.data(from: URL(string: "https://jsonplaceholder.typicode.com/posts")!)
}
.map(\.0) // Extract the data from the URLSession response
.decode(type: [Post].self, decoder: JSONDecoder()) // Decode the JSON into an array of `Post` objects
.retry() // Automatically retry the request if it fails
.execute() // Execute the deferred task and await the result.
//.run() // Run could've also been used to execute the task without awaiting the result.In this example:
- DeferredTaskinitiates the asynchronous task.
- mapextracts the data payload from the URLSession data task response.
- decodetakes that data and decodes it into an array of Post objects.
- retrywill retry the task if it encounters an error.
- executeruns the entire chain and awaits the result.
- runruns the entire chain without awaiting the result.
There are times when you need sequence mechanics and Afluent is there to help! Here's the same example, but converted to an AsyncSequence with all the same operators.
let posts = try await DeferredTask {
    try await URLSession.shared.data(from: URL(string: "https://jsonplaceholder.typicode.com/posts")!)
}
.toAsyncSequence() // Convert this to an AsyncSequence, thus enabling Swift Async Algorithms and standard library methods
.map(\.0) // Extract the data from the URLSession response
.decode(type: [Post].self, decoder: JSONDecoder()) // Decode the JSON into an array of `Post` objects
.retry() // Automatically retry the request if it fails
.first() // Get the first result from the sequence- 
Conciseness: Afluent's chainable interface reduces boilerplate, making complex operations more concise. 
- 
Built-in Error Handling: Afluent's retrymethod elegantly handles retries, eliminating the need for manual loops and error checks.
- 
Rich Set of Operations: Beyond retries, Afluent offers operations like map,flatMap, and over 20 more, enriching theasync/awaitexperience.
- 
Readability: Afluent's fluent design makes the code's intent clearer, enhancing maintainability. 
By enhancing async/await with a rich set of operations, Afluent simplifies and elevates the asynchronous coding experience in Swift.
Adopting Afluent: A Guide for Combine Users
If you're familiar with Combine and are looking to transition to Afluent, this guide will help you understand the similarities and differences, making your adoption process smoother. Afluent deliberately uses an API that is very similar to Combine, making the transition easier.
- Asynchronous Units of Work vs. Publishers vs. AsyncSequence:
- In Combine, you work with Publishers.
- In Afluent, there are 2 choices.
- For an asynchronous operation that emits one value eventually, use AsynchonousUnitOfWork. This is perfect for network requests or other "one-time" async operations and comes with all the operators that Combine comes with (includingshare).
- For async operations that emit multiple values over time, use AsyncSequenceand simply rely on Afluent operators that extend both the standard library and Apple's open source Swift Async Algorithms package. This does not have 100% parity with Combine on its own, but almost entirely gives the same operators as Combine when combined with both of Apple's libraries.
 
- For an asynchronous operation that emits one value eventually, use 
- Built for async/await: Afluent is designed around Swift'sasync/awaitansAsyncSequencesyntax, making it a natural fit for the new concurrency model.
- 
JustandFuture: In Combine, you might useJustfor immediate values andFuturefor asynchronous operations.- For an AsynchronousUnitOfWorkDeferredTaskwill replace bothJustandFuture.
- For AsyncSequenceAfluent offers aJustsequence and the standard library'sAsyncStreamorAsyncThrowingStreamprovide the same mechanics as Combine'sFuture.
 
- For an 
- 
map,flatMap,filter,merge,zip, etc...:- For an AsynchronousUnitOfWorkthe operators that make sense are all available within Afluent directly. For example,map, andflatMapmake perfect sense, butfilterdoesn't, because anAsynchronousUnitOfWorkonly ever emits one value.
- For an AsyncSequencethese operators are almost entirely provided by either Foundation or AsyncAlgorithms.
 
- For an 
- 
catchandretry: Afluent provides these methods, similar to Combine, to handle errors and retry operations.
- 
assign: Afluent also has anassignoperator, similar to Combine's.
- 
sinkandsubscribe:- For an AsynchronousUnitOfWorksubscribeis the method Afluent provides. It's deliberately a little different thansinkas only one value will be emitted. However, they serve the same general purpose.
- For an AsyncSequenceAfluent provides asinkmethod that works the same way Combine's does.
 
- For an 
- 
Understand the Scope: - If you only emit one value eventually, Afluent can completely replace Combine. You'll probably use a DeferredTaskand go from there.
- If you emit multiple values over time, you'll probably want to use a combination of Afluent, Foundation, and AsyncAlgorithms to supplement Combine. With all 3 of these you can get the majority of behavior Combine offered.
 
- If you only emit one value eventually, Afluent can completely replace Combine. You'll probably use a 
- 
Embrace the Differences: Afluent does not have a customizable Failuretype like publishers in Combine. EveryAsynchronousUnitOfWorkcan throw aCancellationError, making the failure type alwaysError.
- 
Use Documentation: Afluent's documentation is a valuable resource. Refer to it often as you transition. 
- 
Join the Community: Engage with other Afluent users on GitHub. Sharing experiences and solutions can be beneficial. 
Remember, while Afluent and Combine have similarities, they are distinct libraries with their own strengths. Embrace the learning curve, and soon you'll be leveraging the power of Afluent in your projects.
Frequently Asked Questions (FAQs)
1. How can I contribute to Afluent or report issues?
Afluent is hosted on GitHub. You can fork the repository, make changes, and submit a pull request. For reporting issues, open a new issue on the GitHub repository with a detailed description.
4. Why is it Afluent and not Affluent? Async/Await + Fluent == Afluent
