gpt4 book ai didi

json - RxSwift 可观察到的错误停止链 - 使用 Rx 的 Web 服务,如何恢复?

转载 作者:搜寻专家 更新时间:2023-10-31 19:27:46 25 4
gpt4 key购买 nike

显然我是 RxSwift 的新手,虽然我阅读了大量文档和演讲,但我认为我遗漏了一些基本概念。

在我的应用程序中,我有一个 RESTful 网络服务来加载各种资源,但网络服务的基本 url 在构建/启动时是未知的。相反,我有一个“URL 解析器”Web 服务,我可以使用我的应用程序包、版本和可能的环境(“生产”、“调试”或在应用程序调试设置中输入的任何自定义字符串)调用它来获取我随后使用的基本 URL为实际服务。

我的想法是,我将创建 2 项服务,一项用于 URL 解析器,一项用于为我提供资源的实际 Web 服务。 URL 解析器将有一个变量和一个 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 对此很有用,但它似乎永远杀死了所有订阅者。

我可以将订阅放在它自己的函数中和 Observer 的错误处理程序中,我可以显示一个警报,然后再次调用订阅函数,如下所示:

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 上唯一的其他观察者是我的另一个 Web 服务类,我想我也可以在那里实现相同样式的订阅函数.

我读到有人建议返回一个 Result 枚举,它有两种情况:实际结果 (.success(result: T)) 或错误 (.error(error: Error))。

那么在 Rx 中处理错误 Web 服务错误的更好方法是什么?我无法解决这个问题,我正在尝试 2 天来理解它。有什么想法或建议吗?

更新

我突然想到,我可以完全忽略来自 Web 服务调用的错误,而是将任何错误发布到一个全局“错误”变量,我的应用程序委托(delegate)可以观察到该变量以显示警报。 “错误”可以引用最初导致错误的函数,因此可以进行重试。我仍然很困惑,不确定我应该做什么。 :/

更新 2

我想我可能找到了可行的解决方案。由于我仍然是 Rx 和 RxSwift 的初学者,我很乐意接受改进建议。在编写实际代码时,我将调用链分为两部分:

  • 我进行网络服务调用的部分
  • 我点击按钮并处理网络服务结果的部分,无论是错误还是成功

在我单击按钮并处理结果的部分,我使用了 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 服务的调用从 cain 移到它们自己的变量中,这样我就可以随时重新调用它。当我现在返回“userObservable”并且在 Web 服务调用期间发生错误时,我可以在 catchError 中显示错误并为下一次重试返回相同的“userObservable”。

目前,这只能正确处理 Web 服务调用链中发生的错误,因此我认为我应该将按钮点击作为驱动程序。

最佳答案

好吧,对于来到这里的每个人来说,您可能对 Rx 世界应该如何工作缺乏理解或误解。我仍然觉得它有时会令人困惑,但我找到了比我在原始问题中发布的更好的解决方案。

在 Rx 中,一个错误“杀死”或更确切地说是完成链中的所有观察者,这实际上是一件好事。如果在 Web 服务调用中出现 API 错误等预期错误,您应该尝试在它们发生的地方处理它们,或者将它们视为预期值。

例如,您的观察者可以返回可选类型,而订阅者可以过滤值是否存在。如果 API 调用发生错误,则返回 nil。其他“错误处理程序”可以过滤 nil 值以向用户显示错误消息。

同样可行的是返回一个 Result 枚举,有两种情况:.success(value: T) 和 .error(error: Error)。您将错误视为可接受的结果,观察者负责检查它是否应该显示错误消息或成功结果值。

还有另一种选择,当然也不是最好的,但可以将您预计会失败的调用简单地嵌套在不应受到影响的调用用户中。在我的例子中,这是一个按钮点击,它会导致对网络服务的调用。

我原来帖子的“更新 2”将变成:

    signInButton.rx.tap.throttle(0.5, scheduler: MainScheduler.instance)
.subscribe(onNext: { [unowned self] () in
log.debug("Trying to sign user in. Presenting HUD")
self.hud = MBProgressHUD.present(withTitle: "Signing in...");
self.viewModel.signIn()
.subscribe(onNext: { [unowned self] user in
log.debug("User signed in successfully. Dismissing HUD")
self.hud?.dismiss()
}, onError: { [unowned self] error in
log.error("Failed to sign user in. Dismissing HUD and presenting error: \(error)")
self.hud?.dismiss()
self.handleError(error)
}).disposed(by: self.disposeBag)
}).disposed(by: self.disposeBag)

MVVM View 模型像这样调用网络服务:

func signIn() -> Observable<User> {
log.debug("HUD presented. Loading BaseURL to sign in User")
return URLService.getBaseUrl(environment: UserDefaults.standard.environment)
.flatMap { [unowned self] baseURL -> Observable<BaseURL> in
log.debug("BaseURL loaded. Checking if special env is used.")
if let specialEnv = baseURL.users[self.username.value] {
log.debug("Special env is used. Reloading BaseURL")
UserDefaults.standard.environment = specialEnv
return URLService.getBaseUrl(environment: specialEnv)
} else {
log.debug("Current env is used. Returning BaseURL")
return Observable.just(baseURL)
}
}
.flatMap { [unowned self] baseURL -> Observable<User> in
log.debug("BaseURL to use is: \(baseURL.url). Now signing in User.")
let getUser = UserService.getUser(baseURL: baseURL.url, email: self.username.value, password: self.password.value).share()
getUser.subscribe(onError: { error in
UserDefaults.standard.environment = nil
}).disposed(by: self.disposeBag)
return getUser
}
.map{ user in
UserDefaults.standard.accessToken = user.accessToken
UserDefaults.standard.tokenType = user.tokenType
return user
}
}

首先我想在按下按钮时只调用 View 模型的 signIn() 函数,但由于 View 模型中不应该有 UI 代码,我认为呈现和关闭 HUD 是 ViewController 的职责。

我认为这个设计现在非常可靠。按钮观察者永远不会完成,并且可以永远继续发送事件。早些时候,如果有第二个错误,可能会发生按钮观察者死了,我的日志显示 userObservable 被执行了两次,这也一定不会发生。

我只是想知道是否有比嵌套订阅者更好的方法。

关于json - RxSwift 可观察到的错误停止链 - 使用 Rx 的 Web 服务,如何恢复?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46988049/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com