Break for loop inside a web request (in swift) - swift

Inside my for loop i run a web request. Now if theres an error in the web request i want to break the for loop, however i get this error:
break is only allowed inside a loop or switch I've also tried naming my for loop like I've seen such as myLoopName : for(...) then calling it with break myLoopName but then i get the same error.
Here is an example of how my code looks currently
myLoopName : for(...) {
...
SRWebClient.POST(someUrlString)
.data(...)
.send({(response:AnyObject!, status:Int) -> Void in
//process success response
},failure:{(error:NSError!) -> Void in
//process failure response
println(error)
//where break must occur
})
}
How do i break the loop inside a web request?

I would do it using a function when dealing with closures and breaks:
func loop() {
SRWebClient.POST(someUrlString).data().send( { response, status in
loop()
},
failure:{ (error:NSError!) -> Void in
println(error)
}
}

I used return instead of break and it worked perfectly
func loop() {
SRWebClient.POST(someUrlString).data().send( { response, status in
loop()
},
failure:{ (error:NSError!) -> Void in
println(error)
return
}
}

Related

ReactiveSwift pipeline flatMap body transform not executed

I have the following pipeline setup, and for some reason I can't understand, the second flatMap is skipped:
func letsDoThis() -> SignalProducer<(), MyError> {
let logError: (MyError) -> Void = { error in
print("Error: \(error); \((error as NSError).userInfo)")
}
return upload(uploads) // returns: SignalProducer<Signal<(), MyError>.Event, Never>
.collect() // SignalProducer<[Signal<(), MyError>.Event], Never>
.flatMap(.merge, { [uploadContext] values -> SignalProducer<[Signal<(), MyError>.Event], MyError> in
return context.saveSignal() // SignalProducer<(), NSError>
.map { values } // SignalProducer<[Signal<(), MyError>.Event], NSError>
.mapError { MyError.saveFailed(error: $0) } // SignalProducer<[Signal<(), MyError>.Event], MyError>
})
.flatMap(.merge, { values -> SignalProducer<(), MyError> in
if let error = values.first(where: { $0.error != nil })?.error {
return SignalProducer(error: error)
} else {
return SignalProducer(value: ())
}
})
.on(failed: logError)
}
See the transformations/signatures starting with the upload method.
When I say skipped I mean even if I add breakpoints or log statements, they are not executed.
Any idea how to debug this or how to fix?
Thanks.
EDIT: it is most likely has something to do with the map withing the first flatMap, but not sure how to fix it yet.
See this link.
EDIT 2: versions
- ReactiveCocoa (10.1.0):
- ReactiveObjC (3.1.1)
- ReactiveObjCBridge (6.0.0):
- ReactiveSwift (6.1.0)
EDIT 3: I found the problem which was due to my method saveSignal sending sendCompleted.
extension NSManagedObjectContext {
func saveSignal() -> SignalProducer<(), NSError> {
return SignalProducer { observer, disposable in
self.perform {
do {
try self.save()
observer.sendCompleted()
}
catch {
observer.send(error: error as NSError)
}
}
}
}
Sending completed make sense, so I can't change that. Any way to change the flatMap to still do what I intended to do?
I think the reason your second flatMap is never executed is that saveSignal never sends a value; it just finishes with a completed event or an error event. That means map will never be called, and no values will ever be passed to your second flatMap. You can fix it by doing something like this:
context.saveSignal()
.mapError { MyError.saveFailed(error: $0) }
.then(SignalProducer(value: values))
Instead of using map (which does nothing because there are no values to map), you just create a new producer that sends the values after saveSignal completes successfully.

Chained Throwing Futures in SwiftNIO & Vapor

In Vapor 4, I'm processing a post request by calling a request on a 3rd party API and returning a value based on the result I get back. The following code results in the error: "Invalid conversion from throwing function ... to non-throwing function"
app.post("activate") { req -> EventLoopFuture<ActivationRequestResponse> in
return req.client.post("https://api.example.com/activation", headers: HTTPHeaders(), beforeSend: { (req) in
try req.content.encode(RequestBody(value: someValue), as: .json)
})
.map { (response) -> ActivationRequestResponse in
let response = try response.content.decode(ResponseModel.self)
return ActivationRequestResponse(success: true, message: "success")
}
}
I can't seem to use try in my chained map() after getting the API result. The above code will work if I add a ! to the try in let response = try response.content.decode(ResponseModel.self) inside the map, but ideally I want to catch this error. The first try used when creating the response body seems to be implicitly passed back up the chain, but not the second.
What am I doing wrong? How do I catch the error when decoding the response content? Why is the first try caught but not the second?
The property of map is that it will just transform a value on the “success path”. Your transformation may however fail which means that you presumably want the future to fail too.
Whenever you want to transform a value with a function that either succeeds or fails you need to use one of the flatMap* functions.
In your case, try replacing map with flatMapThrowing and then it should work.
To expand on Johannes Weiss' answer, to have a throwing closure that returns a future, you need something like:
future.flatMap {
do {
return try liveDangerously()
} catch {
future.eventLoop.makeFailedFuture(error)
}
}
After doing this too many times, I decided to roll my own (though the name is a bit dubious):
extension EventLoopFuture {
#inlinable
public func flatterMapThrowing<NewValue>(file: StaticString = #file,
line: UInt = #line,
_ callback: #escaping (Value) throws -> EventLoopFuture<NewValue>) -> EventLoopFuture<NewValue> {
return self.flatMap(file: file, line: line) { (value: Value) -> EventLoopFuture<NewValue> in
do {
return try callback(value)
} catch {
return self.eventLoop.makeFailedFuture(error)
}
}
}
}
That way you can just write:
future.flatterMapThrowing {
return try liveDangerously()
}

RxSwift catch networking and reachability errors

I try to use retryOnBecomesReachable method from the RX example files in my networking layer
extension ObservableConvertibleType {
func retryOnBecomesReachable(_ valueOnFailure:E, reachabilityService: ReachabilityService?) -> Observable<E> {
return self.asObservable()
.catchError { (e) -> Observable<E> in
return reachabilityService.reachability
.skip(1)
.filter { $0.reachable }
.flatMap({ _ -> Observable<E> in
return Observable.error(e)
})
.startWith(valueOnFailure)
}
.retry()
}
}
// My layer
request
.flatMapLatest{ request in
provider.rx.request(request)
.map{ User.self }
.map{ RequestState.loaded($0) }
.retryOnBecomesReachable(.error(.notConnectedToInternet), reachabilityService: reachabilityService)
.catchError({ .just(.error($0)) })
.startWith(.startLoading)
}
Without this method, all works awesome. All error catching and returning .just(.error($0)) sequence.
With this method, the retry feature works awesome. But when something happens (mapping, decoding or other error) I get .notConnectedToInternet. I think the reason in .startWith(valueOnFailure) method. I tried to move, remove, change position but nothing helps. I'm stuck.
What should I do to use retry feature and catch errors correct?
I think that basically changing .startWith(valueOnFailure) to startWith(e) might work for you. Another option is to check if the error is a reachability error to begin with inside the catch block.
e.g.
.catchError { e in
guard e == SomeError.notConnectedToInternet else {
return .error(e)
}
... rest of your code

How to redirect all 404 errors

How would I catch all 404 errors and redirect to /app/index.html (I'm trying to satisfy Angular's need to forward all not found resources to index.html) ? I thought implementing my own middleware would work, but not sure I'm doing it right
public final class ForwardToAngularMiddleware: Middleware {
public func respond(to req: Request, chainingTo next: Responder) throws -> EventLoopFuture<Response> {
do {
let response: Future<Response> = try next.respond(to: req)
return response
} catch let error as AbortError {
if error.status == .notFound && req.http.url.path.starts(with: "/app") {
return Future.map(on: req) { req.redirect(to: "/index.html") }
}
throw error
}
}
}
My program never hits the catch block no matter what URL I send. I am configuring my middleware like this:
middlewares.use(FileMiddleware.self)
middlewares.use(ForwardToAngularMiddleware())
middlewares.use(ErrorMiddleware.self)
middlewares.use(SessionsMiddleware.self)
services.register(middlewares)
You may be hitting a couple of issues here. First, the abort error could be being thrown in a future, in which case you need to add a catchMap to the next.respond(to:) call to catch that case.
It also may not throw (though this is unlikely), so you can try unwrapping the response and checking the status code.
Have you put a breakpoint in to see if it ever hits it etc?

Swift: if a do return try fails does catch execute

I am working on a piece of code that is going to fetch an array of NSManagedObjects from CoreData. When using a do catch statement in my code it doesn't seem right to do it this way, but it is the simplest way I can write this line of code.
In any other scenario when you use the return statement you are jumping out of the current function you are in. And you can be assured that no other code in your function will execute past the return statement. I am wondering if the same applies to Swift's do catch paradigm.
class func getAll() -> [MMNotification] {
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<MMNotification>(entityName: "MMNotification")
do {
return try context.fetch(fetchRequest)
}
catch {
// Will this 'catch' if the try fails,
// even if we said we are 'return'ing right before the 'try'?
return []
}
}
Here I am fetching a list of notifications stored in CoreData. In the do block you can see the line of code in question.
QUESTION
Will the catch block execute if the try fails after already stating that the function should return?
What you have should work as expected. Basically what happens is if a throw occurs at any time within a do, the catch is called and any code after the throw will not be executed.
Yes, the catch block will execute if the try in return try fails. The return will not happen.
Here's a little code to prove it to yourself. Paste it into a new playground to try it out.
import UIKit
let shouldFail = true
enum DemoError:Error {
case shouldFail
}
func failableGetter() throws -> String {
if shouldFail { throw DemoError.shouldFail }
return "Succeeded"
}
func fetchInfo() -> String {
do {
return try failableGetter()
} catch {
return "Failed"
}
}
print(fetchInfo()) // "Failed" or "Succeeded" depending on shouldFail
When shouldFail is true, the failableGetter() throws an error and the do-catch in fetchInfo() skips to the catch section before returning.
When shouldFail is false, the failableGetter() doesn't fail and fetchInfo() returns the result.
Adding to this answer. Scope matters a bit here. Code inside the do block code after a throw will NOT be executed. However, code further down outside of the scope of the do block will be executed. I made a simple playground you can run to see for yourself.
import Foundation
let error = NSError(domain: "", code: 123, userInfo: [NSLocalizedDescriptionKey: "My error"])
func functionThatAlwaysThrows() throws {
throw(error)
}
func myFunction() {
do {
try functionThatAlwaysThrows()
// This will never print
print("Continuing after throw inside do scope")
} catch let err {
print("Caught Error: \(err.localizedDescription)")
}
// This will always print
print("Continuing after throw outside do scope")
}
Output:
Caught Error: My error
Continuing after throw outside do scope
If you want more information on Error handling you can take a look at the docs