【问题标题】:How to branch out of a 'Future' sequence in a single Vapor route?如何在单个 Vapor 路线中分支出“未来”序列?
【发布时间】:2018-12-29 07:55:33
【问题描述】:

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

Future方法catch(_:)catchMap(on:_:)catchFlatMap(_:)可以在抛出错误时执行;但是,到目前为止,我对任何 catch 方法的实验能够扩展Future 操作的序列。 (见API & Docs

注意:由于 Vapor 3 Async core 是在 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
}

【问题讨论】:

    标签: swift swift4 vapor swift-nio


    【解决方案1】:

    建立在 swift-nio 之上的 Vapor 3 异步核心似乎没有任何直接机制来分支出 多个 Future 序列。 Future 是 SwiftNIO 的 typealias 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
    ##########################
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-20
      • 2020-04-07
      • 2019-04-30
      • 1970-01-01
      • 1970-01-01
      • 2021-10-25
      • 2019-05-23
      • 2010-09-12
      相关资源
      最近更新 更多