Feature request: allow throwing inside map/flatMap closures
jordanebelanger opened this issue · 3 comments
Similar to the Swift NIO EventLoopFuture implementation, it would be nice to be able to do something like this:
let someFuture: Future<Data, Error> ...
someFuture.map { someData -> SomeCodableObject in
let decoder = JSONDecoder()
return try decoder.decode(SomeCodableObject.self, someData)
}
This would also apply to flatMap and the other operators of course.
Although it would probably break the typed error functionalities.
Hey, @jordanebelanger. We have tryMap
which is a throwing map:
extension Future where Error == Swift.Error {
public func tryMap<NewValue>(_ transform: @escaping (Value) throws -> NewValue) -> Future<NewValue, Error> {
It's a bit less convenience that having a throwing map
but it's necessary because of the decisions to use typed errors.
Throwing flatMap
I think is probably not something that you would need often. If you do need to throw right before starting an async task, maybe you should do tryMap
first, get the data that you need to start an async tasks, then do flatMap
.
We also have a special "catching" initializers which were added in a PR recently and which are very convenient:
extension Future where Error == Swift.Error {
/// Creates a future by evaluating the given throwing closure, capturing the
/// returned value as a success, or any thrown error as a failure.
public init(catching body: () throws -> Value) {
self.init(result: Result(catching: body))
}
}
extension Future.Result where Error == Swift.Error {
/// Creates a future by evaluating the given throwing closure, capturing the
/// returned value as a success, or any thrown error as a failure.
public init(catching body: () throws -> Value) {
Hi @kean
The catching initializers are pretty nice, I don't have futureX setup in a playground atm but if I understand correctly my code could be rewritten as such:
let someFuture: Future<Data, Error> ...
someFuture.flatMap { someData -> Future<SomeCodableObject, Error> in
return Future {
let decoder = JSONDecoder()
return try decoder.decode(SomeCodableObject.self, someData)
}
}
not too bad indeed.
Yeah, you should be able to do that. That's exactly how tryMap
is implemented internally:
public func tryMap<NewValue>(_ transform: @escaping (Value) throws -> NewValue) -> Future<NewValue, Error> {
return flatMap { value -> Future<NewValue, Error> in
return Future<NewValue, Error> { try transform(value) }
}
}
These "catching" initializers compose nicely with many other methods, not just flatMap
.