显然我是RxSwift的新手,虽然我消耗了大量文档和演讲,但我想我缺少一些基本概念。RxSwift可观察错误停止链 - 带Rx的Web服务,如何恢复?
在我的应用程序中,我有一个REST风格的Web服务来加载各种资源,但Web服务的基础URL在构建/开始时未知。相反,我有一个“URL解析器”Web服务,我可以使用我的应用程序包,版本和可能的环境(“生产”,“调试”或在应用程序调试设置中输入的任何自定义字符串)调用以获取基础URL,然后使用为实际的服务。
我的想法是,我会创建2个服务,一个用于URL解析器,另一个用于实际的Web服务,它为我提供了资源。 URL解析器将有一个Variable和一个Observable。我使用该变量来表示需要通过对URL解析器的Web服务调用刷新基础URL。我通过观察变量并仅对真实值进行过滤来实现此目的。服务类中的函数将变量值设置为true(最初为false),并在已过滤变量的观察者中,在另一个Observable中进行Web服务调用(此示例使用虚拟JSON Web服务):
import Foundation
import RxSwift
import Alamofire
struct BaseURL: Codable {
let title: String
}
struct URLService {
private static var counter = 0
private static let urlVariable: Variable<Bool> = Variable(false)
static let urlObservable: Observable<BaseURL> = urlVariable.asObservable()
.filter { counter += 1; return $0 }
.flatMap { _ in
return Observable.create { observer in
let url = counter < 5 ? "https://jsonplaceholder.typicode.com/posts" : ""
let requestReference = Alamofire.request(url).responseJSON { response in
do {
let items = try JSONDecoder().decode([BaseURL].self, from: response.data!)
observer.onNext(items[0])
} catch {
observer.onError(error)
}
}
return Disposables.create() {
requestReference.cancel()
}
}
}
static func getBaseUrl() {
urlVariable.value = true;
}
static func reset() {
counter = 0;
}
}
现在的问题是,有时可能会发生Web服务调用失败,我需要向用户显示错误,以便重试。我认为onError对此有用,但它似乎永远杀死所有用户。
我可以把订阅在它自己的功能和观察者的错误处理程序中,我可以显示一个警告,然后再次调用订阅功能,像这样:
func subscribe() {
URLService.urlObservable.subscribe(onNext: { (baseURL) in
let alert = UIAlertController(title: "Success in Web Service", message: "Base URL is \(baseURL.title)", preferredStyle: .alert)
let actionYes = UIAlertAction(title: "Try again!", style: .default, handler: { action in
URLService.getBaseUrl()
})
alert.addAction(actionYes)
DispatchQueue.main.async {
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: true, completion: nil)
}
}, onError: { error in
let alert = UIAlertController(title: "Error in Web Service", message: "Something went wrong: \(error.localizedDescription)", preferredStyle: .alert)
let actionYes = UIAlertAction(title: "Yes", style: .default, handler: { action in
URLService.reset()
self.subscribe()
})
alert.addAction(actionYes)
DispatchQueue.main.async {
VesselService.reset()
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alert, animated: true, completion: nil)
}
}).disposed(by: disposeBag)
}
然后在我的AppDelegate我会调用
subscribe()
URLService.getBaseUrl()
的问题是,所有其他观察员丧命上的错误,以及但是因为在URLService.urlObservable的唯一的其他观察者是我的其他网络服务类,我想我可以实现同样的风格认购功能也在那里。
我读过一些人建议返回一个Result枚举,它有两种情况:实际结果(.success(result:T))或错误(.error(error:Error))。
那么,在Rx中处理错误Web服务错误的更好方法是什么?我不能把这个问题包裹起来,我想了解2天。任何想法或建议?
更新
它只是来到我的脑海,我可以忽略来自Web服务的错误调用的任何错误完全,而是张贴到一个全球性的“错误”的变量,我的应用程序代理可以观察到显示警报。 “错误”可以引用最初导致它的功能,因此可以进行重试。我仍然感到困惑,不知道该怎么做。 :/
更新2
我想我可能找到了一个工作方案。由于我还是Rx和RxSwift的初学者,我很乐意接受改进建议。当我写实际的代码,我分裂我的调用链分为两个部分:
- ,我让Web服务调用
- ,我点击一个按钮,处理Web服务的结果的一部分,部分,无论是错误还是成功
在我单击按钮并处理结果的部分中,我使用catchError并按照注释中的建议重试。代码如下所示:
let userObservable = URLService
.getBaseUrl(environment: UserDefaults.standard.environment) //Get base url from web service 1
.flatMap({ [unowned self] baseURL -> Observable<User> in
UserService.getUser(baseURL: baseURL,
email: self.usernameTextField.text!,
password: self.passwordTextField.text!) //Get user from web service 2 using the base url from webservice 1
})
signInButton
.rx
.tap
.throttle(0.5, scheduler: MainScheduler.instance)
.flatMap({ [unowned self]() -> Observable<()> in
Observable.create { observable in
let hud = MBProgressHUD.present(withTitle: "Signing in...");
self.hud = hud
observable.onNext(())
return Disposables.create {
hud?.dismiss()
}
}
})
.flatMap({() -> Observable<User> in
return userObservable
})
.catchError({ [unowned self] error -> Observable<User> in
self.hud?.dismiss()
self.handleError(error)
return userObservable
})
.retry()
.subscribe(onNext: { [unowned self] (user) in
UserDefaults.standard.accessToken = user.accessToken
UserDefaults.standard.tokenType = user.tokenType
self.hud?.dismiss()
})
.disposed(by: disposeBag)
诀窍是移动调用两个Web服务了该隐到自己的变量,所以我可以在任何时候重新调用它。当我现在返回“userObservable”并在Web服务调用期间发生错误时,我可以在catchError中显示错误,并为下一次重试返回相同的“userObservable”。
目前这只能正确处理错误,当他们发生在Web服务调用链,所以我认为我应该让按钮点击驱动程序。
“问题是所有其他观察者都因为错误而死亡” - 这可能是一个问题,但它是Rx设计的方式。一个可观察的**可能有零个或多个OnNext,并且可能只有一个OnError或OnCompleted,此时观察结束,并且不能返回任何更多值**。 – Enigmativity
您通常会使用带**重试**的** catch **运算符来捕获错误并重试observables。 – Enigmativity
啊,我会看看并重试。谢谢。我知道这是设计,所以我想知道这个:我可以在我的Web服务中创建1个观察者。然后,我可以订阅观察员,转换其数据并仅在成功时将其发送给我的变量。然后,我可以创建第二个类似的转换,我将返回。只有在转换成功的情况下,变量才会被更新,而返回会将错误升级到我的UI,因此用户可以处理错误。这是一种有效的方法吗? – xxtesaxx