Reputation: 1414
I'm implementing a function, which takes an array and a completion closure. I iterate over the items in the array, performing a network request based on each item. When each request is finished, it appends its results to a collection.
Once all the requests have returned, the function's completion block is called with the accumulated collection.
My issue is that I'm not sure about how to handle the (potentially multiple) errors that the network request might provide. Is there an standard way to deal with this pattern? The first ideas that come to mind are:
As soon as an network request's error is given, call the function's completion closure with nil results and that error.
After all network requests have all finished, call the completion closure with some kind of structure that contains the results for the calls that completed successfully and the errors for the calls that didn't.
Neither of these options seem ideal. Is there a standard way to deal with this issue? I'm not sure what a user of this function would reasonably expect.
I'm implementing this in Swift 3.1.
Upvotes: 0
Views: 611
Reputation: 6876
I don't know how your completion closure looks but you could consider using an enum
with an associated value for the result (here you can read more about associated values).
If you create an enum
like this:
enum ClosureResult {
case success([String])
case failure([String], [Error])
}
you have the ability to return different values dependent on whether your network lookups failed or succeded. Note that I'm passing an array of Error
objects as the second parameter in my failure
case, I don't know if that suits you but it is just meant to illustrate that you can pass more than one parameter in return.
If you then use the ClosureResult
as the return value of you completion closure, you can do a switch
on it in your own block when you return and act dependent on the value.
So, your function could look something like this:
func yourFunction(items: [String], completion: (ClosureResult) -> Void) {
var completedItems = [String]()
var failedItems = [Error]()
for item in items {
//Do your network magic here
//if it succeeds add it to completedItems
//if it fails, add it to failedItems
}
//did we encounter any failures?
if failedItems.count > 0 {
//yes we did, return a .failure then
let failure = ClosureResult.failure(completedItems, failedItems)
completion(failure)
} else {
//no...return .success
let success = ClosureResult.success(completedItems)
completion(success)
}
}
And you can then use it like so:
let items = ["a", "b", "c"]
yourFunction(items: items) { result in
switch result {
case .success(let okItems):
print(okItems)
case .failure(let okItems, let failedItems):
print(failedItems)
}
}
In your comment you ask:
My only concern is that in, in your example, the network request succeeds on "a" and "c" but not "b", the closure result contain any information about which item failed. Would it be reasonable to return tuples of the items and the network result for that item in the ClosureResult object instead of just strings?
And yes, that makes sense. I just used String
and Error
as an example but you can use your own classes too.
I think, what I'd do was, instead of using a tuple I'd create a simple class and return that.
For instance something like:
struct ClosureError {
let item: String
let error: Error
}
And then your ClosureResult
enum could use that:
enum ClosureResult {
case success([String])
case failure([String], [ClosureError])
}
In your yourFunction
you'd need to create a new instance of ClosureError
when you encountered a failure:
...
var failedItems = [ClosureError]()
...
for item in items {
//Do your network magic here
//if it succeeds add it to completedItems
//if it fails, add it to failedItems
let error = //create the error
let closureFailure = ClosureFailure(item, error)
failedItems.append(closureFailure)
}
...
And finally, in your switch
you would know which items had failed:
let items = ["a", "b", "c"]
yourFunction(items: items) { result in
switch result {
case .success(let okItems):
print(okItems)
case .failure(let okItems, let failedItems):
for closureError in failedItems {
print("item: \(closureError.item) has failed with error \(closureError.error)"
}
}
}
You should probably figure out some better names for the various parts :)
Hope that makes sense and can be used.
Upvotes: 2