映射 Swift 将未来组合到另一个未来

Mapping Swift Combine Future to another Future

我有一个 returns 未来的方法:

func getItem(id: String) -> Future<MediaItem, Error> {
  return Future { promise in
    // alamofire async operation
  }
}

我想用在另一个方法中,将MediaItem转换为NSImage,这是一个同步操作。我希望简单地在原始 Future 上做一个 mapflatMap 但它创建了一个我无法擦除到 Future<NSImage, Error>.

的长 Publisher
func getImage(id: String) -> Future<NSImage, Error> {
  return getItem(id).map { mediaItem in
    // some sync operation to convert mediaItem to NSImage
    return convertToNSImage(mediaItem)  // this returns NSImage
  }
}

我收到以下错误:

Cannot convert return expression of type 'Publishers.Map<Future<MediaItem, Error>, NSImage>' to return type 'Future<NSImage, Error>'

我尝试使用 flatMap 但出现了类似的错误。我可以 eraseToAnyPublisher 但我认为这隐藏了 getImage(id: String returns 一个未来的事实。

我想我可以在将来包装 getImage 的主体,但这似乎不如链接和映射那么干净。欢迎任何建议。

您不能像那样使用 Combine 框架中的点点滴滴。您必须制作一个 管道 — 一个发布者、一些操作员和一个订阅者(您将其存储以便管道有机会 运行)。

 Publisher
     |
     V
 Operator
     |
     V
 Operator
     |
     V
 Subscriber (and store it)

所以,在这里,getItem 是一个生成你的 Publisher 的函数,一个 Future。所以你可以说

getItem (...)
    .map {...}
    ( maybe other operators )
    .sink {...} (or .assign(...))
    .store (...)

现在,未来(以及整个管道)将 运行 异步,结果将从管道的末端弹出,您可以用它做一些事情。

现在,您当然可以将 Future 和 Map 放在一起,然后 停止, 出售它们以便其他人 else 可以附加其他运营商和他们的订户。您现在已经组装了管道的起点,仅此而已。但是它的 type 不会是 Future;它将是 AnyPublisher<NSImage,Error>。这并没有错!

你总是可以将一个未来包裹在另一个未来中。与其将其映射为 Publisher,不如在将来订阅它的结果 return.

func mapping(futureToWrap: Future<MediaItem, Error>) -> Future<NSImage, Error> {
    var cancellable: AnyCancellable?
    return Future<String, Error> { promise in
        // cancellable is captured to assure the completion of the wrapped future
        cancellable = futureToWrap
            .sink { completion in
                if case .failure(let error) = completion {
                    promise(.failure(error))
                }
            } receiveValue: { value in
                promise(.success(convertToNSImage(mediaItem)))
            }
    }
}

这总是可以概括为

extension Publisher {
    func asFuture() -> Future<Output, Failure> {
        var cancellable: AnyCancellable?
        return Future<Output, Failure> { promise in
            // cancellable is captured to assure the completion of the wrapped future
            cancellable = self.sink { completion in
                    if case .failure(let error) = completion {
                        promise(.failure(error))
                    }
                } receiveValue: { value in
                    promise(.success(value))
                }
        }
    }
}

请注意,如果有问题的发布者是 class,它将在未来 returned 的关闭生命周期内保留。此外,作为未来,您只会获得发布的第一个值,之后未来将完成。

最后,简单的擦除到AnyPublisher就好了。如果您想确保只获得第一个值(类似于获得未来的唯一值),您可以执行以下操作:

getItem(id)
    .map(convertToNSImage)
    .eraseToAnyPublisher()
    .first()

结果类型 Publishers.First<AnyPublisher<Output, Failure>> 具有足够的表现力来传达永远只会收到一个结果,类似于 Future。您甚至可以为此定义一个类型别名(尽管在这一点上可能有点矫枉过正):

typealias AnyFirst<Output, Failure> = Publishers.First<AnyPublisher<Output, Failure>>