使用Apple的新的Combine框架,我希望从列表中的每个元素发出多个请求。然后,我希望减少所有响应得到一个结果。基本上,我想从发布者列表转到拥有响应列表的单个发布者。
我尝试列出发布者列表,但是我不知道如何将列表缩小为一个发布者。而且我尝试过使发布者包含一个列表,但是我无法平面映射发布者列表。
请查看“ createIngredients”功能
func createIngredient(ingredient: Ingredient) -> AnyPublisher<CreateIngredientMutation.Data, Error> {
return apollo.performPub(mutation: CreateIngredientMutation(name: ingredient.name, optionalProduct: ingredient.productId, quantity: ingredient.quantity, unit: ingredient.unit))
.eraseToAnyPublisher()
}
func createIngredients(ingredients: [Ingredient]) -> AnyPublisher<[CreateIngredientMutation.Data], Error> {
// first attempt
let results = ingredients
.map(createIngredient)
// results = [AnyPublisher<CreateIngredientMutation.Data, Error>]
// second attempt
return Publishers.Just(ingredients)
.eraseToAnyPublisher()
.flatMap { (list: [Ingredient]) -> Publisher<[CreateIngredientMutation.Data], Error> in
return list.map(createIngredient) // [AnyPublisher<CreateIngredientMutation.Data, Error>]
}
}
Run Code Online (Sandbox Code Playgroud)
我不确定如何获取发布者数组并将其转换为包含数组的发布者。
类型“ [AnyPublisher]”的结果值与关闭结果类型“ Publisher”不符
我认为这Publishers.MergeMany可能会有所帮助。在您的示例中,您可以像这样使用它:
func createIngredients(ingredients: [Ingredient]) -> AnyPublisher<CreateIngredientMutation.Data, Error> {
let publishers = ingredients.map(createIngredient(ingredient:))
return Publishers.MergeMany(publishers).eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)
这将为您提供一个发布者,向您发送Output.
但是,如果您特别希望Output在所有发布者完成时一次性全部放入数组,则可以使用collect()with MergeMany:
func createIngredients(ingredients: [Ingredient]) -> AnyPublisher<[CreateIngredientMutation.Data], Error> {
let publishers = ingredients.map(createIngredient(ingredient:))
return Publishers.MergeMany(publishers).collect().eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)
如果您愿意,您可以将上述任何一个示例简化为一行,即:
func createIngredients(ingredients: [Ingredient]) -> AnyPublisher<CreateIngredientMutation.Data, Error> {
Publishers.MergeMany(ingredients.map(createIngredient(ingredient:))).eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)
您还可以定义自己的自定义merge()扩展方法Sequence并使用它来稍微简化代码:
extension Sequence where Element: Publisher {
func merge() -> Publishers.MergeMany<Element> {
Publishers.MergeMany(self)
}
}
func createIngredients(ingredients: [Ingredient]) -> AnyPublisher<CreateIngredientMutation.Data, Error> {
ingredients.map(createIngredient).merge().eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)
本质上,在您的特定情况下,您正在查看的是以下内容:
func createIngredients(ingredients: [Ingredient]) -> AnyPublisher<[CreateIngredientMutation.Data], Error> {
let publisherOfPublishers = Publishers.Sequence<[AnyPublisher<CreateIngredientMutation.Data, Error>], Error>(sequence: ingredients.map(createIngredient))
return publisherOfPublishers.flatMap { $0 }.collect().eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)
这将“收集”上游出版商产生的所有元素,并且一旦完成,就会产生一个包含所有结果的数组,并最终完成。
请记住,如果上游发布者之一失败(或产生一个以上的结果),则元素的数量可能与订阅者的数量不匹配,因此您可能需要其他运算符来缓解这种情况。
更通用的答案,可以使用EntwineTest框架对其进行测试:
import XCTest
import Combine
import EntwineTest
final class MyTests: XCTestCase {
func testCreateArrayFromArrayOfPublishers() {
typealias SimplePublisher = Publishers.Just<Int>
// we'll create our 'list of publishers' here
let publishers: [SimplePublisher] = [
.init(1),
.init(2),
.init(3),
]
// we'll turn our publishers into a sequence of
// publishers, a publisher of publishers if you will
let publisherOfPublishers = Publishers.Sequence<[SimplePublisher], Never>(sequence: publishers)
// we flatten our publisher of publishers into a single merged stream
// via `flatMap` then we `collect` all the results into a single array,
// and finally we return the resulting publisher
let finalPublisher = publisherOfPublishers.flatMap{ $0 }.collect()
// Let's test what we expect to happen, will happen.
// We'll create a scheduler to run our test on
let testScheduler = TestScheduler()
// Then we'll start a test. Our test will subscribe to our publisher
// at a virtual time of 200, and cancel the subscription at 900
let testableSubscriber = testScheduler.start { finalPublisher }
// we're expecting that, immediately upon subscription, our results will
// arrive. This is because we're using `just` type publishers which
// dispatch their contents as soon as they're subscribed to
XCTAssertEqual(testableSubscriber.sequence, [
(200, .subscription), // we're expecting to subscribe at 200
(200, .input([1, 2, 3])), // then receive an array of results immediately
(200, .completion(.finished)), // the `collect` operator finishes immediately after completion
])
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1518 次 |
| 最近记录: |