Creating Promises to make routes wait


(Tiago Ferreira) #1

Hello everyone! Beginner Vapor user here.

I’ve been trying to develop a Vapor web app as a demo of an API wrapper I built in Swift. Right now I have a function that returns an Encodable object, and I’m trying to pass it into leaf to display the info. However, I’ve been having a hard time getting Async to work with it and making sure Vapor waits for the data before presenting the template.
Right now my code compiles but the template is presented without the data.

Here’s the route in question:

final class RoomController {

    // Registers Room routes.
    func addRoutes(_ router: Router) {
        let room = router.grouped("room")
        room.get("/", use: self.room)
    }

    // Handles root Room endpoint.
    func room(_ req: Request) throws -> Future<View> {
        let config = TokenConfiguration("TOKEN")
        let dataPromise = req.eventLoop.newPromise(Room.self) // Miserable attempt at creating a data promise
        let _ = UCLKit(config).rooms() { response in // Call to my Framework.
            switch response {
            case .success(let roomsResponse):
                print(roomsResponse.rooms![0].roomName!) // Successfuly prints data, so it should be working.
                dataPromise.succeed(result: roomsResponse.rooms![0]) // Attempt at fullfiling promise.
            case .failure(let error):
                dataPromise.fail(error: error)
            }
        }
        return try req.view().render("Room", dataPromise.futureResult) // Return rendered templatre with future result.
    }
}

And this is the template:

<!DOCTYPE html>
<html>
    <head>
        <title>UCL Rooms</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="/styles/normalize.css">
        <link rel="stylesheet" href="/styles/skeleton.css">
        <link href="//fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css">
    </head>
    <body>
        <div class="container">
            <div class="row">
                <div class="u-full-width">
                    <h1>#(roomName)</h1>
                </div>
            </div>
            <div class="row">
                <div class="six columns">
                    <h5>About This Room</h5>
                    <p>Room Name: #(room.roomName)</p>
                    <p>Site ID: #(siteID)</p>
                    <p>Room ID: #(roomID)</p>
                </div>
                <div class="six columns">
                    <h5>Bookings</h5>
                </div>
            </div>
        </div>
    </body>
</html>

Any idea how I can make this work?

Thanks a lot,
Tiago


(Tim) #2

I would say that should just return Future<[Room]> otherwise you’re massively overcomplicating it. If you do that you can simply do

func room(_ req: Request) throws -> Future<View> {
  let config = TokenConfiguration("TOKEN")
  return UCLKit(config).rooms().flatMap(to: View.self) { rooms in
    return try req.view().render("Room", dataPromise.futureResult)
  }
}

The way you have it is that your view is returned before your promise completes, so it won’t wait


(Tiago Ferreira) #3

Well I also don’t want to limit the entire framework to Vapor, most people don’t use promises, rewriting the entire framework to return futures by default seems a bit overkill


(Tim) #4

Oh I see it’s a callback. What type is roomsResponse.rooms[0]? Or what does it look like


(Tiago Ferreira) #5

It’s of type Room which is a simple class blueprint that conforms to Codable, it’s equivalent to a JSON like this:

{
  "ok": true,
  "rooms": [
    {
      "roomname": "Wilkins Building (Main Building) Portico",
      "roomid": "Z4",
      "siteid": "005",
      "sitename": "Main Building",
      "capacity": 50,
      "classification": "SS",
      "automated": "N",
      "location": {
        "coordinates": {
          "lat": "51.524699",
          "lng": "-0.13366"
        },
        "address": [
          "Gower Street",
          "London",
          "WC1E 6BT",
          ""
        ]
      }
    }
    ...
  ]
}

So rooms[0] is the first room from the response.
This is reflected as:

/// Wrapper for the Rooms response
@objc public final class RoomsResponse: NSObject, Codable {
    open var OK: Bool?
    open var error: String?
    open var rooms: [Room]?

    enum CodingKeys: String, CodingKey {
        case OK = "ok"
        case error
        case rooms
    }
}

/// Payload from the Rooms response
@objc public final class Room: NSObject, Codable {
    @objc open var roomID: String?
    @objc open var roomName: String?
    @objc open var siteID: String?
    @objc open var siteName: String?
    open var capacity: Int?
    open var classification: Classification?
    open var automated: Automation?
    @objc open var location: Location?

    enum CodingKeys: String, CodingKey {
        case roomID = "roomid"
        case roomName = "roomname"
        case siteID = "siteid"
        case siteName = "sitename"
        case capacity
        case classification
        case automated
        case location
    }
}

...

(Tim) #6

So since you’re using CodingKeys, you’ll need to change the things that Leaf refers to, something like:

<!DOCTYPE html>
<html>
    <head>
        <title>UCL Rooms</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="/styles/normalize.css">
        <link rel="stylesheet" href="/styles/skeleton.css">
        <link href="//fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css">
    </head>
    <body>
        <div class="container">
            <div class="row">
                <div class="u-full-width">
                    <h1>#(roomname)</h1>
                </div>
            </div>
            <div class="row">
                <div class="six columns">
                    <h5>About This Room</h5>
                    <p>Room Name: #(roomname)</p>
                    <p>Site ID: #(siteid)</p>
                    <p>Room ID: #(roomid)</p>
                </div>
                <div class="six columns">
                    <h5>Bookings</h5>
                </div>
            </div>
        </div>
    </body>
</html>

(Tiago Ferreira) #7

Oh that’s interesting! I can confirm it works, thanks a lot!
So, is there a reason why Leaf uses the codingKeys (which I originally used to just parse the API JSON into the object)?


(Tim) #8

@tiferrei because Leaf uses Codable then the coding keys affect it as well. So it would be the same if you were to use Fluent to save that into a database etc


(Tiago Ferreira) #9

Ah of course, that makes sense. Thanks a lot for your help @0xTim!