I am trying use Combine in my Swift application and have problem in my following code:
//Get it from local storage(realm)
voucherCodeStorageProvider.fetchVoucherCode(voucherId).flatMap { (code) -> AnyPublisher<String?, Error> in
if let code = code {
return Just(code).setFailureType(to: Error.self).eraseToAnyPublisher()
}
//If not found in storage, Get it from api
return self.voucherCodeProvider.fetchVoucherCode(voucherId: voucherId).handleEvents( receiveOutput: { code in
guard let code = code else { return }
_ = self.voucherCodeStorageProvider.saveVoucherCode(code, voucherId)
}).mapError{ $0 as Error }.eraseToAnyPublisher()
}.eraseToAnyPublisher()
Above fetchVoucherCode is currently publishing an error, now I want to catch that error and do task that I perform after nil check in my code. But I am not able to catch error here. How can I catch an error in flatmap and can perform some operation like I have above?
I did it using catch before flatmap. Below is my working code:
voucherCodeStorageProvider.fetchVoucherCode(voucherId).catch { _ in
return self.voucherCodeProvider.fetchVoucherCode(voucherId: voucherId).handleEvents( receiveOutput: { code in
guard let code = code else { return }
_ = self.voucherCodeStorageProvider.saveVoucherCode(code, voucherId)
}).mapError{ $0 as Error }.eraseToAnyPublisher()
}.flatMap { (code) -> AnyPublisher<String?, Error> in
return Just(code).setFailureType(to: Error.self).eraseToAnyPublisher()
}.eraseToAnyPublisher()
Related
I am trying to pass the value of gyroX to another function but it just ends up in it having a value of 0 when I use it as gyroX in that other function.
Here is the code:
var gyroX = Float()
motion.startGyroUpdates(to: .main) { (data, error) in
if let myData = data {
gyroX = Float(myData.rotationRate.x)
}
}
With Xcode 13 Beta and Swift 5.5
This is a problem that we can now solve with Async/Await's Continuations
We would first make a function that converts the callback into an awaitable result like:
func getXRotation(from motion: CMMotionManager) async throws -> Float {
try await withCheckedThrowingContinuation { continuation in
class GyroUpdateFailure: Error {} // make error to throw
motion.startGyroUpdates(to: .main) { (data, error) in
if let myData = data {
continuation.resume(returning: Float(myData.rotationRate.x))
} else {
throw GyroUpdateFailure()
}
}
}
}
Then we can assign the variable and use it like so:
let gyroX = try await getXRotation(from: motion)
callSomeOtherFunction(with: gyroX)
With Xcode <= 12 and Combine
In the current release of Swift and Xcode we can use the Combine framework to make callback handling a little easier for us. First we'll convert the closure from the motion manager into a "Future". Then we can use that future in a combine chain.
func getXRotation(from motion: CMMotionManager) -> Future<CMGyroData, Error> {
Future { promise in
class GyroUpdateFailure: Error {} // make error to throw
motion.startGyroUpdates(to: .main) { (data, error) in
if let myData = data {
promise(.success(myData))
} else {
promise(.failure(GyroUpdateFailure()))
}
}
}
}
// This is the other function you want to call
func someOtherFunction(_ x: Float) {}
// Then we can use it like so
_ = getXRotation(from: motion)
.eraseToAnyPublisher()
.map { Float($0.rotationRate.x) }
.map(someOtherFunction)
.sink { completion in
switch completion {
case .failure(let error):
print(error.localizedDescription)
default: break
}
} receiveValue: {
print($0)
}
There are some important parts to the combine flow. The _ = is one of them. The result of "sinking" on a publisher is a "cancellable" object. If we don't store that in a local variable the system can clean up the task before it fishes executing. So you will want to do that for sure.
I highly recommend you checkout SwiftBySundell.com to learn more about Combine or Async/Await and RayWenderlich.com for mobile development in general.
I am trying to chain some API calls and I think I am confusing some concepts. Would love some clarification & code samples.
I have implemented these functions...
func promiseFetchPayments(for accountId: String) -> Promise <[OperationResponse]> {
return Promise <[OperationResponse]> { seal in
payments(for: accountId) { (records, error) in
if let recs = records {
seal.resolve(.fulfilled(recs))
return
}
if let e = error {
seal.reject(e)
return
}
}
}
}
and
func payments(for accountId: String, completion: #escaping (_ records: [OperationResponse]?, _ error: Error?) -> Void) {
stellar.payments.getPayments(
forAccount: accountId,
order: Order.descending,
limit: 10
) { response in
switch response {
case .success(let paymentsResponse):
DispatchQueue.main.async {
completion(paymentsResponse.records, nil)
}
case .failure(let error):
DispatchQueue.main.async {
completion(nil, error)
}
}
}
}
I am trying to use it like so:
firstly {
promiseFetchPayments(for: "XXX")
}.done { records in
print(records)
} .catch { error in
print(error)
}
Now this actually ^^^ works OK!!!! My problem is I want to be able to change done to then and be able to chain another function / response or many more.
But the error I keep getting is:
Cannot conform to Thenable.
I am looking for something very similar to this (I know the syntax isn't right just logically follow the chain....
firstly {
stellar.promiseFetchPayments(for: "")
}.done { records in
print(records)
}.then {
// call some other method
}.done { data in
// more data
}.catch { error in
print(error)
}
Is this actually possible? Can't seem to get any tutorials on the interwebs to compile. Seems Swift compiler really doesn't like PMK syntax or something.
Any ideas?
The problem is because you're chaining off of a done, which doesn't like that you're trying to then do a call to then off of that.
Instead, you'll want to save the promise and use it for the later calls. You can do something like this:
let promise = firstly {
stellar.promiseFetchPayments(for: "")
}
promise.done { records in
print(records)
}
promise.then {
// call some other method
}.done { data in
// more data
}.catch { error in
print(error)
}
You can even return that promise from a method to use in other places, or pass it around to another method.
I have this code:
...
let minMaxReq = networkProvider.getMinMaxAmortization(id: id)
let emitExtractReq = networkProvider.getEmitExtract(id: id)
self.isLoading.accept(true)
Observable.zip(minMaxReq.asObservable(), emitExtractReq.asObservable()) { (minMaxResp, emitExtractResp) in
return (minMaxResp, emitExtractResp)
}.subscribe(onNext: { [weak self] responses in
let minMaxResp = responses.0
let emitExtractResp = responses.1
guard let self = self else { return }
self.isLoading.accept(false)
self.getMinMaxAmortizationResponse.accept(minMaxResp)
self.receiptsCNH.accept(emitExtractResp)
}, onError: { [weak self] error in
self?.isLoading.accept(false)
self?.receivedError.accept(error)
}).disposed(by: disposeBag)
In this case all errors from both requests will end up in the onError closure, how can I handle the error from minMaxReq in a different onError closure?
My goal is to make the 2 requests at the same time but handle their error with different closures. How can I achieve this?
thanks
Have you tried the materialize operator? Maybe it can be useful for the implementation you need. Annex an example of how it is usually used:
let valid = network.getToken(apikey)
.flatMap{ token in self.verifyToken(token).materialize()}
.share()
valid
.compactMap { $0.element }
.subscribe(onNext: { data in print("token is valid:", data) })
.disposed(by: disposeBag)
valid
.compactMap { $0.error?.localizedDescription }
.subscribe(onNext: { data in print("token is not valid:", data) })
.disposed(by: disposeBag)
In that way, you could divide the stream into two and give it the appropriate treatment.
Another option might be to manipulate the error event in the minMaxReq operation. Something similar to:
let minMaxReq = networkProvider.getMinMaxAmortization(id: id)
.catchError({ error in Observable.empty()
.do(onCompleted: { /* Do anything with no side effect*/ }) })
let emitExtractReq = networkProvider.getEmitExtract(id: id)
Observable.zip(...)
Here is an article that explains more detail Handling Errors in RxSwift
I found this solution helpful for me RxSwift zip operator when one observable can fail
Need to catch error before add to zip
let request1 = usecase.request1().asObservable()
let request2 = usecase.request2().catchErrorJustReturn([]).asObservable() // fetching some not so important data which are just good to have.Return empty array on error
Observable.zip(request1,request2)..observeOn(MainScheduler.instance)
.subscribe(
onNext: { //success code },
onError: { _ in //error code}).disposeBy(bag:myDisposeBag)
But I changed a bit the final:
let request2 = usecase.request2().catchError { _ in
return .just([])
}
I am finding extremely hard to use PromiseKit 6.13.1 in an apparently simple situation.
I have the following two functions returning a Promise<String> but I cannot seem to find a way to use them with ```firstly{}.then{} syntax:
func promiseGetJWTToken() -> Promise<String> {
return Promise<String> { seal in
let error: Error = NSError(domain: "", code: 2000)
getJWTToken { tokenJWT in
guard let tokenJWT = tokenJWT else {
seal.resolve(.rejected(error))
return
}
seal.resolve(.fulfilled(tokenJWT))
}
}
}
func promiseGetBEToken() -> Promise<String> {
return Promise<String> { seal in
let error: Error = NSError(domain: "", code: 2000)
getBEToken { result in
switch result {
case .success(let response):
guard let tokenBE = response.token else {
seal.resolve(.rejected(error))
return
}
seal.fulfill(tokenBE)
case .failure(let error):
debugPrint(error)
seal.resolve(.rejected(error))
case .none:
seal.resolve(.rejected(error))
}
}
}
}
When I try to use the following as follows
firstly {
promiseGetJWTToken()
}.then { tokenJWT in
// no need go on because I have issues already here
}
I receive:
Type '()' cannot conform to 'Thenable'; only struct/enum/class types can conform to protocols
I have also tried, which comes from autocompletion:
promiseGetJWTToken().then { token -> Thenable in
// no need go on because I have issues already here
}
In this case I receive:
Protocol 'Thenable' can only be used as a generic constraint because it has Self or associated type requirements
I decided to give PromiseKit a try because I have three network calls dependent on each other on cascade, but I wouldn't expect this to be so hard. Can anyone show me what am I doing wrong?
The error message is misleading; the real issue is that the .then closure should return a new Thenable. In your examples, the .then closures are empty.
Or just use .done, if not chaining promises.
They replaced that usage of then { } with done { }.
firstly {
promiseGetJWTToken()
}.done { tokenJWT in
// use your token
}
You can try
firstly {
promiseGetJWTToken()
}.then { tokenJWT -> Promise<String> in
return promiseGetBEToken()
}
or
firstly {
promiseGetJWTToken()
}.then { tokenJWT -> Promise<String> in
promiseGetBEToken()
return Promise.value(tokenJWT)
}
How to convert:
func getResults(completion: ([Result]?, Error) -> Void)
Into
var resultsPublisher: AnyPublisher<[Result], Error>
Just a scheme how I see it is (this syntax doesn't exist):
var resultsPublisher: AnyPublisher<[Result], Error> {
let publisher: AnyPublisher = ... // init
getResults { results, error in
guard let results = results else {
publisher.produce(error: error) // this syntax doesn't exist
return
}
publisher.produce(results: results) // this syntax doesn't exist
}
return publisher
}
I need that because some 3d party SDKs use completion closures and I want to write extensions to them that return Publishers.
The answer is Future Publisher as matt explained:
var resultsPublisher: AnyPublisher<[Result], Error> {
// need deferred when want
// to start request only when somebody subscribes
// + without Deferred it's impossible to .retry() later
Deferred {
Future { promise in
self.getResults { results, error in
guard let results = results else {
promise(.failure(error))
return
}
promise(.success(results))
}
}
}
.eraseToAnyPublisher()
}