given a repository class, which returns a single:
Single<SomeObject> find()
and a CompletableFuture which returns a Float:
CompletableFuture<Long> completableFuture
I would like to invoke the repository method first and based on the result, I need to invoke the completableFuture. This is the code I have:
repository.find()
.flatMap(s -> {
CompletableFuture<Long> completableFuture = serviceReturningCompletableFuture;
return Single.fromFuture(completableFuture);
}).subscribe(System.out::println)
Problem here is that Single.fromFuture will block and therefor can not be used.
In order to work around this I tried things like:
repository.find()
.map(s -> {
CompletableFuture<Long> completableFuture = new CompletableFuture<>();
return Flowable.fromFuture(completableFuture);
}).subscribe(System.out::println)
while this works fine without blocking, the subscribe function prints the following instead of the number which is returned by the CompletableFuture
io.reactivex.internal.operators.flowable.FlowableFromFuture#62ce978e
I also tried to use the non blocking converter to single from net.javacrumbs.future-converter:future-converter-rxjava-java8:1.2.0:
repository.find()
.map(s -> {
CompletableFuture<Long> completableFuture = new CompletableFuture<>();
return toSingle(completableFuture);
}).subscribe(System.out::println)
but, this leads to pretty much the same output: net.javacrumbs.futureconverter.rxjavacommon.RxJavaFutureUtils$ValueSourceBackedSingle#3f1eebb8
What am I missing?
some fiddling later and this helper method:
public static <T> Single<T> toSingle(CompletableFuture<T> future) {
return Single.create(subscriber ->
future.whenComplete((result, error) -> {
if (error != null) {
subscriber.onError(error);
} else {
subscriber.onSuccess(result);
}
}));
}
seems to do the trick:
repository.find()
.flatMap(s -> {
CompletableFuture<Long> completableFuture = serviceReturningCompletableFuture;
return toSingle(completableFuture);
}).subscribe(System.out::println)
and as #akarnokd pointed out in the comment, there is this lib:
https://github.com/akarnokd/RxJavaJdk8Interop#completionstage-to-rxjava which works pretty much the same way:
repository.find()
.flatMap(s -> {
CompletableFuture<Long> completableFuture = serviceReturningCompletableFuture;
return SingleInterop.fromFuture(completableFuture);
}).subscribe(System.out::println)
Related
I have a working directory that contains every user's picture and I am trying to implement a call that returns data containing the user's picture, defined in this structure:
struct ImageData: Content {
var picture: Data // UIImage data
}
I tried to implement a solution also partially using what I found in the book 'Server Side Swift with Vapor' (version 3) in chapter 26 but that's different for me because I am not using Leaf and I need to return the data directly.
I came up with this function to return the user picture, which does its job but I am trying to improve it.
func getProfilePictureHandler(_ req: Request) throws -> EventLoopFuture<ImageData> {
return User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { user in
// To do: throw error (flatMapThrowing?)
let filename = user.profilePicture!
let path = req.application.directory.workingDirectory
+ imageFolder
+ filename
// Improvement: Do I need this?
var data = Data()
return req.fileio.readFile(at: path) { buffer -> EventLoopFuture<Void> in
let additionalData = Data(buffer: buffer)
data.append(contentsOf: additionalData)
return req.eventLoop.makeSucceededVoidFuture()
}.map {
return ImageData(picture: data)
}
}
}
First:
How to implement this using flatMapThrowing? If I replace flatMap with flatMapThrowing I get this error: "Cannot convert return expression of type 'EventLoopFuture' to return type 'ImageData'". Which doesn't make sense to me considering that flatMap allows returning a future and not a value.
I didn't find any solution other than using a Data variable and appending chunks of data as more data is read. I am not sure that this is thread-safe, FIFO and I don't consider it an elegant solution. Does anybody know any better way of doing it?
The short answer is that as soon as you have the file path, Vapor can handle it all for you:
func getProfilePictureHandler(_ req: Request) throws -> EventLoopFuture<Response> {
return User.find(req.parameters.get("userID"), on: req.db)
.unwrap(or: Abort(.notFound))
.tryflatMap { user in
// To do: throw error (flatMapThrowing?)
guard let filename = user.profilePicture else {
throw Abort(.notFound)
}
let path = req.application.directory.workingDirectory
+ imageFolder
+ filename
return req.fileio.streamFile(at: path)
}
}
You can use tryFlatMap to have a flatMap that can throw and you want to return a Response. Manually messing around with Data is not usually a good idea.
However, the better answers are use async/await and the FileMiddleware as two tools to clean up your code and remove the handler altogether
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.
I am new to Swift, so maybe the question is a little bit stupid. I don't know why I got the error here:
htmlHelper.fetchHtmlObservable(url) // this one is an Observable<String> function
.subscribeOn(ConcurrentDispatchQueueScheduler.init(qos: .background))
.map { //Generic parameter 'Result' could not be inferred
let parsingHelper = ParsingTypeFactory.getParsingType(parsingType: self.parsingType)
return parsingHelper.parseActionItems(document: $0)
}
To resolve it, I had to add
.map { doc -> [MyItem] in
let parsingHelper = ParsingTypeFactory.getParsingType(parsingType: self.parsingType)
return parsingHelper.parseActionItems(document: doc)
}
When can I omit the params and the return type?
In order for type inference to work in closures, you generally need either the outer scope to know the type or the closure itself needs to be one line. This is a limitation in the Swift type system. So either:
htmlHelper.fetchHtmlObservable(url) // this one is an Observable<String> function
.subscribeOn(ConcurrentDispatchQueueScheduler.init(qos: .background))
.map {
ParsingTypeFactory.getParsingType(parsingType: self.parsingType).parseActionItems(document: $0)
}
or
let myItems: Observable<[MyItem]> = htmlHelper.fetchHtmlObservable(url) // this one is an Observable<String> function
.subscribeOn(ConcurrentDispatchQueueScheduler.init(qos: .background))
.map {
let parsingHelper = ParsingTypeFactory.getParsingType(parsingType: self.parsingType)
return parsingHelper.parseActionItems(document: $0)
}
Other options:
Note that in all of the cases discussed so far, you are holding a strong reference to self and likely causing a memory cycle/leak. You can avoid that by making a helper function that isn't part of the class:
// do NOT put this in the class, make it a free function (possibly private to avoid namespace pollution.)
func parser(for parsingType: ParsingType) -> (Document) -> [MyItem] {
return { document in
let parsingHelper = ParsingTypeFactory.getParsingType(parsingType: parsingType)
return parsingHelper.parseActionItems(document: document)
}
}
And now the code in question becomes:
let myItems = htmlHelper.fetchHtmlObservable(url) // this one is an Observable<String> function
.subscribeOn(ConcurrentDispatchQueueScheduler.init(qos: .background))
.map(parser(for: parsingType))
If you don't like the idea of a free function, or you don't like a function that returns a function, you can put the function in an extension on ParserType:
extension ParsingType {
func parser(document: Document) -> [MyItem] {
let parsingHelper = ParsingTypeFactory.getParsingType(parsingType: self)
return parsingHelper.parseActionItems(document: document)
}
}
and now the original code becomes:
let myItems = htmlHelper.fetchHtmlObservable(url) // this one is an Observable<String> function
.subscribeOn(ConcurrentDispatchQueueScheduler.init(qos: .background))
.map(parsingType.parser(document:))
This also avoids keeping a reference to self.
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()
}
I'm trying to write a function using Swift and Vapor but I don't understand why one statement gets printed before the other:
// Logout user
func logout(_ req: Request) throws -> Future<APIResponseMessage> {
let userID = self.checkAccessToken(req: req)
// Delete access token here
let apiResponseMessage = APIResponseMessage()
apiResponseMessage.message = "success"
apiResponseMessage.userID = userID
return apiResponseMessage.create(on: req)
}
func checkAccessToken(req: Request) -> Int {
let bearerAuthorization = req.http.headers.bearerAuthorization
guard let _bearerAuthorization = bearerAuthorization else {
// Works fine
print("no bearer incluced")
return 0
}
let _ = AccessToken.query(on: req).filter(\.accessToken == _bearerAuthorization.token).first().map(to: Int.self) { queriedAccessToken in
// This should be first
print("This gets printed second")
return queriedAccessToken!.userID!
}
// This should be second
print("This gets printed first")
return 0
}
Can anyone tell me how to make the second print statement wait until the first one is completed?
Right now it's causing my logout function to run with userID == 0 when this shouldn't be the case
As #nathan said, this is due to your code being async. Your .map callback is like the closure you pass into to a URLSession.dataTask when making request's to an external API for an iOS app.
Vapor uses a slightly different async model then what you use in iOS though, using promises and futures instead of callback closures. You can read about them in the docs.
In your case, you want to return the userID you get from the AccessToken query. To do this, you first need to change your method's return type from Int to Future<Int>. Then, instead of assigning the result of the .map call to _, you can return it from the method:
func checkAccessToken(req: Request) -> Future<Int> {
let bearerAuthorization = req.http.headers.bearerAuthorization
guard let _bearerAuthorization = bearerAuthorization else {
return req.future(0)
}
return AccessToken.query(on: req).filter(\.accessToken == _bearerAuthorization.token).first().map(to: Int.self) { queriedAccessToken in
return queriedAccessToken!.userID!
}
}
I would suggest you look into error handling for your queriedAccessToken and userID values so you aren't force-unwrapping them.