Дождитесь завершения асинхронной задачи в запросе в Vapor

Я пишу API, используя быстрый фреймворк на стороне сервера Vapor. Моя модель очень проста. У меня есть таблица Workout, которая имеет отношение ко многим к таблице Circuit, которая имеет отношение ко многим с таблицей Exercise. Когда потребитель api создает тренировку, json содержит все схемы и упражнения в тренировке. Вот пример:

{
    ...
    circuits: [{
        ...,
        exercises: [{
            "title": ...
        }, {
            "title": ...
        }]
    }, {
        ...,
        exercises: [{
            "title": ...
        }, {
            "title": ...
        }]
    }]
}

Проблема, с которой я сталкиваюсь, заключается в создании дублирующих упражнений, когда пользователь выполняет одно и то же упражнение в нескольких схемах. Я запрашиваю базу данных перед созданием нового упражнения, но похоже, что все сохранения происходят асинхронно, что не позволяет мне обнаружить существующее упражнение с тем же идентификатором / заголовком в базе данных перед сохранением нового. Вот мой код:

private func saveExercise(on conn: DatabaseConnectable, with content: WorkoutCreateExerciseContent, for authUser: User) throws -> Future<Exercise> {

        if let exerciseId: UUID = content.id {

            return Exercise.find(exerciseId, on: conn)
                .unwrap(or: Abort(.badRequest, reason: "No exercise for id: \(exerciseId.uuidString)"))

        } else if let title: String = content.title, let isPublicallyVisible: Bool = content.isPublicallyVisible {

            return Exercise.query(on: conn).filter(\.title == title).filter(\.directions == content.directions).first().flatMap({ (possibleMatchingExercise: Exercise?) -> Future<Exercise> in

                if let matchingExercise: Exercise = possibleMatchingExercise {

                    return conn.future(matchingExercise)

                } else {
                    print("xxx Found no match for \(title)")
                    let newExercise: Exercise = Exercise(
                        title: title,
                        directions: content.directions,
                        isPublicallyVisible: isPublicallyVisible
                    )
                    newExercise.userId = authUser.id
                    return newExercise.save(on: conn)

                }

            })

        } else {

            throw Abort(.badRequest, reason: """
                Bad data for exercise. Expecting either of the following:
                {
                    /"id/": /"some id/"
                }
                or
                {
                    /"title/": /"some title/"
                    /"directions/": /"(optional)/",
                    /"isPublicallyVisible/": false
                }
                id takes priority over other properties.
            """)
        }

    }

    private func saveCircuit(on conn: DatabaseConnectable, with content: CircuitCreateContent, order: Int, workoutId: UUID, authUser: User) throws -> Future<Circuit> {

        return try content.exercises.map({ (exerciseContent: WorkoutCreateExerciseContent) throws -> Future<Exercise> in

            let createdExercise: Future<Exercise> = try self.saveExercise(
                on: conn,
                with: exerciseContent,
                for: authUser
            )

            return createdExercise

        })
        .flatten(on: conn)
        .flatMap({ (exercises: [Exercise]) -> Future<Circuit> in

            let circuit: Circuit = Circuit(
                workoutId: workoutId,
                order: order,
                exerciseDuration: content.exerciseDuration,
                restDuration: content.restDuration,
                exerciseIDs: exercises.compactMap({ $0.id })
            )

            return circuit.save(on: conn)

        })

    }

    func saveWorkout(_ conn: DatabaseConnectable,  _ authentiatedUser: User, _ content: WorkoutCreateContent) throws -> Future<Workout> {

        let workout: Workout = content.makeWorkout()

        workout.userId = authentiatedUser.id

        // First save the exercises

        let createdWorkout: Future<Workout> = workout.save(on: conn).flatMap({ (savedWorkout: Workout) -> Future<Workout> in

            let createdCircuits: Future<[Circuit]> = try content.circuits.enumerated().map({ (order: Int, circuitContent: CircuitCreateContent) throws -> Future<Circuit> in

                let createdCircuit: Future<Circuit> = try self.saveCircuit(
                    on: conn, with: circuitContent,
                    order: order,
                    workoutId: savedWorkout.id!,
                    authUser: authentiatedUser
                )

                return createdCircuit

            })
            .flatten(on: conn)

            return createdCircuits.then({ _ -> Future<Workout> in
                return conn.future(savedWorkout)
            })

        })

        return createdWorkout

    }

Кто-нибудь видит, что мне здесь не хватает? Есть ли способ дождаться выполнения Future перед запуском нового Future? Я новичок в Vapor, поэтому я не очень хорошо знаком с api Vapor.


person Chandler De Angelis    schedule 26.06.2019    source источник


Ответы (1)


допустим, у нас есть два воображаемых метода, возвращающих Future<>

func randomUInt() -> Future<UInt> { ... }
func multiply(value: UInt, by multiplier: UInt) -> Future<UInt> { ... }

и мы хотим умножить случайное число на 5, но если случайное число равно нулю или 1, мы хотим выдать ошибку. поэтому нам нужно дождаться случайного числа перед вызовом метода multiply(value: ,by: ).

let result: Future<UInt> = randomUInt().flatMap { (value) -> Future<UInt> in
    guard value > 1 else { throw YourError }
    return multiply(value: value, by: 5)
}
person MomoNjf    schedule 18.02.2020