How to handle Errors on never ending chain with materialize? - swift

Imagine the following chain where a user wants to save a list of some sort:
var saveChain = userTappedSaveListSubject
.doOnNext { list -> Void in // create pdf version
let pdfFactory = ArticleListPDFFactory()
list.pdf = try pdfFactory.buildPDF(list)
try database.save(list)
}
.flatMap { list in
AuthorizedNetworking.shared.request(.createList(try ListRequestModel(list)))
.filter(statusCode: 201)
.map { _ in list }
}
.doOnNext { list in
list.uploaded = true
try database.save(list)
try Printer().print(list)
}
.materialize()
.share()
On every operator in the chain errors can occur, which would terminate the stream and the user would be unable to retry saving and printing the list (the whole chain gets disposed).
In the end the user should see either a "success" or "failure" screen by binding the observable to a textField:
Observable.of(
saveChain.elements().map { _ in
("List saved!", subtitle: "Saving successfull")
},
saveChain.errors().map { error in
("Error!", subtitle: error.localizedDescription)
})
.merge()
How should the error be handled?

Here's the obvious fix:
let saveChain = userTappedSaveListSubject
.flatMap { list in
Observable.just(list)
.do(onNext: { list -> Void in // create pdf version
let pdfFactory = ArticleListPDFFactory()
list.pdf = try pdfFactory.buildPDF(list)
try database.save(list)
})
.flatMap { list in
AuthorizedNetworking.shared.request(.createList(try ListRequestModel(list)))
.filter(statusCode: 201)
.map { _ in list }
}
.do(onNext: { list in
list.uploaded = true
try database.save(list)
try Printer().print(list)
})
.materialize()
}
.share()
However, there are a host of problems with this code because of the mixed paradigms.
You are passing around a mutable class inside your Observables. This is problematic because it's a functional paradigm so the system expects the contained type to be either a struct/enum or an immutable class.
Your reliance on side effects to load up said mutable class object again is quite odd and against the paradigm.

Related

Swift equivalent of await Promise.all

I'm coming from JS and learning Swift to build an iOS native version of an app.
In JS, I use the following pattern all the time:
...
async function doAyncFunction(item) {
try {
// do async call to fetch data using item
return Promise.resolve(data);
} catch (error) {
return Promise.reject(error);
}
}
const promises = items.map((item) => doAyncFunction(item));
const results = await Promise.all(promises);
...
I've started looking at PromiseKit, but I'm wondering what's are the Swift ways of doing this?
Thanks.
The forthcoming Swift 5.5 in Xcode 13 (still in beta at this point in time) uses a very similar async-await pattern. See The Swift Programming Language: Concurrency.
In the interim, there are a unfortunately dizzying number of alternatives. For example, there are a variety of third-party promise/future frameworks. Or there is the declarative Combine framework, which was launched a few years agar with the advent of the non-imperative patterns of SwiftUI.
All of that having been said, the most common pattern you’ll see in Swift code is the use of escaping “closures” which are effectively units of code that are passed as a parameter to a function, and which the function invokes when the asynchronous task completes. In that pattern you don’t await, but rather just specify what you want to do when the asynchronous task finishes. For example, in this function, it has a parameter called completion which is a closure that is called when the asynchronous task completes:
func fetch(using value: Int, completion: #escaping (Result<Foo, Error>) -> Void) {
let url = …
let task = URLSession.shared.dataTask(with: url) { data, response, error in
// handle errors, if any, e.g.:
if let error == error else {
completion(.failure(error))
return
}
// parse data into `Foo` here, and when done, call the `completion closure:
…
completion(.success(foo))
}
task.resume()
}
And then you would call it like so:
fetch(using: 42, completion: { result in
// this is called when the fetch finishes
switch result {
case .failure(let error): // do something with `error`
case .success(let foo): // do something with `foo`
}
})
// note, execution will continue here, and the above closure will
// be called later, so do not try to use `foo` here
Or, using a more concise “trailing closure” syntax:
fetch(using: 42) { result in
// this is called when the fetch finishes
switch result {
case .failure(let error): // do something with `error`
case .success(let foo): // do something with `foo`
}
}
// note, execution will continue here, and the above closure will
// be called later, so do not try to use `foo` here
And if you wanted to be notified when a series of calls was done, you could use a DispatchGroup, e.g.
let group = DispatchGroup()
for value in values {
group.enter()
fetch(using: value) { result in
// do something with result
group.leave()
}
}
group.notify(queue: .main) {
// this is called when every `enter` call is matched up with a `leave` Call
}
It is up to you whether you stick to the beta version of Swift 5.5 with a very familiar async-await pattern, use a third-party future/promise library, use Combine, or use the traditional closure-based pattern, shown above.
At the very least, I would suggest familiarizing yourself with this latter pattern as it is the predominant technique in Swift right now. But rest assured that the familiar async-await pattern is coming soon, so if you are willing to wait for it to finish going through the beta process (or join that beta process), then check that out.
Using the aforementioned builtin Combine framework, you have several options. The one that you probably want is Publishers.Merge:
let publishers = ... // multiple functions that implement the Publisher protocol
let combined = Publishers.MergeMany(publishers)
Alternatives to MergeMany are Merge, Merge3, Merge4 up to Merge8 when the amount of publishers is set. If the number of outputs is variable, use MergeMany.
Other options include merge on the publishers themselves:
let publisher1 = ...
let publisher2 = ...
publisher1.merge(publisher2)
CombineLatest or, in the case of a publisher that immediately completes, Zip can be used to receive a tuple when everything is done:
let publisher1 = ...
let publisher2 = ...
Publishers.CombineLatest(publisher1, publisher2)
For the moment there is a great framework that is closest to async/await, it's SwiftCoroutine https://github.com/belozierov/SwiftCoroutine (much better than promiseKit, I tested the 2..)
Swift coroutine with your example:
func doFutureFunction() -> CoFuture<Int> {
CoFuture { promise in
myRequest { error, data in
if let error = error {
promise(.failure(error))
} else {
promise(.success(data))
}
}
}
}
let futures = items.map { item in doFutureFunction(item) } // [CoFuture<Int>]
DispatchQueue.main.startCoroutine {
let results = promises.compactMap { try? $0.await() } // [Int]
}
The equivalent of
consts value = await future.value
consts value1 = await future.value
consts value2 = await future.value
console.log("Value " + value + ", value1 " + value1 + ", value2 " + value2)
is
DispatchQueue.main.startCoroutine {
do {
let value = try future.await()
let value1 = try future.await()
let value2 = try future.await()
print("Value \(value), value1 \(value1), value2 \(value2)")
} catch {
print(error.localizedDescription)
}
}
While waiting for swift 5.5 and official async / await from Apple
You can look at PromiseQ it's javascript style promises for Swift. It implements all javascript's Promise features: resolve/reject, then, finally, fetch etc. and appends some additional ones: suspend/resume, cancel, retry, timeout etc.
It also supports all, race, any e.g.:
// Fetch avatars of first 30 GitHub users.
struct User : Codable {
let login: String
let avatar_url: String
}
async {
let response = try fetch("https://api.github.com/users").await()
guard response.ok else {
throw response.statusCodeDescription
}
guard let data = response.data else {
throw "No data"
}
let users = try JSONDecoder().decode([User].self, from: data)
let images =
try Promise.all(
users
.map { $0.avatar_url }
.map { fetch($0) }
).await()
.compactMap { $0.data }
.compactMap { UIImage(data: $0) }
async(.main) {
print(images.count)
}
}
.catch { error in
print(error.localizedDescription)
}
Swift's concurrency such as Dispatch queues, Combine and the newest async\await (Swift 5.5) are different from javascript Promises and you can not find many convenient approaches that you used before.
I'm answering myself here with a solution, using PromiseKit, in case it might help someone.
The below is obviously not a full implementation, but it shows how the pattern can be implemented.
func doManyAsyncRequests(userIds: [String], accessToken: String) -> Promise<Void> {
Promise { seal in
let promises = spotifyUserIds.map {
doSingleAsyncRequest(userId: $0.id, accessToken: accessToken) // this function returns a promise
}
when(fulfilled: promises).done { results in
print("Results: \(results)")
// process results
}.catch { error in
print("\(error)")
// handle error
}
}
}

Unable to infer complex closure return type; add explicit type to disambiguate in RxSwift

I need to make multiple calls.
1. Delete Document Upload
2. Image 1 & server returns URL
3. Upload Image 2 & server returns URL
4. Create Document API contains both URLs & extra
parameters.
The code which I tried to write is in RxSwift,& MVVM.
let resultOfDocumentUpdateWithDelete =
donepressed
.filter{ $0 }
.withLatestFrom(self.existingDocumentIDChangedProperty)
.flatMapLatest {id in
let deleted_document = apiClient.deleteDocument(id).asObservable().materialize()
let upload_frontImage = deleted_document
.withLatestFrom(self.frontImageNameChangedProperty)
.flatMapLatest {image in
apiClient.uploadImage(image: image!).asObservable().materialize()
}
let upload_backImage = upload_frontImage
.withLatestFrom(self.backImageChangedProperty)
.flatMapLatest {image in
apiClient.uploadImage(image: image!).asObservable().materialize()
}
let upload_document = upload_backImage
.withLatestFrom(self.parametersChangedProperty)
.flatMapLatest {parameters in
apiClient.uploadDocument(parameters: parameters)
}
return upload_document.materialize()
}
.share(replay: 1)
Make sure, two responses of server are input in last API, so all of these will be called in a sequence.
how to do in RxSwift.
This was an interesting one! The take-away here is that when you are in doubt, go ahead and make your own operator. If it turns out that you later figure out how to do the job using the built-in operators, then you can replace yours. The only thing with making your own is that they require a lot more testing.
Note, to use the below, you will have to combineLatest of your observables and then flatMap and pass their values into this function.
// all possible results from this job.
enum ProcessResult {
case success
case deleteFailure(Error)
case imageFailue(Error)
case backImageFailure(Error)
case documentFailure(Error)
}
func uploadContent(apiClient: APIClient, documentID: Int, frontImage: UIImage, backImage: UIImage, parameters: Parameters) -> Single<ProcessResult> {
// instead of trying to deal with all the materializes, I decided to turn it into a single process.
return Single.create { observer in
// each api call happens in turn. Note that there are no roll-back semantics included! You are dealing with a very poorly written server.
let deleted = apiClient.deleteDocument(id: documentID)
.asObservable()
.share()
let imagesUploaded = deleted
.flatMap { _ in Observable.zip(apiClient.uploadImage(image: frontImage).asObservable(), apiClient.uploadImage(image: backImage).asObservable()) }
.share()
let documentUploaded = imagesUploaded
.flatMap { arg -> Single<Void> in
let (frontURL, backURL) = arg
var updatedParams = parameters
// add frontURL and backURL to parameters
return apiClient.uploadDocument(parameters: updatedParams)
}
.share()
let disposable = deleted
.subscribe(onError: { observer(.success(ProcessResult.deleteFailure($0))) })
let disposable1 = imagesUploaded
.subscribe(onError: { observer(.success(ProcessResult.imageFailue($0))) })
let disposable2 = documentUploaded
.subscribe(
onNext: { observer(.success(ProcessResult.success)) },
onError: { observer(.success(ProcessResult.documentFailure($0))) }
)
return Disposables.create([disposable, disposable1, disposable2])
}
}

RxSwift, Share + retry mechanism

I have a network request that can Succeed or Fail
I have encapsulated it in an observable.
I have 2 rules for the request
1) There can never be more then 1 request at the same time
-> there is a share operator i can use for this
2) When the request was Succeeded i don't want to repeat the same
request again and just return the latest value
-> I can use shareReplay(1) operator for this
The problem arises when the request fails, the shareReplay(1) will just replay the latest error and not restart the request again.
The request should start again at the next subscription.
Does anyone have an idea how i can turn this into a Observable chain?
// scenario 1
let obs: Observable<Int> = request().shareReplay(1)
// outputs a value
obs.subscribe()
// does not start a new request but outputs the same value as before
obs.subscribe()
// scenario 2 - in case of an error
let obs: Observable<Int> = request().shareReplay(1)
// outputs a error
obs.subscribe()
// does not start a new request but outputs the same value as before, but in this case i want it to start a new request
obs.subscribe()
This seems to be a exactly doing what i want, but it consists of keeping state outside the observable, anyone know how i can achieve this in a more Rx way?
enum Err: Swift.Error {
case x
}
enum Result<T> {
case value(val: T)
case error(err: Swift.Error)
}
func sample() {
var result: Result<Int>? = nil
var i = 0
let intSequence: Observable<Result<Int>> = Observable<Int>.create { observer in
if let result = result {
if case .value(let val) = result {
return Observable<Int>.just(val).subscribe(observer)
}
}
print("do work")
delay(1) {
if i == 0 {
observer.onError(Err.x)
} else {
observer.onNext(1)
observer.onCompleted()
}
i += 1
}
return Disposables.create {}
}
.map { value -> Result<Int> in Result.value(val: value) }
.catchError { error -> Observable<Result<Int>> in
return .just(.error(err: error))
}
.do(onNext: { result = $0 })
.share()
_ = intSequence
.debug()
.subscribe()
delay(2) {
_ = intSequence
.debug()
.subscribe()
_ = intSequence
.debug()
.subscribe()
}
delay(4) {
_ = intSequence
.debug()
.subscribe()
}
}
sample()
it only generates work when we don't have anything cached, but thing again we need to use side effects to achieve the desired output
As mentioned earlier, RxSwift errors need to be treated as fatal errors. They are errors your stream usually cannot recover from, and usually errors that would not even be user facing.
For that reason - a stream that emits an .error or .completed event, will immediately dispose and you won't receive any more events there.
There are two approaches to tackling this:
Using a Result type like you just did
Using .materialize() (and .dematerialize() if needed). These first operator will turn your Observable<Element> into a Observable<Event<Element>>, meaning instead of an error being emitted and the sequence terminated, you will get an element that tells you it was an error event, but without any termination.
You can read more about error handling in RxSwift in Adam Borek's great blog post about this: http://adamborek.com/how-to-handle-errors-in-rxswift/
If an Observable sequence emits an error, it can never emit another event. However, it is a fairly common practice to wrap an error-prone Observable inside of another Observable using flatMap and catch any errors before they are allowed to propagate through to the outer Observable. For example:
safeObservable
.flatMap {
Requestor
.makeUnsafeObservable()
.catchErrorJustReturn(0)
}
.shareReplay(1)
.subscribe()

Binding an RxSwift Observable to an array

Trying out RxSwift and trying to convert my network calls. I can't seem to display my data in the view because im not sure how I convert my observable to something my views can use. Here's an example of my request:
class SomeService {
let provider = Provider()
func getData() -> Observable<[Object]?> { // Returns json
return provider
.request(.getSomething())
.debug()
.mapArrayOptional(type: Object.self)
// Using Moya_Modelmapper map each item in the array
}
}
In my view controller I get the data:
let data = Service.getData()
print(data) ... <Swift.Optional<Swift.Array<MyApp.Item>>>
I have tried to subscribe to the response to the sequence but I don't know how I actually convert it to something like an array I can use in my view.
UPDATE: With answer implemented:
func itemsObserver() {
print("Time to print step 1") // This gets printed
data
.filter { $0 != nil }.map { $0! }
.subscribe(
onNext: { objects in
print(objects as Any)
print("Step 2") // This does not get executed at all
},
onCompleted:{ objects in
print(objects as Any) // This is ()
print("Complete") // This gets printed
}
).addDisposableTo(disposeBag)
}
itemsObserver()
Console output:
Time to print step 1
Service.swift:21 (getData()) -> subscribed
Service.swift:21 (getData()) -> Event next(Status Code: 200, Data Length: 141)
Service.swift:21 (getData()) -> Event completed
Service.swift:21 (getData()) -> isDisposed
()
Complete
Update:
If your onNext block isn't getting called at all, it's because data never produced anything. Either your producer isn't producing any objects or mapArrayOptional is not transforming them.
The onCompleted block doesn't accept any arguments so the objects var you have in it is meaningless/Void.
Try this:
let data = service.getData()
data
.filter { $0 != nil }.map { $0! } // this removes the optionality of the result.
.subscribe(onNext: { objects in
// in here `objects` will be an array of the objects that came through.
}).disposed(by: bag)

RxSwift Using Variables Correctly

I am trying to convert a project to use RxSwift and MVVM. I have a service that syncs a list of data from Parse on every app launch and I basically want to make sure I am taking the correct approach.
What I have done is made a Variable subject and then allow my models to listen to this.
ParseService:
let rx_parseMushrooms = Variable<[ParseMushroom]>([])
MushroomLibraryModel:
_ = parseService.rx_parseMushrooms
.asObservable()
.map { (parseMushrooms:[ParseMushroom]) -> [Mushroom] in
let mushrooms = parseMushrooms.map { (parseMushroom:ParseMushroom) -> Mushroom in
let mushroom = Mapper<Mushroom>().map(parseMushroom.dictionaryWithValuesForKeys(parseMushroom.allKeys()))
return mushroom!
}
return mushrooms
}
.subscribeNext({ (mushrooms:[Mushroom]) -> Void in
self.mushrooms = mushrooms
print(mushrooms)
})
I do the same for expressing the sync state.
ParseService:
struct SyncState {
enum State {
case Unsynced, ConnectingToServer, SyncingInfo, FetchingImageList, SyncingImages, SyncComplete, SyncCompleteWithError
}
var infoToSync = 0
var imagesToSync = 0
var imagesSynced = 0
var state = State.Unsynced
}
let rx_syncState = Variable(SyncState())
I then update the variable a la
self.rx_syncState.value = self.syncState
SyncViewModel:
_ = parseService.rx_syncState
.asObservable()
.subscribeNext { [weak self] (syncState:ParseService.SyncState) -> Void in
switch syncState.state {
//show stuff based on state struct
}
}
Anyways, I would greatly appreciate if someone can tell me if this is a good way of going about it or if I am misusing RxSwift (and guide me on how I should be doing this).
Cheers!
Hmm... Here is an article about using Variable (note that Variable is a wrapper around BehaviorSubject.)
http://davesexton.com/blog/post/To-Use-Subject-Or-Not-To-Use-Subject.aspx
In your case, you already have a cold observable (the network call,) so you don't need a Subject/Variable. All you need to do is publish the observable you already have and use replay(1) to cache the value. I would expect a class named something like ParseServer that contains a computed property named something like mushrooms.
To help get the mushrooms out of parse, you could use this (this will create the cold observable you need):
extension PFQuery {
var rx_findObjects: Observable<[PFObject]> {
return Observable.create { observer in
self.findObjectsInBackgroundWithBlock({ results, error in
if let results = results {
observer.on(.Next(results))
observer.on(.Completed)
}
else {
observer.on(.Error(error ?? RxError.Unknown))
}
})
return AnonymousDisposable({ self.cancel() })
}
}
}
And then you would have something like:
class ParseServer {
var mushrooms: Observable<[Mushroom]> {
return PFQuery(className: "Mushroom").rx_findObjects
.map { $0.map { Mushroom(pfObject: $0) } }
.publish()
.replay(1)
}
}
I think the above is correct. I didn't run it through a compiler though, much less test it. It might need editing.
The idea though is that the first time you call myParseServer.mushrooms the system will call Parse to get the mushrooms out and cache them. From then on, it will just return the previous cashed mushrooms.