Failing to understand getting a value from a promise/future

I've read the vapor documentation, bought the Ray Wenderlich book on Vapor and also followed this StackOverflow on how to get data from a promise, but alas I can only seem to get the future, not the value. The remoteStruct variable always holds a future but I'm looking to get the value.

My specific use case is I have a route that needs to get JSON from a different server, transform that JSON and return the transformed JSON to the client. But the return from the .map is of course a future. I thought a .transform may be the right approach, but don't see a way to get the http.body.data to the remoteStruct and then to the localStruct in the call to .transform. Here is my (most of my, with some anonymization and brevity(!) for clarity) code:

        func sessionData(request: Request) throws -> Response {
            let response = Response(http: HTTPResponse(status: .ok), using: request)
            let client = try request.make(Client.self)
            let remoteResponse = client.get(remoteURLString)
            let remoteStruct = remoteResponse.map(to: RemoteStruct.self) { clientHTTPResponse in
                let data = clientHTTPResponse.http.body.data!
                let remoteStruct = try JSONDecoder().decode(RemoteStruct.self, from: data)
                return remoteStruct
            }
        
            let localObject = LocalObject(from: remoteObject)

            try response.content.encode(localObject, as: MediaType.json)
            return response
}

Thanks in advance.

In general, it’s the wrong mindset to think about “getting the value from a Future”. A Future is an assertion that there will be a result sometime, but there isn’t necessarily one right now.

In a Vapor application, where possible it is better to operate within the Future abstraction. That means that your function should probably be changed to return Future<Response> instead of Response, which should avoid this problem altogether.

Thanks, I'll admit that is not intuitive to me. So then the code above would transform to this below (conceptually, if not practically since there's some shortcuts for brevity)?

 func sessionData(request: Request) throws -> Future<Response> {
        let client = try request.make(Client.self)
        client.get(remoteURLString).map(to: Response.self) { clientHTTPResponse in
            let data = clientHTTPResponse.http.body.data!
            let remoteStruct = try JSONDecoder().decode(RemoteStruct.self, from: data)
            let localStruct = LocalStruct(from: remoteStruct)
            let response = Response(http: HTTPResponse(status: .ok), using: request)
            return try response.content.encode(localStruct, as: MediaType.json)
        }
    }

Ahead of this code, I check for a valid parameter and an API key, which can return immediately when they're not in the request. What is the syntax to create a Future when it should return immediately with an error?

Would this be simply:

        let content = ErrorStruct(error: true, message: "No parameter")
        return response.transform(to: Response(http: HTTPResponse(status: .ok), using: request.content.encode(content, as: MediaType.json))

You can make EventLoopFuture that is already in a fulfilled or failed state with newFailedFuture(error:) or newSucceededFuture(result:) on the request.eventLoop property.

1 Like

You can actually tidy this up as well, using a lot of Vapor's helpers. So your code could look like:

 func sessionData(request: Request) throws -> Future< LocalStruct > {
        return try request.client().get(remoteURLString).map { response in
            let remoteStruct = try response.content.syncDecode(RemoteStruct.self)
            return LocalStruct(from: remoteStruct)
        }
    }

When you return the no parameter error do you want to return a 200 OK or something like a 400 Bad Request. Since Vapor's request handlers allow you to throw you could just do throw Abort(.badRequest). (You can create your own custom AbortErrors if you want to set the message etc.

Vapor also makes it easy to return futures out of any objects, you can just return req.future(myContent) to convert MyContent into Future<MyContent>

1 Like

Thanks all. I've got that working now!

Regarding why I return status .ok and an error object when there is an error is out of my control unfortunately, even if I don't agree its correct.