将 URLSession.DataTaskPublisher 转换为未来发布者

Convert URLSession.DataTaskPublisher to Future publisher

如何在 Combine 框架中将 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)
    }
}

有什么简单的方法可以做到这一点吗?

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

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

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

因此,没有编程理由专门 return a Future。您可能会争辩说 returning a Future 记录了您总是 return 只输出一个输出或失败的意图。但它不会改变您编写订阅者的方式。

此外,由于 Combine 大量使用泛型,一旦您想将任何运算符应用于 Future,您就没有未来了。如果您将 map 应用于某些 Future<V, E>,您将得到 Map<Future<V, E>, V2>,并且对于所有其他运算符都类似。这些类型很快就会失控,并掩盖了底部有一个 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 [=10=] {
                    case .failure(let error):
                        promise(.failure(error))
                    case .finished:
                        // WHAT DO WE DO HERE???
                        fatalError()
                    }
            },
                receiveValue: {
                    ticket?.cancel()
                    ticket = nil
                    promise(.success([=10=]))
            })
        }
    }
}

如何return 函数的未来

您需要将现有发布者转换为 AnyPublisher<Value, Error>,而不是尝试从函数 return 'future'。您可以使用 .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()
}