1

计算流如何Future在单个 Vapor 路由中从多个动作序列中分支出来,以返回一个简单String Response的指示退出哪个阶段?

Future如果抛出错误,方法catch(_:)、、catchMap(on:_:)和可以执行;catchFlatMap(_:)但是,到目前为止,我对任何 catch 方法的实验都无法Future扩展动作序列。(参见API文档

注意:由于Vapor 3 Async 核心是建立在swift-nio之上的,所以 SwiftNIO 解决方案也很有趣。

例子

例如,考虑一个Future序列,它将create一个 db 条目、update相同的 db 条目、query(读取)该 db 条目,然后返回一些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
}
4

1 回答 1

0

建立在swift-nio之上的 Vapor 3 异步核心似乎没有任何直接机制来分支出多个 Future序列。FuturetypealiasSwiftNIO的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
##########################
于 2018-07-25T00:26:25.730 回答