使用 Vapor 迭代调用返回 Future 的方法

Posted

技术标签:

【中文标题】使用 Vapor 迭代调用返回 Future 的方法【英文标题】:Using Vapor to iteratively call a method the returns a Future 【发布时间】:2020-05-08 03:14:34 【问题描述】:

我正在使用 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 来迭代调用它,直到我访问了光标的所有页面,同时累积从每个调用返回的Users?

这是我到目前为止所拥有的,但我不知所措。我觉得我离题了。

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】:

我认为在 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  

【讨论】:

谢谢!这样可行!我希望你不介意,但我用整个工作客户端替换了你的解决方案草图,以供未来的读者阅读。

以上是关于使用 Vapor 迭代调用返回 Future 的方法的主要内容,如果未能解决你的问题,请参考以下文章

Vapor 3 Swift 4 如何制作计时器

XCTest 中未保留经过身份验证的 Vapor 会话

Vapor Swift - 从两个模型中获取数据

找不到在 Vapor 3 中使连接查询结果可编码的方法

从数据库加载数据并将其加载到 Vapor 3 中的视图的正确方法?

在同步调用中返回 Future 的结果