Я думаю, что внутри 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 } }
}