Использование Vapor для итеративного вызова метода возвращает Future - PullRequest
2 голосов
/ 08 мая 2020

Я использую Swift 5 и Vapor 3. Я написал клиент для звонка в Twitter, чтобы получить подписчиков пользователя. Похоже, что

func followersOf(_ screenName : String, nextCursor : Int64 = -1) throws -> Future<UserCursor> {
    logger.debug("Fetching followers of \(screenName)")
    let res = httpClient.get("https://api.twitter.com/1.1/followers/list.json?screen_name=\(screenName)&nextCursor=\(nextCursor)", headers: ["authorization": authToken])
    return res.flatMap { res in
        return try res.content.decode(UserCursor.self, using: self.jsonDecoder)
    }
}

UserCursor возвращает значение для nextCursor и список пользователей для выбранной страницы. Мне нужно продолжать вызывать этот метод со значением nextCursor и накапливать пользователей для каждой страницы, пока nextCursor не вернет -1. Как мне использовать Future, возвращенный из этого метода, чтобы итеративно вызывать его, пока я не посетил все страницы курсора, накапливая User s, возвращаемые из каждого вызова?

Это то, что у меня так далеко, но я в растерянности. Я чувствую, что ошибаюсь.

func followersOf(_ req : Request) throws -> Future<FollowersView> {
    let logger = try req.make(Logger.self)
    let screenName = try req.parameters.next(String.self)

    logger.debug("Request for followers of \(screenName)")

    let twitter = try req.make(TwitterClient.self)
    return try twitter.followersOf(screenName).flatMap { userCursor in
        var uc = userCursor
        var users : Set<User> = []
        users = users.union(userCursor.users)
        while (uc.nextCursor != -1) {
            try twitter.followersOf(screenName, nextCursor: userCursor.nextCursor).map { uc in uc}
        }
        return FollowersView(screenName, users)
    }
}

1 Ответ

2 голосов
/ 08 мая 2020

Я думаю, что внутри twitter вы могли бы создать частный _followersFetcher метод, который будет вызывать _followers, пока не получит -1 курсор, и метод publi c fetchFollowers, который будет иметь дело с сборщиком, что-то вот так:

import Vapor

class TwitterClient : Service {
    private let authToken : String
    var httpClient : Client

    let jsonDecoder : JSONDecoder
    let logger : Logger
    let eventLoop : EventLoop

    init(_ client : Client, _ logger : Logger) throws {
        jsonDecoder = JSONDecoder()
        jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase

        guard let apiToken = Environment.get("TWITTER_TOKEN") else {
            throw Abort(.internalServerError)
        }

        authToken =  "Bearer " + apiToken

        self.logger = logger

        self.httpClient = client

        self.eventLoop = httpClient.container.eventLoop
    }

    private func _followers(of screenName : String, nextCursor : Int64 = -1) throws -> Future<UserCursor>{
        logger.debug("Fetching followers of \(screenName) cursor \(nextCursor)")
        let res = httpClient.get("https://api.twitter.com/1.1/followers/list.json?screen_name=\(screenName)&cursor=\(nextCursor)", headers: ["authorization": authToken])
        return res.flatMap { res in
            return try res.content.decode(UserCursor.self, using: self.jsonDecoder)
        }
    }

    private func _followersFetcher(of screenName : String, nextCursor : Int64 = -1, users: Set<User> = []) throws -> Future<UserCursor> {
        return try _followers(of: screenName, nextCursor: nextCursor).flatMap {
            let newUsers = users.union($0.users)
            if $0.nextCursor > 0 {
                return try self._followersFetcher(of: screenName, nextCursor: $0.nextCursor, users: newUsers).map {$0}
            }
            return self.eventLoop.future(UserCursor(users: newUsers.map{$0}))
        }
    }

    func fetchFollwers(of screenName : String) throws -> Future<[User]> {
        return try _followersFetcher(of: screenName).map{$0.users}
    }
}

С Vapor и NIO очень важно оставаться на eventL oop все время. В приведенном выше примере _followersFetcher вызывает себя столько раз, сколько необходимо, чтобы получить всех пользователей, и только затем возвращает результат.

Вы могли бы переписать код, возможно, чтобы он выглядел более чистым / элегантным, но я думаю, что это единственный метод, доступный в ситуации, когда вы получаете следующий курсор только после запроса с предыдущим.

В случае, если у вас есть список курсоров заранее, вы можете просто использовать flatten

private func _followersFetcher(of screenName : String, cursors: [Int64]) throws -> Future<[User]> {
    var users: Set<User> = []
    return cursors.map {
        _followers(of: screenName, nextCursor: $0).map {
            users.union($0.users)
        }
    }.flatten(on: eventLoop).map { users.map { $0 } }
}
...