Swift Combine:将错误转化为发布者的输出

时间:2020-04-28 14:20:23

标签: swift combine

我正在尝试重构一些代码,目前我正在将Combine发布者与回调闭包混合在一起。

该代码向REST端点发出登录请求。在失败时,我希望函数的调用者接收错误有效载荷(挑战),在成功时,调用者无需执行任何操作。因此,这是一种奇怪的情况,其中呼叫者仅对该错误感兴趣。

目前,我正在这样做(简化代码):

import Foundation
import Combine

struct Challenge {
  let id: Int
}

struct MyError: Error {
  let challenge: Challenge?
}

class UserManager {
  @Published var user: String?

  private func doRequest(username: String, password: String) -> AnyPublisher<String, MyError> {
    // In the real world this does a request to a REST API, returning either a User object or an error containing a Challenge.
    return Fail(outputType: String.self, failure: MyError(challenge: Challenge(id: 1)))
      .eraseToAnyPublisher()
  }

  func login(username: String, password: String, next: @escaping (Challenge) -> ()) -> AnyCancellable {
    doRequest(username: username, password: password)
      .catch { error -> AnyPublisher<String, Never> in
        if let challenge = error.challenge {
          next(challenge)
        }
        return Empty().eraseToAnyPublisher()
      }
      .sink(
        receiveValue: { [weak self] user in
          self?.user = user
        }
      )
  }
}

let userManager = UserManager()
var anyCancellables = Set<AnyCancellable>()

userManager.login(username: "hello", password: "world") { challenge in
  print("received challenge!")
}
.store(in: &anyCancellables)

如您所见,login函数使用一个名为next的参数,发布者自己处理sink运算符内的值。将发布者与像这样的闭包结合起来感觉并不好,理想情况下,我希望login函数具有以下签名:

func login(username: String, password: String) -> AnyPublisher<Challenge, Never>

但是当我想编写这样的函数时,我遇到了一个问题,即我无法在sink函数本身中使用login,因为这会将返回类型转换为{{ 1}}。基本上,我想将错误转化为发布者的输出,并且还要处理AnyCancellable函数中的实际值。这怎么可能?

1 个答案:

答案 0 :(得分:0)

好吧,我想有时候要做的只是写下一个问题,以求得解决方案的方法?

func login(username: String, password: String) -> AnyPublisher<Challenge, Never> {
  doRequest(username: username, password: password)
    .compactMap {
      self.user = $0
      return nil
    }
    .catch { error -> AnyPublisher<Challenge, Never> in
      if let challenge = error.challenge {
        return Just(challenge).eraseToAnyPublisher()
      }
      return Empty().eraseToAnyPublisher()
    }
    .eraseToAnyPublisher()
}

不确定是否使用这样的compactMap来处理副作用确实是最好的方法,但是它确实有效。