我如何才能从一个API调用的结果中分支出多个API调用,并在完成Combine之后将它们收集起来? [英] How can I branch out multiple API calls from the result of one API call and collect them after all are finished with Combine?

查看:45
本文介绍了我如何才能从一个API调用的结果中分支出多个API调用,并在完成Combine之后将它们收集起来?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,我有此API调用序列,在这里我获取了一个员工详细信息,然后获取了与该员工相关联的公司和项目详细信息.两次提取均完成后,我将两者合并并发布了一个fetchCompleted事件.我在下面隔离了相关代码.

So, I have this sequence of API calls, where I fetch a employee details, then fetch the company and project details that the employee is associated with. After both fetching are complete, I combine both and publish a fetchCompleted event. I've isolated the relevant code below.

func getUserDetails() -> AnyPublisher<UserDetails, Error>
func getCompanyDetails(user: UserDetails) -> AnyPublisher<CompanyDetails, Error>
func getProjectDetails(user: UserDetails) -> AnyPublisher<ProjectDetails, Error>

如果我这样做,

func getCompleteUserDetails() -> AnyPublisher<UserFetchState, Never> {

  let cvs = CurrentValueSubject<UserFetchState, Error>(.initial)

  let companyPublisher = getUserDetails()
    .flatMap { getCompanyDetails($0) }
  let projectPublisher = getUserDetails()
    .flatMap { getProjectDetails($0) }
  
  companyPublisher.combineLatest(projectPublisher)
    .sink { cvs.send(.fetchComplete) }
  return cvs.eraseToAnyPublisher()
}

getUserDetails()将被调用两次.我需要的是一次获取userDetails,然后将流分成两部分,将其映射以获取公司详细信息和项目详细信息,然后重新合并两者.有没有一种优雅的方法可以做到以下几点?

getUserDetails() will get called twice. What I need is fetch the userDetails once and with that, branch the stream into two, map it to fetch the company details and project details and re-combine both. Is there a elegant(flatter) way to do the following.

func getCompleteUserDetails() -> AnyPublisher<UserFetchState, Never> {

  let cvs = CurrentValueSubject<UserFetchState, Error>(.initial)

    getUserDetails()
      .sink {
        let companyPublisher = getCompanyDetails($0)
        let projectPublisher = getProjectDetails($0)
        
        companyPublisher.combineLatest(projectPublisher)
          .sink { cvs.send(.fetchComplete) }
      }
  return cvs.eraseToAnyPublisher()
}

推荐答案

Combine的整体思想是构造一个管道,数据在该管道中流动.实际上,向下流动的可能是值或完成,其中完成可能是失败(错误).所以:

The whole idea of Combine is that you construct a pipeline down which data flows. Actually what flows down can be a value or a completion, where a completion could be a failure (error). So:

  • 您不需要发出信号,表明管道已经产生了价值;该值到达管道的末端是 该信号.

  • You do not need to make a signal that the pipeline has produced its value; the arrival of that value at the end of the pipeline is that signal.

类似地,您不需要发出信号,表明管道的工作已经完成;已经产生了将要产生的所有值的发布者会自动产生完成信号,因此该完成到达管道的末端就是该信号.

Similarly, you do not need to make a signal that the pipeline's work has completed; a publisher that has produced all the values it is going to produce produces the completion signal automatically, so the arrival of that completion at the end of the pipeline is that signal.

毕竟,当您收到一封信时,邮局不会打电话给您,而是说:您有邮件."相反,邮递员递给您这封信.您不必告诉您已经收到一封信;您只需接收它即可.

After all, when you receive a letter, the post office doesn't call you up on the phone and say, "You've got mail." Rather, the postman hands you the letter. You don't need to be told you've received a letter; you simply receive it.

好的,让我们演示一下.理解您自己的管道的关键只是简单地跟踪在任何给定的关口沿途传递的价值类型.因此,让我们构建一个模型管道来完成您需要完成的事情.我将假定三种类型的值:

Okay, let's demonstrate. The key to understanding your own pipeline is simply to track what kind of value is traveling down it at any given juncture. So let's construct a model pipeline that does the sort of thing you need done. I will posit three types of value:

struct User {
}
struct Project {
}
struct Company {
}

我将想象有可能上网并获取所有这些信息:用户独立,以及基于用户中包含的信息的项目和公司.我将通过提供实用程序函数来模拟这种情况,这些函数会返回每种类型的信息的发布者;在现实生活中,这些可能是延期的期货,但是我将仅使用Just来简化事情:

And I will imagine that it is possible to go online and fetch all of that information: the User independently, and the Project and Company based on information contained in the User. I will simulate that by providing utility functions that return publishers for each type of information; in real life these would probably be deferred futures, but I will simply use Just to keep things simple:

func makeUserFetcherPublisher() -> AnyPublisher<User,Error> {
    Just(User()).setFailureType(to: Error.self).eraseToAnyPublisher()
}
func makeProjectFetcherPublisher(user:User) -> AnyPublisher<Project,Error> {
    Just(Project()).setFailureType(to: Error.self).eraseToAnyPublisher()
}
func makeCompanyFetcherPublisher(user:User) -> AnyPublisher<Company,Error> {
    Just(Company()).setFailureType(to: Error.self).eraseToAnyPublisher()
}

现在,让我们构建管道.我认为,我们的目标是产生 all 我们收集的信息(用户,项目和公司)作为管道中的最终价值.因此,我们的最终输出将是这三件事的一个 tuple .(在进行合并"操作时,成组是重要.在管道中传递元组非常普遍.)

Now then, let's construct our pipeline. I take it that our goal is to produce, as the final value in the pipeline, all the information we have collected: the User, the Project, and the Company. So our final output will be a tuple of those three things. (Tuples are important when you are doing Combine stuff. Passing a tuple down the pipeline is extremely common.)

好的,让我们开始吧.一开始没有任何内容,因此我们需要一个初始发布者来启动该过程.那将是我们的用户获取程序:

Okay, let's get started. In the beginning there is nothing, so we need an initial publisher to kick off the process. That will be our user fetcher:

let myWonderfulPipeline = self.makeUserFetcherPublisher()

该管道的末尾是一个用户.现在,我们希望将该用户提供给接下来的两个发布者,以获取相应的Project和Company.将发布者插入管道中间的方法是使用 flatMap .记住,我们的目标是产生所有信息的元组.所以:

What's coming out the end of that pipeline is a User. We now want to feed that User into the next two publishers, fetching the corresponding Project and Company. The way to insert a publisher into the middle of a pipeline is with flatMap. And remember, our goal is to produce the tuple of all our info. So:

let myWonderfulPipeline = self.makeUserFetcherPublisher()
    // at this point, the value is a User
    .flatMap { (user:User) -> AnyPublisher<(User,Project,Company), Error> in
        // ?
    }
    // at this point, the value is a tuple: (User,Project,Company)

那么在 flatMap 中,问号在哪里呢?好吧,我们必须产生一个 publisher ,它产生我们所承诺的元组.出色的元组发布者是Zip.我们的元组中有三个值,所以这是一个Zip3:

So what goes into flatMap, where the question mark is? Well, we must produce a publisher that produces the tuple we have promised. The tuple-making publisher par excellence is Zip. We have three values in our tuple, so this is a Zip3:

let myWonderfulPipeline = self.makeUserFetcherPublisher()
    .flatMap { (user:User) -> AnyPublisher<(User,Project,Company), Error> in
        // ?
        let result = Publishers.Zip3(/* ? */)
        return result.eraseToAnyPublisher()
    }

那我们要压缩什么?我们必须压缩发布者.好吧,我们知道其中两个发布者-他们是我们已经定义的发布者!

So what are we zipping? We must zip publishers. Well, we know two of those publishers — they are the publishers we have already defined!

let myWonderfulPipeline = self.makeUserFetcherPublisher()
    .flatMap { (user:User) -> AnyPublisher<(User,Project,Company), Error> in
        let pub1 = self.makeProjectFetcherPublisher(user: user)
        let pub2 = self.makeCompanyFetcherPublisher(user: user)
        // ?
        let result = Publishers.Zip3(/* ? */, pub1, pub2)
        return result.eraseToAnyPublisher()
    }

我们快完成了!缺少的插槽中有什么?请记住,它必须是发布者.我们的目标是什么?我们要传递从上游到达的非常相同的用户.那是什么发行商呢?只是!所以:

We're almost done! What goes in the missing slot? Remember, it must be a publisher. And what's our goal? We want to pass on the very same User that arrived from upstream. And what's the publisher that does that? It's Just! So:

let myWonderfulPipeline = self.makeUserFetcherPublisher()
    .flatMap { (user:User) -> AnyPublisher<(User,Project,Company), Error> in
        let pub1 = self.makeProjectFetcherPublisher(user: user)
        let pub2 = self.makeCompanyFetcherPublisher(user: user)
        let just = Just(user).setFailureType(to:Error.self)
        let result = Publishers.Zip3(just, pub1, pub2)
        return result.eraseToAnyPublisher()
    }

我们完成了.没有大惊小怪.这是一个生成(User,Project,Company)元组的管道.订阅此管道的任何人都不需要一些额外的信号.元组的到来是信号.现在,订户可以使用该信息进行操作.让我们创建订户:

And we're done. No muss no fuss. This is a pipeline that produces a (User,Project,Company) tuple. Whoever subscribes to this pipeline does not need some extra signal; the arrival of the tuple is the signal. And now the subscriber can do something with that info. Let's create the subscriber:

myWonderfulPipeline.sink {
    completion in
    if case .failure(let error) = completion {
        print("error:", error)
    }
} receiveValue: {
    user, project, company in
    print(user, project, company)
}.store(in: &self.storage)

我们没有做任何非常有趣的事情-我们只打印了元组的内容.但是您会看到,在现实生活中,订户现在可以对该数据进行一些有用的操作.

We didn't do anything very interesting — we simply printed the tuple contents. But you see, in real life the subscriber would now do something useful with that data.

这篇关于我如何才能从一个API调用的结果中分支出多个API调用,并在完成Combine之后将它们收集起来?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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