在 Swift Combine 中按顺序将 Publisher 映射到另一个发布者,并单独(或跳过)处理每个步骤的错误?

Map Publisher to another publishers sequentially and handle errors for each step separately (or skip) in Swift Combine?

提问人:Gargo 提问时间:6/16/2023 更新时间:6/16/2023 访问量:222

问:

法典:

import Combine

func login() -> Future<Token, Error> { ... }
func updateImage() -> Future<Status, Never> { ... }
func getProfile() -> Future<Profile, Error> { ... }

我需要执行如下操作(顺序操作):

login()
.catch { error in
  ... //handle error
}
.flatMap { token in
  ...//handle login results
  return updateImage()
}
.catch { _ in
  ... //skip error
}
.flatMap { 
  ... //handle updateImage results
  return getProfile()
}
.sink(...) //handle getProfile results and errors

问题是内部有误导性的类型和 .CombineflatMapcatch

尝试返回捕获块内部:Empty

return Empty<String, CustomError>(completeImmediately: true)
                    .eraseToAnyPublisher()

但我不明白它是否停止在部分中产生错误。对于我的任务来说,这是正确的方法吗?sink

快速 错误处理 Combine FlatMap Sink

评论

0赞 Sweeper 6/16/2023
“问题是 Combine 在 flatMap 和 catch 中具有误导性类型。”你在谈论什么?什么是“误导类型”?为什么这是一个问题?它会产生任何错误吗?
0赞 matt 6/16/2023
这可能会有所帮助: apeth.com/UnderstandingCombine/tricksandtips.html
0赞 Gargo 6/16/2023
@Sweeper/没关系。 - 如何停止执行或继续使用其他类型,如void?官方文档显示了如何使用将错误映射到仅由发布者指定的类型的值mapflatMapcatchJust
0赞 Gargo 6/16/2023
@matt感谢您提供链接,但它不包含有关如何处理错误的任何信息。
0赞 matt 6/16/2023
完全不清楚你的问题是什么,所以我不得不猜测。我正在解决你的“误导类型”。捕获页面是 apeth.com/UnderstandingCombine/operators/operatorsErrorHandlers/...。我不认为你需要用手来浏览一本书,所以我就留给你了。只需单击一些链接,直到您了解您想知道的内容。

答:

-1赞 Dávid Pásztor 6/16/2023 #1

如果你正在使用 s,并且想要将多个一次性异步调用彼此链接,那么使用方法比使用 Combine 要好得多。Futureasync

不幸的是,Combine 没有类型级支持只发出 1x 然后总是完成的 s。 尝试实现此目的,但是一旦将任何运算符应用于 ,就会得到一个 ,因此无法构建一个 Combine 管道来保证其每个步骤只能发出 1 倍。PublisherFutureFuturePublisher

另一方面,方法实现了这个确切的目标。async

func login() async throws -> Token { ... }
func updateImage() async -> Status { ... }
func getProfile() async throws -> Profile { ... }

do {
  let token: Token
  do {
    token = try await login() 
  } catch {
    token = // assign a default value
  }
  let status = try await updateImage()
  let profile = try await getProfile()
} catch {
  // handle profile errors
}

评论

0赞 Sweeper 6/16/2023
虽然我同意异步方法更合适,但我不同意“所以你不能构建一个保证其每个步骤只能发出 1 倍的 Combine 管道”每个 Combine 运算符都记录在案并执行非常具体的事情。为什么不能保证?静态分析一个人使用过的运算符,在某些情况下会有保证。
0赞 Dávid Pásztor 6/16/2023
@Sweeper类型系统中内置的文档和编译时保证是截然不同的东西。再次使用第三方工具进行静态分析,与语言级支持截然不同。
0赞 Sweeper 6/16/2023 #2

如果要链接多个这些独立的 s,并在每个步骤中处理错误,可以遵循以下模式:Future

future().map { result in
    // handle the future's result
    // this implicitly returns Void, turning it into a publisher of Void
}
.catch { error in
    // handle error...

    // in the case of an error,
    // if you want the pipeline to continue, return Just(())
    // if you want the pipeline to stop, return Empty()
}

其中每个都是发布一个值或根本不发布值的发布者。然后,您可以将其中的多个链接在一起:()flatMap

let cancellable = login().map { token in
    // handle login result...
    return ()
}
.catch { error in
    // handle login error...
    return Just(())
}
.flatMap { _ in
    updateImage().map { status in
        // handle updateImage results...
    }
    // no need for .catch here because updateImage doesn't fail
}
.flatMap { _ in
    getProfile().map { profile in
        // handle getProfile results...
    }.catch { error in
        // handle getProfile errors...
        return Just(())
    }
}.sink { completion in
    // handle completion
} receiveValue: { _ in
    // you will only recieve a () here
}

为了帮助编译器更快地找出类型,甚至根本不知道类型,您应该添加显式返回类型和/或在适当的情况下。eraseToAnyPublisher()

正如 Dávid Pásztor 的回答所说,如果 and 等是方法,则这种链接直接内置于语言中。您可以像编写顺序语句一样编写“链”。loginasync

func login() async throws -> Token { ... }
func updateImage() async -> Status { ... }
func getProfile() async throws -> Profile { ... }

func asyncChain() async {
    do {
        let token = try await login()
        // handle login results...
    } catch {
        // handle login error...
    }
    
    let status = await updateImage()
    // handle updateImage results...
    
    do {
        let profile = try await getProfile()
        // handle getProfile results...
    } catch {
        // handle getProfile error...
    }
}

评论

0赞 Gargo 6/16/2023
它不适合我,但至少在您的帮助下,我在代码中发现了一个错误 - 在之前,应该返回一个与前面的链步骤中获得的发布者具有相同值类型的发布者。catchflatMapcatch