我使用的是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
的每次呼叫返回?
这是我目前掌握的情况,但我很茫然。我觉得我的方法不对。
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)
}
}
我认为在 twitter
你可以私下 _followersFetcher
方法,该方法将调用 _followers
直至 -1
游标,以及公共 fetchFollowers
方法,它将与fetcher进行交易,类似这样。
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来说,始终保持在eventLoop上是非常重要的。在上面的例子中 _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 } }
}