Как выйти из последовательности "Будущее" в одном маршруте Vapor?

Как вычисление может ветвиться из последовательности нескольких Future действия, по одному маршруту Vapor, чтобы вернуть простой StringResponse что указывает на какой этап был завершен?

Возможно ли это в 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
##########################
Другие вопросы по тегам