PromiseKit flatMapError - ios

ReactiveSwift has this great function called flatMapError that allows you to respond with an event stream when an error occurs. A simple example might look like:
authenticationProducer.flatMapError { _ in self.reauthenticate() }
Whenever an error occurs, that error gets mapped into a producer that attempts to re-authenticate.
How would I build a similar operator using PromiseKit? The function signature would look like:
func flatMapError<U>(_ transform: #escaping (Error) -> Promise<U>) -> Promise<U>
My implementation so far:
func flatMapError<U>(_ transform: #escaping (Error) -> Promise<U>) -> Promise<U> {
return Promise<U> { resolve, reject in
self.catch { error in
let promise = transform(error)
let _ = promise.then { value in
resolve(value)
}
}
}
}

Use recover, it behaves as you request.
https://github.com/mxcl/PromiseKit/blob/master/Sources/Promise.swift#L254-L278

Related

Completion handler in function

I have a function that looks like this, and I have tried to add a completionHandler in the code below:
func getValueFromAPI(completionHandler: (_ result: Bool) -> Void){
apii.getVehicle(id!).done {
(vehicle: Vehicle) -> Void in
print("ggg.state: \(vehicle.state!)")
print("ggg.state: \(vehicle.displayName!)")
apii.getAllData(vehicle).done { (extendedVehicle: VehicleExtended) in
let entryBattery = (extendedVehicle.chargeState?.batteryLevel)!
let entryCarState = (extendedVehicle.state)!
print("entryBattery: \(entryBattery)")
print("entryCarState: \(entryCarState)")
completionHandler(true)
}.catch { (error) in
print("ERROOOOR: \(error)")
}
}.catch { error in
print("errorr: \(error)")
}
}
I have already tried to add a complete handler, but I get the following error on these lines:
Line: apii.getVehicle(id!).done {
Error: Escaping closure captures non-escaping parameter 'completionHandler'
Line: apii.getAllData(vehicle).done { (extendedVehicle: VehicleExtended) in
Error: Escaping closure captures non-escaping parameter 'completionHandler'
What am I doing wrong here, and how can I fix this?
I am using Swift 5.
You need to declare your completionHandler to be an escaping closure. E.g.:
func getValueFromAPI(completionHandler: #escaping (Bool) -> Void) {
...
}
Note the #escaping qualifier.

API funtion using RxSwift

I want to change the API request code written using the closure to RxSwift.
For example, I would like to make rxGetList() function using getList() function.
// This function cannot be modified.
func getList(success: #escaping ([String]) -> Void,
failure: #escaping (Error) -> Void) {
// Request to Server...
}
func rxGetList() -> Observable<String> {
// Using getList() function
// TODO
}
What code should I write in TODO section?
Please give me some advice.
The easiest way to meet your expectations is to use something like this:
func rxGetList() -> Observable<String> {
return Observable.create { observer in
getList(success: { result in
for everyString in result {
observer.onNext(everyString)
}
observer.onCompleted()
}, failure: { error in
observer.onError(error)
})
return Disposables.create() {
// specify any action to be performed on observable dispose (like cancel URL task)
}
}
}
Note that you have [String] specified as an input type of your success closure. If it's not a typo then above code fits. If you want one String instead, it's as simple as this:
func rxGetList() -> Observable<String> {
return Observable.create { observer in
getList(success: { result in
observer.onNext(result)
observer.onCompleted()
}, failure: { error in
observer.onError(error)
})
return Disposables.create() {
// specify any action to be performed on observable dispose (like cancel URL task)
}
}
}
Petr Grigorev's answer is the correct one, but if you want to have fun with some extreme function composition, here's a more advanced way to handle it:
let rxGetList = Observable.create(rx_(getList(success:failure:)))
.flatMap { Observable.from($0) }
func rx_<A>(_ fn: #escaping (#escaping (A) -> Void, #escaping (Error) -> Void) -> Void) -> (AnyObserver<A>) -> Disposable {
{
fn(singleObserve($0), $0.onError)
return Disposables.create()
}
}
func singleObserve<A>(_ observer: AnyObserver<A>) -> (A) -> Void {
{
observer.onNext($0)
observer.onCompleted()
}
}
I'm not sure about actually using the above, but if you have a lot of functions that you want to wrap, it may help reduce the amount of code you have to write.

Cannot convert value of type '(Void) -> ()' to expected argument type '() -> Void'

I am trying to implement the TradeIt ios SDK and I am not able to compile due to issues in the sdk.
let promises = self.getAllDisplayableLinkedBrokers().map {
linkedBroker in
return Promise<Void> { seal in
linkedBroker.authenticateIfNeeded(
onSuccess: seal.fulfill,
onSecurityQuestion: onSecurityQuestion,
onFailure: { tradeItErrorResult in
onFailure(tradeItErrorResult, linkedBroker)
seal.fulfill(())
}
)
}
}
on the line with onSuccess: seal.fulfill, there is an error: Cannot convert value of type '(Void) -> ()' to expected argument type '() -> Void'
The following is the definition for the authenticateIfNeeded method that details what it's expecting for onSuccess.
#objc public func authenticateIfNeeded(onSuccess: #escaping () -> Void, onSecurityQuestion: #escaping (TradeItSecurityQuestionResult,_ submitAnswer: #escaping (String) -> Void, _ onCancelSecurityQuestion: #escaping () -> Void) -> Void,
onFailure: #escaping (TradeItErrorResult) -> Void
) -> Void {
guard let error = self.error else {
onSuccess()
return
}
if error.requiresAuthentication() {
self.authenticate(
onSuccess: onSuccess,
onSecurityQuestion: onSecurityQuestion,
onFailure: onFailure
)
} else if error.requiresRelink() {
onFailure(error)
} else {
onSuccess()
}
}
I am developing a react native application and I need to create a react native module for ios for the TradeIt sdk. That is why I am not familiar with objective-c and have a steep learning curve ahead of me. Any help would be appreciated. Thank you!
could you try this:
return Promise<Void> { seal in
linkedBroker.authenticateIfNeeded(
onSuccess: seal.fulfill(()),
onSuccess: seal.fulfill,
This line says to call seal.fulfill() on success. However, fulfill requires a value of the type you promised. You promised a Void (which is just another name for ()), so you need to pass that.
onSuccess: { seal.fulfill(()) },
This syntax is a little unfortunate, which is why I generally avoid specializing on Void this way. I'm not very familiar with PromiseKit, but I would look and see if there's another tool that is designed for the "no useful return value" case than Promise<Void>. There may not be, but sometimes there's another name for it.
As a note, I don't see any Objective-C here. This is entirely Swift.

Firebase observer architecture

Okay, so I'm trying to build an iOS app that relies on Firebase (To work with its android version)
I started with creating a repository for each actor in my app and a general repository to manage them all
Each repository manages the observers of this actor. An example:
Inside the PagesRepository, this is a function that retrieves all the pages from Firebase and returns it inside a completionHandler:
//MARK: Gets the whole pages list
func getPagesList(completionHandler: #escaping (_ pages: [Page]?, _ error: NSError?) -> Void) {
func displayError(error: String) {
print(error)
completionHandler(nil, self.getErrorFromString(error))
}
pagesRef.observe(DataEventType.value) { pagesSnapshot in
guard pagesSnapshot.exists() else {
displayError(error: "Pages snapshot doesn't exist")
return
}
var pagesList = [Page]()
for pageSnapshot in pagesSnapshot.children {
pagesList.append(Page(snapshot: pageSnapshot as! DataSnapshot))
}
completionHandler(pagesList, nil)
}
}
And then I call it from the ViewController like this:
repository.getPagesList { (pages, error) in
guard error == nil else {
return
}
//Do processing
}
I know this may be a lot to take in, but my problem is that every time I call the function, it creates a new observer but doesn't cancel the old one... So, the completionHandler is called multiple times with different values
How should I manage this problem?
(Sorry for being complicated and a little unclear, I'm just really lost)
It seems like you only want to observe the value once so I would use this instead:
func getPagesList(completionHandler: #escaping (_ pages: [Page]?, _ error: NSError?) -> Void) {
func displayError(error: String) {
print(error)
completionHandler(nil, self.getErrorFromString(error))
}
pagesRef.observeSingleEvent(of: .value, with: { (pagesSnapshot) in
var pagesList = [Page]()
for pageSnapshot in pagesSnapshot.children {
pagesList.append(Page(snapshot: pageSnapshot as! DataSnapshot))
}
completionHandler(pagesList, nil)
}) { (error) in
// Display error
}
}

How to use typealias when getting data from server

I am trying to get user data from a server. The application does not have to show any views until the data is loaded.
I read about typealias and I don't understand how to use it.
What I want: when data is loaded, move on to next step. If failed, load data again.
Here's how I declare typealias
typealias onCompleted = () -> ()
typealias onFailed = () -> ()
Here is my request code
func getUserData(_ completed: #escaping onCompleted, failed: #escaping onFailed){
let fullURL = AFUtils.getFullURL(AUTHURL.getUserData)
AFNetworking.requestGETURL(fullURL, params: nil, success: {
(JSONResponse) -> Void in
if let status = JSONResponse["status"].string {
switch status{
case Status.ok:
completed()
break
default:
failed()
break
}
}
})
}
But how could I use this on my view controller when calling getUserData?
Assuming your custom AFNetworking.requestGETURLs completion handler is called on the main queue:
func viewDidLoad() {
super.viewDidLoad()
getUserData({
//do somthing and update ui
}) {
//handle error
}
}
Edit:
How I understand your comment, you actually want to name your completion and error block parameters. If so, change the method to :
func getUserData(completion completed: #escaping onCompleted, error failed: #escaping onFailed){ ... }
and call it like this:
getUserData(completion: {
//do somthing and update ui
}, error: {
//handle error
})

Resources