【问题标题】:Convert URLSession.DataTaskPublisher to Future publisher将 URLSession.DataTaskPublisher 转换为 Future 发布者
【发布时间】:2020-02-27 07:35:39
【问题描述】:

如何在组合框架中将URLSession.DataTaskPublisher 转换为Future。 在我看来,Future 发布者在这里更合适,因为调用只能发出一个响应并最终失败。

在 RxSwift 中有类似 asSingle 的辅助方法。

我已经使用以下方法实现了这种转换,但不知道这是否是最好的方法。

        return Future<ResponseType, Error>.init { (observer) in
        self.urlSession.dataTaskPublisher(for: urlRequest)
            .tryMap { (object) -> Data in
            //......
            }
            .receive(on: RunLoop.main)
            .sink(receiveCompletion: { (completion) in
                if case let .failure(error) = completion {
                    observer(.failure(error))
                }
            }) { (response) in
                observer(.success(response))
            }.store(in: &self.cancellable)
    }
}

有什么简单的方法吗?

【问题讨论】:

  • URLSession.DataTaskPublisher 还保证它只能发出 1 个值或失败,所以我真的看不出有任何理由将其转换为 Future 会产生任何真正的好处。
  • 这个问题不是“基于意见的”。这个想法是否好是基于意见的,但问题不在于它是否是一个好主意。它在问如何编写代码。
  • @robmayoff 我没有投票结束,但不是。它呈现一些代码并询问它是“最好的”并且是“简单”的方式。这些都是基于意见的标志。
  • “在 RxSwift 中有像 asSingle 这样的辅助方法?”是一个事实问题。 “有什么简单的方法可以做到这一点吗?”是基于意见的,但我认为最简单的是我们可以找到广泛的共识。耸耸肩。

标签: ios swift combine


【解决方案1】:

据我了解,在 RxSwift 中使用 .asSingle 的原因是,当您订阅时,您的订阅者会收到一个 SingleEvent,它是 .success(value).error(error)。因此,您的订阅者不必担心会收到 .completion 类型的事件,因为没有。

Combine 中没有等价物。在Combine 中,从订阅者的角度来看,Future 只是另一种Publisher,它可以发出输出值和.finished.failure(error)。类型系统不会强制执行 Future 永远不会发出 .finished 的事实。

因此,没有编程理由专门返回Future。您可能会争辩说,返回 Future 记录了您始终返回一个输出或失败的意图。但这不会改变您编写订阅者的方式。

此外,由于Combine 大量使用泛型,只要您想将任何运算符应用于Future,您就没有未来了。如果你将map 应用到一些Future&lt;V, E&gt;,你会得到一个Map&lt;Future&lt;V, E&gt;, V2&gt;,并且对于其他所有的操作符都是类似的。这些类型很快就会失控,并掩盖了底部有一个Future 的事实。

如果你真的想要,你可以实现你自己的操作符将任何Publisher 转换为Future。但是你必须决定如果上游发出 .finished 怎么办,因为 Future 不能发出 .finished

extension Publisher {
    func asFuture() -> Future<Output, Failure> {
        return Future { promise in
            var ticket: AnyCancellable? = nil
            ticket = self.sink(
                receiveCompletion: {
                    ticket?.cancel()
                    ticket = nil
                    switch $0 {
                    case .failure(let error):
                        promise(.failure(error))
                    case .finished:
                        // WHAT DO WE DO HERE???
                        fatalError()
                    }
            },
                receiveValue: {
                    ticket?.cancel()
                    ticket = nil
                    promise(.success($0))
            })
        }
    }
}

【讨论】:

  • "没有程序化的理由来专门返回 Future" asFuture 运算符可能非常有用(并且应该内置 imo)。
  • 关于“我们在这里做什么”,我认为值得注意的是,这与一般的 Futures 没有什么不同。在从不发送值或错误的发布者上调用 asFuture 是程序员错误,就像使用从不调用 promise 的闭包创建 Future 是程序员错误一样。
  • 如果承诺得到履行,Future 可以发出 .finished。我认为在您的代码中,它不会发出 .finished ,因为您在接收值时取消了链。如果下游链被取消,这也不起作用。由于上游链由于内部sink而被保留,即使下游链已经取消,它仍然继续执行上链。
【解决方案2】:

不是将数据任务 publisher 转换为 Future,而是将 数据任务 转换为 Future。只需将 Future 包裹在对 URLSession 的 dataTask(...){...}.resume() 的调用中,问题就解决了。这正是未来的目的:将任何异步操作转变为发布者。

【讨论】:

    【解决方案3】:

    如何从函数返回未来

    您需要将现有发布者转换为AnyPublisher&lt;Value, Error&gt;,而不是尝试从函数返回“未来”。您可以使用 .eraseToAnyPublisher() 运算符来执行此操作。

    func getUser() -> AnyPublisher<User, Error> {
        URLSession.shared.dataTaskPublisher(for: request)
            .tryMap { output -> Data in
                // handle response
                return output.data
            }
            .decode(type: User.self, decoder: JSONDecoder())
            .mapError { error in
                // handle error
                return error
            }
            .eraseToAnyPublisher()
    }
    

    【讨论】:

      猜你喜欢
      • 2017-01-27
      • 2015-07-30
      • 2016-05-04
      • 1970-01-01
      • 2019-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-28
      相关资源
      最近更新 更多