URLSession dataTaskPublisher 在特定错误延迟后重试

Posted

技术标签:

【中文标题】URLSession dataTaskPublisher 在特定错误延迟后重试【英文标题】:URLSession dataTaskPublisher retry after delay on specific errors 【发布时间】:2020-05-13 18:52:02 【问题描述】:

我正在尝试重新发送针对特定类型错误的请求。假设对于超时错误,我想在延迟 3 秒后重试请求。显然,如果请求成功执行,我不希望有任何延迟。

我正在使用here建议的方法

var cancellables = Set<AnyCancellable>()

let url = URL(string: "https://www.apple.com")!
let sessionConfiguration = URLSessionConfiguration.default
sessionConfiguration.timeoutIntervalForRequest = 1
sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData

let publisher = URLSession(configuration: sessionConfiguration).dataTaskPublisher(for: url).share()

let head = publisher.print().tryCatch  error -> AnyPublisher<(data: Data, response: URLResponse), URLError> in
    switch error 
    case URLError.timedOut:
        print("I'm in URLError.timedOut case")
        return publisher.delay(for: 3, scheduler: DispatchQueue.main).eraseToAnyPublisher()

    default:
        print("I'm in default case")
        throw error
    
.retry(3)

head.map  data, response in
    return data
.sink(receiveCompletion: 
    print("completion \($0)")
, receiveValue: 
    print("value \($0)")
).store(in: &cancellables)

出于测试目的,我为 URLSession 设置了 1 秒的超时间隔,并限制了我的网络连接,预计当整个管道在大约 10 秒内完成时会看到 4 个失败的请求。但我实际上看到的只是一个失败的请求,并在一段时间后打印了一个失败的完成。对我来说,我似乎每三秒从tryCatch 运营商返回publisher,但由于某种原因,它不会碰巧发送新请求。

我错过了什么?这个问题有其他解决方案吗?

2020 年 14 月 5 日更新

我在tryCatch 之前添加了print() 运算符,并在闭包内添加了一些打印。我在控制台看到的是这个

receive subscription: (Multicast)
request unlimited
receive error: (URLError(_nsError: Error Domain=NSURLErrorDomain Code=-1001 "The request timed out."...))
I'm in URLError.timedOut case
receive subscription: (Multicast)
request unlimited
receive error: (URLError(_nsError: Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." ...))
I'm in URLError.timedOut case
receive subscription: (Multicast)
request unlimited
receive error: (URLError(_nsError: Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." ...))
I'm in URLError.timedOut case
receive subscription: (Multicast)
request unlimited
receive error: (URLError(_nsError: Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." ...))
I'm in URLError.timedOut case
completion failure(Foundation.URLError(_nsError: Error Domain=NSURLErrorDomain Code=-1001 "The request timed out."...))

但我在 Charles http 代理中看到的正是一个请求,即在重试之前发送的一个请求

【问题讨论】:

最终打印的完成错误是什么?看起来您在tryCatch 中遇到的错误可能与URLError.timedOut 不同。您是否尝试过删除 switch 并始终返回延迟的发布者(只是看看这是否有效) 感谢您的提问!我添加了一些打印,可能会有所帮助。对我来说,它看起来非常好,除了从 catch 返回发布者不会触发请求。我也尝试过简单的catch 而不是tryCatch 没有任何开关,结果是一样的 【参考方案1】:

我做了一些测试,你的问题似乎在这一行:

let publisher = URLSession(configuration: sessionConfiguration).dataTaskPublisher(for: url).share()

通过使用share(),您可以重播或共享数据任务发布者的结果。因此,虽然您的重试逻辑工作正常,但每次都会重播第一个请求的结果。如果您删除 share(),则会在 URLError.timedOut 的情况下发出新请求。

如果您需要重播/共享,那么您可以在 retry 之后添加,这样您创建的数据任务发布者就不会重播初始尝试的结果。

【讨论】:

正如我在问题中提到的,这个想法来自***.com/a/60637050/4933617,这就是我使用 share() 的原因。恐怕简单地删除 share() 不是一个解决方案,因为现在它在完成整个管道之前发出超过 4 个请求,但超时失败 有趣的是,每次尝试似乎都会触发两次请求。我确实想知道该答案中的解决方案如何/为什么有效,因为 share 没有意义,因为这会阻止该特定数据任务发布者进行相同的调用 我在周末做了一大堆修修补补,并想出了解决这个问题的办法。我把它写在我的博客上:donnywals.com/…

以上是关于URLSession dataTaskPublisher 在特定错误延迟后重试的主要内容,如果未能解决你的问题,请参考以下文章

Swift UrlSession 在 UrlSession 中不起作用

如何模拟 URLSession.DataTaskPublisher

URLSession 错误

未调用 URLSession 委托成功方法,但没有错误

URLSession - 下载远程目录

一个 URLSession 中的不同超时