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

问题描述 投票:0回答:3

如何在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)
    }
}

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

ios swift combine
3个回答
20
投票

据我了解,在 RxSwift 中使用

.asSingle
的原因是,当您订阅时,您的订阅者会收到一个
SingleEvent
,它可以是
.success(value)
.error(error)
。因此,您的订阅者不必担心收到
.completion
类型的事件,因为没有这样的事件。

Combine 中没有类似的功能。在Combine中,从订阅者的角度来看,

Future
只是另一种类型的
Publisher
,它可以发出输出值和
.finished
.failure(error)
。类型系统并不强制执行以下事实:
Future
永远不会发出
.finished

因此,没有任何编程理由专门返回

Future
。您可能会争辩说,返回
Future
记录了您始终返回恰好一个输出或失败的意图。但这不会改变您编写订阅者的方式。

此外,由于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?
            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))
            })
        }
    }
}

3
投票

不要将数据任务 publisher 转换为 Future,而是将 data 任务 转换为 Future。只需将 Future 包裹在对 URLSession 的

dataTask(...){...}.resume()
的调用中,问题就解决了。这正是未来的目的:将任何异步操作转变为发布者。


-2
投票

如何从函数返回 future

您需要将现有的发布者转换为

AnyPublisher<Value, Error>
,而不是尝试从函数返回“未来”。您可以使用
.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()
}
© www.soinside.com 2019 - 2024. All rights reserved.