首页 > 解决方案 > Swift / SwiftNIO / Vapor:EventLoop 中的 wait() 或类似的,相当于 JavaScript 的 Promise.all()

问题描述

我从 Vapor 4 开始,在旅程的一开始就卡住了。

我知道 JavaScript 中的 Promises,并且我想我对 Swift 的 Futures 有所了解。我认为我的问题是事实,可悲的是,大多数教程都使用wait()它们来保持他们的例子简短而简单。在 Vapor 中,我遇到了 EventLoopwait()并被禁止进入。

我正在尝试做的事情

我正在尝试对 MySQL 数据库执行一些查询,这些查询需要串行执行:

  1. 两个表被截断。
  2. 然后我将第三个表中的所有行复制到一个截断表中。
  3. 最后,我正在查询该填充表,尝试遍历每个匹配的结果并将其插入到另一个截断表中。

出了什么问题/我需要帮助的地方

  1. 经过几天难以理解的编译错误,它现在正在运行。第一部分正在执行,但它缺少某些回调的正确实现。我正在考虑JavaScript 中的Promise.all([])之类的东西。我不想嵌套这两个查询,因为我认为拥有一个表名数组并为每个查询执行查询会更干净。这是我不知道该怎么做的第一件小事。
  2. 最重要的是:第二步,将匹配的行插入到另一个表中,失败了。在 Xcode 的控制台中,它会打印很多次:
    [ ERROR ] Connection request timed out. This might indicate a connection deadlock in your application. If you're running long running requests, consider increasing your connection timeout. [database-id: mysql, request-id: F159E838-0E90-4025-929E-596A6A66A502]

我想有几种更好的方法可以解决这个问题,但是因为我想学习和思考一些我想尝试实现的其他任务,我想通过串行执行这些查询来解决它。

我的代码

Controllers/RubricsTreeController.swift

import Fluent
import FluentMySQLDriver
import MySQLNIO
import Vapor

struct RubricsTreeController: RouteCollection {
    func rebuild(req: Request) throws -> EventLoopFuture<[Rubric]> {
        let mysql = req.db as? MySQLDatabase

        // Clear database tables
        let tables = ["rubrics", "rubrics_tree"]
        for table in tables {
            mysql!.simpleQuery("TRUNCATE TABLE `\(table)`")    // <-- HERE …
            // … I´d like to somehow collect each returned Future in an Array …
        }
        // … and wait for all Futures to finish

        // Copy contents from imported `import` into table `rubrics`
        mysql!.simpleQuery("INSERT INTO `rubrics` SELECT * FROM `import`")

        // Iterate over all Rubrics and build the Tree by inserting each as a Node into the Nested Set
        let nestedSet = NestedSet(database: mysql!, table: "rubrics_tree")
        var nestedSetRootId = 1;
        let rubrics = Rubric.query(on: mysql as! Database)
            .filter(\.$level == 0)
            .sort(\.$level)
            .sort(\.$parentId)
            .sort(\.$sorting)
            .sort(\.$id)
            .all()
            .flatMapEachThrowing { rubric -> Rubric in
                try? nestedSet.newRoot(rootId: UInt16(nestedSetRootId), foreignId: UInt64(rubric.id!))
                nestedSetRootId += 1
                return rubric
            }
        return rubrics
    }
}

Helpers/NestedSet.swift

import Fluent
import FluentMySQLDriver
import Vapor

class NestedSet {
    var database: MySQLDatabase
    var table: String

    init(database: MySQLDatabase, table: String) {
        self.database = database
        self.table = table
    }

    func newRoot(id: UUID? = nil, rootId: UInt16, foreignId: UInt64? = nil) throws -> EventLoopFuture<Bool> {
        return database
            .simpleQuery("INSERT INTO `\(table)`(rootId, leftValue, rightValue, nodeLevel, nodeMoved, foreignId) VALUES(\(rootId), 1, 2, 0, 0, \(foreignId ?? 0)")
            .map { _ -> Bool in
                true
            }
    }

//  func newRoot(id: UUID? = nil, foreignId: UInt64? = nil) throws -> EventLoopFuture<EventLoopFuture<Bool>> {
//      return database
//          .simpleQuery("SELECT COALESCE(MAX(rootId), 0) AS highestRootId FROM `\(table)`")
//          .flatMapThrowing { (results: [MySQLRow]) in
//              let highestRootId = (results[0].column("highestRootId")?.uint64)!
//              let rootId = UInt16(highestRootId + 1)
//              return try self.newRoot(id: id, rootId: rootId, foreignId: foreignId)
//          }
//  }
}

我很好奇你的想法和改进!:)

标签: swiftvaporswift-nio

解决方案


如果可以的话,我的建议是使用新的 async/await 东西。这将使代码更容易编写。

但是,要在EventLoopFuture陆地上执行此操作,您可以使用flatten将期货数组转换为未来数组。例如

struct RubricsTreeController: RouteCollection {
    func rebuild(req: Request) throws -> EventLoopFuture<[Rubric]> {
        let mysql = req.db as? MySQLDatabase

        // Clear database tables
        let tables = ["rubrics", "rubrics_tree"]
        var truncateResults = [EventLoopFuture<Void>]()
        for table in tables {
            let future = mysql!.simpleQuery("TRUNCATE TABLE `\(table)`").transform(to: ())
            truncateResults.append(future)    
        }
        // … and wait for all Futures to finish
        return truncateResults.flatten(on: req.eventLoop).flatMap {

            // Copy contents from imported `import` into table `rubrics`
            return mysql!.simpleQuery("INSERT INTO `rubrics` SELECT * FROM `import`").flatMap { _ in

                // Iterate over all Rubrics and build the Tree by inserting each as a Node into the Nested Set
                let nestedSet = NestedSet(database: mysql!, table: "rubrics_tree")
                var nestedSetRootId = 1;
                let rubrics = Rubric.query(on: mysql as! Database)
                    .filter(\.$level == 0)
                    .sort(\.$level)
                    .sort(\.$parentId)
                    .sort(\.$sorting)
                    .sort(\.$id)
                    .all()
                    .flatMapEachThrowing { rubric -> Rubric in
                        try? nestedSet.newRoot(rootId: UInt16(nestedSetRootId), foreignId: UInt64(rubric.id!))
                        nestedSetRootId += 1
                        return rubric
                    }
                return rubrics
            }
        }
    }
}

推荐阅读