I have following code in Swift 4 with RxSwift
worthReacting.flatMap{ (userSearch) in
translator.getTranslation(ofWord: userSearch)
}.subscribe(
onSuccess: {(dataModel) in
state.value = .translation(word: dataModel.definition,
translations: dataModel.translations)
},
onError: {(error) in
state.value = .networkError
},
onCompleted: {
state.value = .unknownWord
}).disposed(by: disposeBag)
worthReacting has type of Observable<String>
translator.getTranslation returns Maybe<DataModel>
I'm getting build error
Extra argument 'onError' in call
Maybe flatmaped into Observable produces Observable. Observable can not emit onSuccess event, instead it will emit onNext. Following code will work:
worthReacting.flatMap{ (userSearch) in
translator.getTranslation(ofWord: userSearch)
}.subscribe(
onNext: {(dataModel) in
self.state.value = .translation(word: dataModel.definition,
translations: dataModel.translations)
},
onError: {(error) in
self.state.value = .networkError
},
onCompleted: {
self.state.value = .unknownWord
}).disposed(by: disposeBag)
For those who get the OP's error, but have a different cause, check that you have not incidentally make one of your closures throwing by not making your do-catch clause exhaustive.
Also, take care that you don't use a single-statement closure (in this case you should explicitly return).
Related
Im moving my project to Combine from RxSwift
I have a logic where I want publisher to emit event every time I click button. Acrually clicking button executed pushMe.send()
pushMe
.print("Debug")
.flatMap { (res) -> AnyPublisher<Bool, Error> in
return Future<Bool, Error>.init { closure in
closure(.failure(Errors.validationFail))
}.eraseToAnyPublisher()
}
.sink(receiveCompletion: { completion in
print("Completion received")
}, receiveValue: { value in
print("Value = \(value)")
})
.store(in: &subscriptions)
The console result
Debug: receive value: (true)
Completion received
Debug: receive value: (true)
Debug: receive value: (true)
I do not understand why sink receive error only on first event. The rest clicks are ignored.
What does flatMap do -
Subscribes to the given publisher (let's say XPublisher).
Sends the Errors and Output values (not finished event/ completion) emitted
by XPublisher to the down stream.
So If you handle errors inside the flat map , (which means the publisher inside the flatMap does not emit errors), then flatMap Never sends an error to the down stream.
pushMe
.print("Debug")
.flatMap { (res) -> AnyPublisher<Bool, Never> in //<= here
return Future<Bool, Error>.init { closure in
closure(.failure(Errors.validationFail))
}
.replaceError(with: false) //<= here
.eraseToAnyPublisher()
}
.sink(receiveCompletion: { completion in
print("Completion received")
}, receiveValue: { value in
print("Value = \(value)")
})
.store(in: &subscriptions)
Otherwise you can handle error outside the fatMap. Problem here is that, once an error out whole the subscription / cancellable cancelled. ( in the below example error has replace with a false value)
pushMe
.print("Debug")
.flatMap { (res) -> AnyPublisher<Bool, Error> in
return Future<Bool, Error>.init { closure in
closure(.failure(Errors.validationFail))
}
.eraseToAnyPublisher()
}
.replaceError(with: false) //<= here
.sink(receiveCompletion: { completion in
print("Completion received")
}, receiveValue: { value in
print("Value = \(value)")
})
.store(in: &subscriptions)
What is happening in the above code.
FlatMap error outs.
replace the error with false (One false value will receive Because of this)
subscription cancelled because of the error out in the stream.
The rule is that if an error propagates down the pipeline, the entire pipeline is cancelled. Thus, if your Future generates an error, it passes as an error to the Sink and thus the pipeline is cancelled all the way up to the Publisher.
The pattern for preventing this is to deal with the error inside the FlatMap. Basically, you've got two pipelines here: the one that starts with pushMe and the one that starts with Future. Simply don't let the error generated by the Future pipeline "leak" out into the pushMe pipeline, and so the pushMe pipeline will not be cancelled. Instead, catch the error inside the FlatMap and, if you want to pass something out of it to your Sink, pass out of it some sort of value that tells your Sink that there has been a bad input.
A simple solution in your case would be to change the type your FlatMap to <Bool,Never>, and pass either true or false as the Bool to indicate whether validation succeeded in the Future or not.
Or, if it's important to you to pass more detailed information about the error down the pipeline, change the type of your FlatMap to <Result<Bool,Error>,Never> and package the error information into the .failure case of the Result object.
This is how Publishers work in Combine.
The Publisher can either emit values or emit a completion event - once a completion event was emitted, the Publisher is finished and it cannot emit any other values or another completion event. If the Publisher emits an error, the error is emitted as a failure completion, meaning that once an error is emitted, the Publisher completes and it cannot emit any more values.
There are several Combine operators designed for handling errors without completing the Publisher. Have a look into the catch operator for instance.
First, thanks all for helping with this question.
Answer of #matt is one of the possible solution.
Another solution is to create new pipeline every time you clicking button.
Im using this approach because I have sequence of steps below failing publisher and Im not able to rely of dummy true/false result further.
Just<String>()
.sink(receiveValue: { value in
startProcess()
.sink(receiveCompletion: { (completion:
Subscribers.Completion<Failure>) in
// can handle ALL types of error of pipe line in ONE place
}, receiveValue: { (v: P.Output) in
// handle correct result
})
})
.store(in: &subscriptions)
func startProcess() -> AnyPublisher<Bool, Error> {
Future<Bool, Error>.init { closure in
// action 1
closure(.success(true))
}
.flatMap { (b: Bool) -> AnyPubilsher<Void, Error> in
Future<Bool, Error>.init { closure in
// action 2
closure(.success(()))
}
}
}
Benefit is that you are able to handle all types of errors in one place if second sink()
I'm getting a very weird error using RXSwift. I'm calling a service that is a Single trait and when I just print the error my code compile without problem, but if I try to do something else in "onError", the code doens't compile. Someone had the same problem?
The piece of code that doesn't compile:
NetworkManager.shared.authorizeService(with: parameters)
.subscribe(onSuccess: { [weak self] status in
}, onError: { [weak self] error in
paymentAuthorizationFinishedWithError.onNext(APIResponseError.paymentAlreadyInProgress)
}).disposed(by: bag)
The code that compiles:
NetworkManager.shared.authorizeService(with: parameters)
.subscribe(onSuccess: { [weak self] status in
}, onError: { [weak self] error in
print(error)
}).disposed(by: bag)
Swift complier sometimes is very laggy. Especially when it comes to Rx. The problem is with this line
self.paymentAuthorizationFinishedWithError.onNext(APIResponseError.paymentAlreadyInProgress)
you forgot to add ? since you're using weak reference:
self?.paymentAuthorizationFinishedWithError.onNext(APIResponseError.paymentAlreadyInProgress)
So the code below compiles with the errror
var doneSubscription: Disposable = item.doneSubjectObservable
.debug("doneSubscriptions")
.subscribe(
onNext: {
done in self.validateDone(done: done, item: item)
}).disposed(by: disposeBag)
Value of type '()' does not conform to specified type 'Disposable'
on the line .disposed(by: disposeBag)
But I can do this without error:
var doneSubscription: Disposable = item.doneSubjectObservable
.debug("doneSubscriptions")
.subscribe(
onNext: {
done in self.validateDone(done: done, item: item)
})
doneSubscription.disposed(by: disposeBag)
All I've done is moved .disposed(by: disposeBag) out of the subscription chain.
Am I missing something, aren't these two approaches equivalent?
No, they are not equivalent.
In the first case, you are storing the return value of this whole expression into doneSubscription, a variable of type Disposable:
item.doneSubjectObservable
.debug("doneSubscriptions")
.subscribe(
onNext: {
done in self.validateDone(done: done, item: item)
}).disposed(by: disposeBag)
Since disposed(by:) does not return anything, this gives you an error.
In the second case however, you actually assigned a Disposable to the variable doneSubscription - the return value of subscribe.
To fix your first case, simply remove the variable declaration.
My goal is to have an array of functions that execute one after the other.
static func GetData(success:(() ->())?, failure:((response : NSURLResponse, error : NSError) ->())?){
//do something asynchronous. make web service call
success?()
//or
failure?(response: urlResponse, error: errorThrown)
}
And then in a view controller, I'm trying to store functions similar to this one above in an array. My goal is to iterate through this array and execute the functions one after the other. The reason for this is because each function is dependent on the data that gets returned from the previous function.
So I made an enum
enum MyEnum {
case GetData()
case GetData2()...and so on
//and every other function is put here
}
Now in my view controller, I add this to an array
funcs.append(MyEnum.GetData(DataAccess.GetData({
//do stuff
}, failure: { (response, error) in
})))
funcs.append(MyEnum.GetData2(DataAccess.GetData2({
//do stuff
}, failure: { (response, error) in
})))
However, whenever I store a function, it automatically executes. I think I could do it if there weren't any closures but I need them to determine how to proceed.
Is there a better way of doing this? Am I missing something?
Storing an array of functions with closures automatically executes them
var arrayOfFuncs = [
DataAccess.GetData1({
print("success")
}, failure: { (response, error) in
print("failure")
}),
DataAccess.GetData2({
print("ok")
}, failure: { (response, error) in
print("ee")
})
]
GetData takes 2 closures as parameters, it's not two closures. I don't get what you are doing with the enum.
typealias Block = () -> Void
typealias Error = (NSURLResponse,NSError) -> Void
var funcs = [(Block,Error)]
func.forEach{ f => f._0() } // call success()
// Or you could do this
typealias Getter = (Block,Error) -> Void
var funcs2 = [Getter]
funcs2.append(DataAccess.GetData)
I need to translate such a func from Objective-C to Swift language. But can't find an example and can't get how to send 2 closures into func in Swift.
For example, original function in Objective-C:
- (void)getForDemoDataWithToken:(Token *)token
onSuccess:(void(^)(NSArray *demoData))success
onFailure:(void(^)(NSError *error))failure {
}
I know to send 1 closure as param:
getForDemoDataWithToken(token) {(success: String) -> Void in
// some code here
print(success)
}
But, how to send two closures?
Thank you
What about this?
Declaration
func getForDemoDataWithToken(
token: Token,
onSuccess: (demoData:NSArray?) -> (),
onFailure: (error:NSError?) -> ()) {
}
Invocation
getForDemoDataWithToken(Token(),
onSuccess: { (demoData) -> () in
},
onFailure: { (demoData) -> () in
}
)
A more Swifty approach
I usually see Swift code where only one closure is used. So instead of 2 distinct onSuccess and onFailure closures you could have just completion.
Next we should remember that NSArray is compatible with Swift but it's not the Swiftest way to use an array.
Let's see an example where the 2 concepts above are applied.
func getForDemoData(token:Token, completion:(data:[Foo]?, error:NSError?) -> ()) {
}
You can invoke it with the trailing closure syntax.
getForDemoData(Token()) { (data, error) -> () in
if let data = data where error == nil {
// success!
} else {
// oh no... something wrong here
}
}
You should pass the closures as normal parameters, like this:
func acceptsTwoClosures(
onSuccess onSuccess: (success: String) -> Void,
onFailure: (failure: String) -> Void) {
onSuccess(success: "Ook")
onFailure(failure: "Eek")
}
acceptsTwoClosures(
onSuccess: { print("Success: \($0)") },
onFailure: { print("Failure: \($0)") }
)
// In the playground the above prints:
//
// Success: Ook
// Failure: Eek
The way that you used in the question is called trailing closure, and it only works for the closures that are the last arguments in the function signature.
From the documentation:
If you need to pass a closure expression to a function as the function’s final argument and the closure expression is long, it can be useful to write it as a trailing closure instead. A trailing closure is a closure expression that is written outside of (and after) the parentheses of the function call it supports.
For example, you could also re-write my suggested snippet from above like this:
acceptsTwoClosures(onSuccess: { print("Success: \($0)") }) {
print("Failure: \($0)")
}
.. as you can see, I can pass the second (i.e. the last) closure outside of acceptsTwoClosures call as a trailing closure, but I still have to pass the first one as a normal parameter.