如何在同一链中的 dataTaskPublisher 请求中使用来自一个发布者的值?

How do I use the value from one publisher in a request for a dataTaskPublisher in the same chain?

我是合并的新手,正在尝试弄清楚如何链接 Publishers。我有一个发布者 returns 一个字符串值,我想用它来构建 URLRequest,然后将其传递给 DataTaskPublisher。任何有关正确语法的帮助将不胜感激!

示例代码:

struct ResultObject: Decodable {}

func getValueKey() -> AnyPublisher<String, Error> {
    return Just("Test")
        .setFailureType(to: Error.self)
        .eraseToAnyPublisher()
}

func performSearch(_ searchTerm: String) -> AnyPublisher<[ResultObject], Error> {
    return getValueKey().flatMap { valueKey in
        let request: URLRequest = URLRequest(url: URL(string: "http://www.test.com/\(valueKey)")!)
        return URLSession.shared.dataTaskPublisher(for: request)
            .map { [=10=].data }
            .decode(type: [ResultObject].self, decoder: JSONDecoder())
    }
    .eraseToAnyPublisher() /* Error: Type of expression is ambiguous without more context */
}

注意:我很不确定最后一个eraseToAnyPublisher

的位置

你有两个问题。

首先,您将 flatMap 的形式参数命名为 valueKey,但后来您尝试使用名称 value 来引用它。这些需要匹配。

其次,Swift 无法像您传递给 flatMap 的闭包那样推断出 multi-statement 函数的 return 类型。 (它 可以 将参数类型推断为发布者 return 由 getValueKey 编辑的 Output 类型。)

有两种一般方法可以解决此问题:

  • 更改您的代码,使闭包仅包含一个语句,
  • 明确给出 return 类型。

将其重写为单个语句的一种方法是使用 map 运算符将传入的 String 转换为 URLRequest,如下所示:

func performSearch(_ searchTerm: String) -> AnyPublisher<[ResultObject], Error> {
    return getValueKey()
        .map { URLRequest(url: URL(string: "http://www.test.com/\([=10=])")!) }
        .flatMap {
            return URLSession.shared.dataTaskPublisher(for: [=10=])
                .map { [=10=].data }
                .decode(type: [ResultObject].self, decoder: JSONDecoder())
        }
        .eraseToAnyPublisher()
}

我们还可以通过使用 mapErrorURLError 转换为 Error 然后将其他运算符移到 flatMap 之外来减少嵌套:

func performSearch(_ searchTerm: String) -> AnyPublisher<[ResultObject], Error> {
    return getValueKey()
        .map { URLRequest(url: URL(string: "http://www.test.com/\([=11=])")!) }
        .flatMap { URLSession.shared.dataTaskPublisher(for: [=11=]).mapError { [=11=] as Error } }
        .map { [=11=].data }
        .decode(type: [ResultObject].self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

我提到的另一种方法是使闭包的 return 类型显式化。这很棘手,因为 return 类型很复杂。您需要向右滚动才能看到全部内容:

func performSearch0(_ searchTerm: String) -> AnyPublisher<[ResultObject], Error> {
    return getValueKey().flatMap { value -> Publishers.Decode<Publishers.Map<URLSession.DataTaskPublisher, Data>, [ResultObject], JSONDecoder> in
        let request: URLRequest = URLRequest(url: URL(string: "http://www.test.com/\(value)")!)
        return URLSession.shared.dataTaskPublisher(for: request)
            .map { [=12=].data }
            .decode(type: [ResultObject].self, decoder: JSONDecoder())
    }
    .eraseToAnyPublisher()
}

所以我们在选择这种方案的时候,通常会想用eraseToAnyPublisher来简化类型:

func performSearch1(_ searchTerm: String) -> AnyPublisher<[ResultObject], Error> {
    return getValueKey().flatMap { value -> AnyPublisher<[ResultObject], Error> in
        let request: URLRequest = URLRequest(url: URL(string: "http://www.test.com/\(value)")!)
        return URLSession.shared.dataTaskPublisher(for: request)
            .map { [=13=].data }
            .decode(type: [ResultObject].self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
    .eraseToAnyPublisher() /* Error: Type of expression is ambiguous without more context */
}