如何将这种副作用转化为优雅的 Vapor 操作链?
How to transform this side-effect into an elegant chaining of Vapor operations?
我开始使用 Vapor 来 运行 和 API,但我想不出 优雅 的方式来执行以下操作:(1 ) 加载 Fluent 关系并 (2) 在 (3) 响应原始请求之前等待 HTTP 回调的响应。
在我将现实生活中的实体转化为行星和恒星以维护 Vapor 文档中的示例之后,这就是我最终编写的代码。它有效™️,但我无法实现良好的操作链☹️。
func flagPlanetAsHumanFriendly(req: Request) throws -> EventLoopFuture<Planet> {
Planet(req.parameters.get("planetID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { planet in
// I load the star because I need its ID for the HTTP callback
_ = planet.$star.load(on: req.db).map {
let uri = URI(scheme: "http", host: "localhost", port: 4200, path: "/webhooks/star")
// HTTP Callback
req.client.post(uri) { req in
try req.content.encode(
WebhookDTO(
starId: planet.star.id!,
status: .humanFriendly,
planetId: planet.id!
),
using: JSONEncoder()
)
}
.map { res in
debugPrint("\(res)")
return
}
}
// Couldn't find a way to wait for the response, so the HTTP callback is a side-effect
// and its response is not used in the original HTTP response...
// If the callback fails, my API can't report it.
planet.state = .humanFriendly
return planet.save(on: req.db).map { planet }
}
}
问题#1。结合 2 EventLoopFuture
我的第一个问题是我找不到一种方法来加载父关系,同时将实体保留在范围内。
Planet(req.parameters.get("planetID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { planet in // planet is in the scope
return planet.$star.load(on: req.db) // returns a EventLoopFuture<Void>
}
.map {
// I know that star has been loaded but I lost my `planet` reference ☹️
???
}
我假设有一个操作员应该能够 return 混合使用 2 个 EventLoopFuture
实例,但我想不通。
问题#2。将 EventLoopFuture
与辅助 HTTP 请求的响应链接
在这里,我假设我错过了一个操作员,它允许我在响应原始请求之前等待请求的响应,同时保持对行星的引用。
欢迎提供有关如何在良好的操作链中实现此目的的帮助——当然,如果可能的话——我将非常乐意相应地更新 Vapor 的文档。
简而言之,如果您需要一个未来的结果包含在另一个未来的结果中,那么您必须嵌套回调——您不能使用链接。例如:
Planet(req.parameters.get("planetID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { planet in // planet is in the scope
return planet.$star.load(on: req.db).map {
// Star is now eager loaded and you have access to planet
}
}
如果您有多个互不依赖的期货,但您需要两者的结果,您可以使用 and
将两者链接在一起并等待它们。例如:
func flagPlanetAsHumanFriendly(req: Request) throws -> EventLoopFuture<Planet> {
Planet(req.parameters.get("planetID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { planet in
// I load the star because I need its ID for the HTTP callback
planet.$star.load(on: req.db).map {
let uri = URI(scheme: "http", host: "localhost", port: 4200, path: "/webhooks/star")
// HTTP Callback
let postFuture = req.client.post(uri) { req in
try req.content.encode(
WebhookDTO(
starId: planet.star.id!,
status: .humanFriendly,
planetId: planet.id!
),
using: JSONEncoder()
)
}.map { res in
debugPrint("\(res)")
return
}
planet.state = .humanFriendly
let planetUpdateFuture = planet.save(on: req.db)
return postFuture.and(planetUpdateFuture).flatMap { _, _ in
return planet
}
}
}
}
我开始使用 Vapor 来 运行 和 API,但我想不出 优雅 的方式来执行以下操作:(1 ) 加载 Fluent 关系并 (2) 在 (3) 响应原始请求之前等待 HTTP 回调的响应。
在我将现实生活中的实体转化为行星和恒星以维护 Vapor 文档中的示例之后,这就是我最终编写的代码。它有效™️,但我无法实现良好的操作链☹️。
func flagPlanetAsHumanFriendly(req: Request) throws -> EventLoopFuture<Planet> {
Planet(req.parameters.get("planetID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { planet in
// I load the star because I need its ID for the HTTP callback
_ = planet.$star.load(on: req.db).map {
let uri = URI(scheme: "http", host: "localhost", port: 4200, path: "/webhooks/star")
// HTTP Callback
req.client.post(uri) { req in
try req.content.encode(
WebhookDTO(
starId: planet.star.id!,
status: .humanFriendly,
planetId: planet.id!
),
using: JSONEncoder()
)
}
.map { res in
debugPrint("\(res)")
return
}
}
// Couldn't find a way to wait for the response, so the HTTP callback is a side-effect
// and its response is not used in the original HTTP response...
// If the callback fails, my API can't report it.
planet.state = .humanFriendly
return planet.save(on: req.db).map { planet }
}
}
问题#1。结合 2 EventLoopFuture
我的第一个问题是我找不到一种方法来加载父关系,同时将实体保留在范围内。
Planet(req.parameters.get("planetID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { planet in // planet is in the scope
return planet.$star.load(on: req.db) // returns a EventLoopFuture<Void>
}
.map {
// I know that star has been loaded but I lost my `planet` reference ☹️
???
}
我假设有一个操作员应该能够 return 混合使用 2 个 EventLoopFuture
实例,但我想不通。
问题#2。将 EventLoopFuture
与辅助 HTTP 请求的响应链接
在这里,我假设我错过了一个操作员,它允许我在响应原始请求之前等待请求的响应,同时保持对行星的引用。
欢迎提供有关如何在良好的操作链中实现此目的的帮助——当然,如果可能的话——我将非常乐意相应地更新 Vapor 的文档。
简而言之,如果您需要一个未来的结果包含在另一个未来的结果中,那么您必须嵌套回调——您不能使用链接。例如:
Planet(req.parameters.get("planetID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { planet in // planet is in the scope
return planet.$star.load(on: req.db).map {
// Star is now eager loaded and you have access to planet
}
}
如果您有多个互不依赖的期货,但您需要两者的结果,您可以使用 and
将两者链接在一起并等待它们。例如:
func flagPlanetAsHumanFriendly(req: Request) throws -> EventLoopFuture<Planet> {
Planet(req.parameters.get("planetID"), on: req.db)
.unwrap(or: Abort(.notFound))
.flatMap { planet in
// I load the star because I need its ID for the HTTP callback
planet.$star.load(on: req.db).map {
let uri = URI(scheme: "http", host: "localhost", port: 4200, path: "/webhooks/star")
// HTTP Callback
let postFuture = req.client.post(uri) { req in
try req.content.encode(
WebhookDTO(
starId: planet.star.id!,
status: .humanFriendly,
planetId: planet.id!
),
using: JSONEncoder()
)
}.map { res in
debugPrint("\(res)")
return
}
planet.state = .humanFriendly
let planetUpdateFuture = planet.save(on: req.db)
return postFuture.and(planetUpdateFuture).flatMap { _, _ in
return planet
}
}
}
}