Как выйти из последовательности "Будущее" в одном маршруте Vapor?
Как вычисление может ветвиться из последовательности нескольких Future
действия, по одному маршруту Vapor, чтобы вернуть простой String
Response
что указывает на какой этап был завершен?
Возможно ли это в Vapor 3 на основе Swift 4?
Future
методы catch(_:)
, catchMap(on:_:)
, а также catchFlatMap(_:)
может выполнить, если выдана ошибка; однако мои эксперименты с любым подходом к улову не смогли разветвить последовательность Future
действия. (см. API и документы)
Примечание. Поскольку ядро Vapor 3 Async построено на основе swift-nio, решение SwiftNIO также может представлять интерес.
пример
Например, рассмотрим Future
последовательность, которая будет create
запись в БД, update
та же запись в БД, query
(читать) запись в БД, а затем вернуть некоторые String
ответ.
Структура для проводки
{
"number": 0
}
public struct ExamplePipe: Codable {
public var id: UUID?
public var number: Int
init(number: Int) {
self.number = number
}
public func description() -> String {
return """
UUID: \(id?.uuidString ?? "nil.....-....-....-....-............")
{number:\(number)}
"""
}
}
// Database model for fetching and saving data via Fluent.
extension ExamplePipe: SQLiteUUIDModel {}
// Content convertable to/from HTTP message.
extension ExamplePipe: Content {}
// Database migration
extension ExamplePipe: Migration {}
// Dynamic HTTP routing parameter: `id`
extension ExamplePipe: Parameter {}
struct ExamplePipeController: RouteCollection {
func boot(router: Router) throws {
let pipelineRoutes = router.grouped("api", "pipeline")
// POST http://localhost:8080/api/pipeline/linear
pipelineRoutes.post(ExamplePipe.self, at: "linear", use: linearPost)
// POST http://localhost:8080/api/pipeline/nested
pipelineRoutes.post(ExamplePipe.self, at: "nested", use: nestedPost)
}
// …
}
Сценарий: map
Линейная последовательность
// POST http://localhost:8080/api/example/pipeline/basic
func linearPost(_ request: Request, _ pipelineData: ExamplePipe)
throws -> Future<String> {
var s = "##### Linear Pipeline Data #####\n"
let mutableA = pipelineData
s += "## STAGE_A \(mutableA.description())\n"
let futureA: Future<ExamplePipe> = mutableA.create(on: request)
let futureB: Future<ExamplePipe> = futureA.flatMap(to: ExamplePipe.self) {
(nonmutableB: ExamplePipe) -> Future<ExamplePipe> in
var mutableB = nonmutableB
mutableB.number += 1
if mutableB.number == 2 {
print("POSSIBLE TO EXIT SEQUENCE AT STAGE B??")
}
s += "## STAGE_B \(mutableB.description())\n"
let futureB: Future<ExamplePipe> = mutableB.update(on: request)
return futureB
}
let futureC: Future<ExamplePipe?> = futureB.flatMap {
(nonmutableC: ExamplePipe) -> Future<ExamplePipe?> in
s += "## STAGE_C \(nonmutableC.description())\n"
if nonmutableC.id == nil {
print("POSSIBLE TO EXIT SEQUENCE AT STAGE C??")
}
let uuid = nonmutableC.id!
let futureC: Future<ExamplePipe?> = ExamplePipe
.query(on: request)
.filter(\ExamplePipe.id==uuid)
.first()
return futureC
}
let futureD: Future<String> = futureC.map(to: String.self) {
(nonmutableD: ExamplePipe?) -> String in
guard var mutableD = nonmutableD else {
s += "## STAGE_D ExamplePipe is NIL\n"
s += "#################################\n"
print(s)
return s
}
mutableD.number += 1
s += "## STAGE_D \(mutableD.description())\n"
s += "#################################\n"
print(s)
return s
}
return futureD
}
Сценарий: map
Вложенная последовательность
// POST http://localhost:8080/api/example/pipeline/nested
func nestedPost(_ request: Request, _ pipelineData: ExamplePipe)
throws -> Future<String> {
var s = "##### Nested Pipeline Data #####\n"
let mutableA = pipelineData
s += "## STAGE:A \(mutableA.description())\n"
let futureA: Future<ExamplePipe> = mutableA.create(on: request)
let futureD: Future<String> = futureA.flatMap {
(nonmutableB: ExamplePipe) -> Future<String> in
var mutableB = nonmutableB
mutableB.number += 1
if mutableB.number == 2 {
print("POSSIBLE TO EXIT SEQUENCE AT STAGE B??")
}
s += "## STAGE:B \(mutableB.description())\n"
let futureB: Future<ExamplePipe> = mutableB.update(on: request)
let futureDD: Future<String> = futureB.flatMap {
(nonmutableC: ExamplePipe) -> Future<String> in
s += "## STAGE:C \(nonmutableC.description())\n"
if nonmutableC.id == nil {
print("POSSIBLE TO EXIT SEQUENCE AT STAGE C??")
}
let uuid = nonmutableC.id!
let futureC: Future<ExamplePipe?> = ExamplePipe
.query(on: request)
.filter(\ExamplePipe.id==uuid)
.first()
let futureDDD: Future<String> = futureC.map(to: String.self) {
(nonmutableD: ExamplePipe?) -> String in
guard var mutableD = nonmutableD else {
s += "## STAGE:D ExamplePipe is `nil`\n"
s += "#################################\n"
print(s)
return s
}
mutableD.number += 1
s += "## STAGE:D \(mutableD.description())\n"
s += "#################################\n"
print(s)
return s
}
return futureDDD
}
return futureDD
}
return futureD
}
1 ответ
Асинхронное ядро Vapor 3, построенное поверх swift-nio, по-видимому, не имеет прямого механизма (механизмов) для разветвления Future
последовательность.Future
этоtypealias
для SwiftNIO EventLoopFuture
,
Ниже приведены два возможных подхода на основе Swift для выхода из последовательности или эффективного "пропуска" оставшейся последовательности. Оба подхода могут вернуть какое-то значимое полноеString
клиенту.
Подход 1: последовательность выхода (броскиDebuggable
ошибка)
Если целью является возвращение некоторой значимой информации клиенту при выходе из последовательности, то одна возможность состоит в том, чтобы Debuggable
ошибка для выхода Future
последовательность. Debuggable
позволяет получить ответ, содержащий причину. В этом сценарии String
становится значением JSON "разум".
Настроить:
public struct ExamplePipeError: Error {
let comment: String
init(comment: String) {
self.comment = comment
}
}
extension ExamplePipeError: Debuggable {
// `Debuggable` Required Attributes
public var identifier: String {
return "\(ExamplePipeError.readableName)"
}
public var reason: String {
return "\(comment)"
}
}
private func checkNumber(number: Int, mustNotBe: Int, comment: String) throws {
if number == mustNotBe {
let error = ExamplePipeError(comment: comment)
throw error
}
}
Использование:
let futureB: Future<ExamplePipe> = futureA.flatMap(to: ExamplePipe.self) {
(nonmutableB: ExamplePipe) throws -> Future<ExamplePipe> in
var mutableB = nonmutableB
mutableB.number += 1
s += "## STAGE_B \(mutableB.description())\n"
try self.checkNumber(
number: mutableB.number,
mustNotBe: 2,
comment: "STAGE_B requires number which is not 2."
)
let futureB: Future<ExamplePipe> = mutableB.update(on: request)
return futureB
}
Клиент получает:
{"error":true,"reason":"STAGE_B requires number which is not 2."}
Подход 2: пропустить этапы последовательности (использует переменную состояния)
Настроить:
enum ExamplePipeState {
case ok
case exitedStageB
case exitedStageC
}
Использование:
func linearStageFlagPost(_ request: Request, _ pipelineData: ExamplePipe)
throws -> Future<String> {
var state: ExamplePipeState = .ok
var s = ""
s += "##### Pipeline Data: `linear-stage-flag` #####\n"
let mutableA = pipelineData
s += "## STAGE_A \(mutableA.description())\n"
let futureA: Future<ExamplePipe> = mutableA.create(on: request)
let futureB = futureA.flatMap {
(nonmutableB: ExamplePipe) -> Future<ExamplePipe> in
var mutableB = nonmutableB
mutableB.number += 1
s += "## STAGE_B \(mutableB.description())\n"
if mutableB.number == 2 {
s += " … EXIT/SKIP SEQUENCE AT STAGE B.\n"
state = ExamplePipeState.exitedStageB
let skipB: Future<ExamplePipe> = request.eventLoop.future(nonmutableB)
return skipB
}
let futureB = mutableB.update(on: request)
return futureB
}
let futureC: Future<ExamplePipe?> = futureB.flatMap {
(nonmutableC: ExamplePipe) -> Future<ExamplePipe?> in
guard state == .ok else {
s += "## STAGE_C SKIPPED\n"
return request.eventLoop.future(nonmutableC)
}
s += "## STAGE_C \(nonmutableC.description())\n"
if nonmutableC.number == 3 {
s += " … EXIT/SKIP SEQUENCE AT STAGE C.\n"
state = ExamplePipeState.exitedStageC
let skipC: Future<ExamplePipe?> = request.eventLoop.future(nil)
return skipC
}
let uuid = nonmutableC.id!
let futureC: Future<ExamplePipe?> = ExamplePipe
.query(on: request)
.filter(\ExamplePipe.id==uuid)
.first()
return futureC
}
let futureD: Future<String> = futureC.map(to: String.self) {
(nonmutableD: ExamplePipe?) -> String in
switch state {
case .ok:
guard var mutableD = nonmutableD else {
s += "## STAGE_D ExamplePipe is NIL\n"
s += "##########################\n"
print(s)
return s
}
mutableD.number += 1
s += "## STAGE_D \(mutableD.description())\n"
s += "##########################\n"
print(s)
return s
case .exitedStageB:
s += "## STAGE_D after exiting STAGE_B and skipping STAGE_C\n"
s += "##########################\n"
print(s)
return s
case .exitedStageC:
s += "## STAGE_D after exiting STAGE_C\n"
s += "##########################\n"
print(s)
return s
}
}
return futureD
}
Клиент получает:
##### Pipeline Data: `linear-stage-flag` #####
## STAGE_A UUID: nil.....-....-....-....-............
{number:1}
## STAGE_B UUID: 904156A2-BC1A-42B1-AC89-0C4A598EA4BB
{number:2}
… EXIT/SKIP `linear-try-catch` SEQUENCE AT STAGE B.
## STAGE_C SKIPPED `linear-stage-flag` SEQUENCE AT STAGE C
## STAGE_D after exiting STAGE_B and skipping STAGE_C
##########################