如何使用Combine + Swift复制PromiseKit样式的链式异步流 [英] How to replicate PromiseKit-style chained async flow using Combine + Swift

查看:100
本文介绍了如何使用Combine + Swift复制PromiseKit样式的链式异步流的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Xcode 11 beta打破PK v7之前,我一直在项目中成功使用PromiseKit.为了减少外部依赖性,我决定取消PromiseKit.处理链式异步代码的最佳替代方法似乎是使用新的Combine框架的Futures.

I was using PromiseKit successfully in a project until Xcode 11 betas broke PK v7. In an effort to reduce external dependencies, I decided to scrap PromiseKit. The best replacement for handling chained async code seemed to be Futures using the new Combine framework.

我正在努力使用Combine来复制简单的PK语法

I am struggling to replicate the simple PK syntax using Combine

例如.简单的PromiseKit链式异步调用语法

getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.then{popToRootViewController}.catch{handleError(error)}

我了解:

async/await的Swift标准库实现将解决此问题( async/await尚不存在,尽管

我可以使用信号量(容易出错?)

flatMap可用于链接期货

flatMap can be used to chain Futures

我想要的异步代码应该能够按需调用,因为它涉及确保用户登录.我正在努力解决两个概念性问题.

The async code I'd like should be able to be called on demand, since it's involved with ensuring user is logged in. I'm wrestling with two conceptual problems.

  1. 如果我将Futures包装在一个方法中,并使用sink处理结果,则在sink调用订户之前,该方法似乎超出了范围.

  1. If I wrap Futures in a method, with sink to handle result, it seems that the method goes out of scope before subscriber is called by sink.

由于期货仅执行一次,因此我担心,如果多次调用该方法,我只会从第一次调用中得到旧的,陈旧的结果.要解决此问题,也许我会使用PassthroughSubject?这样可以按需调用发布服务器.

Since Futures execute only once, I worry that if I call the method multiple times I'll only get the old, stale, result from the first call. To work around this, maybe I would use a PassthroughSubject? This allows the Publisher to be called on demand.

问题:

  1. 我是否必须保留每个发布商和订阅者 调用方法
  2. 如何使用Swift标准库复制简单的链接异步,然后将其嵌入到swift实例方法中,我可以按需调用以从顶部重新启动链接的异步调用?
  1. Do I have to retain every publisher and subscriber outside of the calling method
  2. How can I replicate simple chained async using the Swift standard library and then embed this in a swift instance method I can call on-demand to restart the chained async calls from the top??

//how is this done using Combine?
func startSync() {
 getAccessCodeFromSyncProvider.then{accessCode in startSync(accessCode)}.catch{\\handle error here}
}

推荐答案

这不是您整个问题的真正答案,仅是有关如何开始使用Combine的部分.我将演示如何使用Combine框架链接两个异步操作:

This is not a real answer to your whole question — only to the part about how to get started with Combine. I'll demonstrate how to chain two asynchronous operations using the Combine framework:

    print("start")
    Future<Bool,Error> { promise in
        delay(3) {
            promise(.success(true))
        }
    }
    .handleEvents(receiveOutput: {_ in print("finished 1")})
    .flatMap {_ in
        Future<Bool,Error> { promise in
            delay(3) {
                promise(.success(true))
            }
        }
    }
    .handleEvents(receiveOutput: {_ in print("finished 2")})
    .sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
        .store(in:&self.storage) // storage is a persistent Set<AnyCancellable>

首先,关于持久性的问题的答案是:最终订阅者必须持久,而实现此目标的方法是使用.store方法.通常,如此处所示,您将具有Set<AnyCancellable>作为属性,并且只需调用.store作为管道中的最后一件事,即可将您的订户放置在其中.

First of all, the answer to your question about persistence is: the final subscriber must persist, and the way to do this is using the .store method. Typically you'll have a Set<AnyCancellable> as a property, as here, and you'll just call .store as the last thing in the pipeline to put your subscriber in there.

接下来,在此管道中,我使用.handleEvents只是为了给自己一些打印内容,以配合管道的前进.这些只是诊断,在实际的实现中不会存在.所有print语句纯粹是为了让我们可以谈论这里发生的事情.

Next, in this pipeline I'm using .handleEvents just to give myself some printout as the pipeline moves along. Those are just diagnostics and wouldn't exist in a real implementation. All the print statements are purely so we can talk about what's happening here.

那会发生什么?

start
finished 1 // 3 seconds later
finished 2 // 3 seconds later
done

所以您可以看到我们已经链接了两个异步操作,每个操作需要3秒钟.

So you can see we've chained two asynchronous operations, each of which takes 3 seconds.

我们是如何做到的?我们从Future开始,它在完成时必须调用其传入的promise方法,并使用Result作为完成处理程序.之后,我们使用.flatMap生成了另一个 Future并将其投入运行,再次执行相同的操作.

How did we do it? We started with a Future, which must call its incoming promise method with a Result as a completion handler when it finishes. After that, we used .flatMap to produce another Future and put it into operation, doing the same thing again.

所以结果不是很漂亮(例如PromiseKit),但是它是一连串的异步操作.

So the result is not beautiful (like PromiseKit) but it is a chain of async operations.

在Combine之前,我们可能已经使用某种Operation/OperationQueue依赖关系完成了此操作,该依赖关系可以正常工作,但PromiseKit的直接可读性更低.

Before Combine, we'd have probably have done this with some sort of Operation / OperationQueue dependency, which would work fine but would have even less of the direct legibility of PromiseKit.

说了这么多,这是一个稍微现实一些的重写:

Having said all that, here's a slightly more realistic rewrite:

var storage = Set<AnyCancellable>()
func async1(_ promise:@escaping (Result<Bool,Error>) -> Void) {
    delay(3) {
        print("async1")
        promise(.success(true))
    }
}
func async2(_ promise:@escaping (Result<Bool,Error>) -> Void) {
    delay(3) {
        print("async2")
        promise(.success(true))
    }
}
override func viewDidLoad() {
    print("start")
    Future<Bool,Error> { promise in
        self.async1(promise)
    }
    .flatMap {_ in
        Future<Bool,Error> { promise in
            self.async2(promise)
        }
    }
    .sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
        .store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}

如您所见,作为我们的未来发布者的想法只需传递promise回调即可;他们实际上不必一定是打电话给他们的人.因此,可以在任何地方调用promise回调,并且直到那时我们都不会继续.

As you can see, the idea that is our Future publishers simply have to pass on the promise callback; they don't actually have to be the ones who call them. A promise callback can thus be called anywhere, and we won't proceed until then.

因此,您可以轻松地看到如何用真实的异步操作替换人工的delay,该操作以某种方式拥有此promise回调,并在完成后可以调用它.同样,我的promise Result类型纯粹是人为的,但是您可以再次看到如何使用它们在管道中传达有意义的信息.当我说promise(.success(true))时,这会导致true弹出管道的末尾.我们在这里不理会,但这可能是某种彻头彻尾的有用价值,甚至可能是下一个未来.

You can thus readily see how to replace the artificial delay with a real asynchronous operation that somehow has hold of this promise callback and can call it when it completes. Also my promise Result types are purely artificial, but again you can see how they might be used to communicate something meaningful down the pipeline. When I say promise(.success(true)), that causes true to pop out the end of the pipeline; we are disregarding that here, but it could be instead a downright useful value of some sort, possibly even the next Future.

(还要注意,我们可以在链的任何位置插入.receive(on: DispatchQueue.main),以确保紧随其后的内容在主线程上开始.)

(Note also that we could insert .receive(on: DispatchQueue.main) at any point in the chain to ensure that what follows immediately is started on the main thread.)

我还想到,通过将Future发行商转移到常量中,我们可以使语法更整洁,也许更接近PromiseKit的可爱简单链.但是,如果这样做,则可能应将它们包装在Deferred发布者中,以防止过早评估.例如:

It also occurs to me that we could make the syntax neater, perhaps a little closer to PromiseKit's lovely simple chain, by moving our Future publishers off into constants. If you do that, though, you should probably wrap them in Deferred publishers to prevent premature evaluation. So for example:

var storage = Set<AnyCancellable>()
func async1(_ promise:@escaping (Result<Bool,Error>) -> Void) {
    delay(3) {
        print("async1")
        promise(.success(true))
    }
}
func async2(_ promise:@escaping (Result<Bool,Error>) -> Void) {
    delay(3) {
        print("async2")
        promise(.success(true))
    }
}
override func viewDidLoad() {
    print("start")
    let f1 = Deferred{Future<Bool,Error> { promise in
        self.async1(promise)
    }}
    let f2 = Deferred{Future<Bool,Error> { promise in
        self.async2(promise)
    }}
    // this is now extremely neat-looking
    f1.flatMap {_ in f2 }
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: {_ in}, receiveValue: {_ in print("done")})
        .store(in:&self.storage) // storage is a persistent Set<AnyCancellable>
}

这篇关于如何使用Combine + Swift复制PromiseKit样式的链式异步流的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆