Swift Combine - 按给定顺序执行任务
Swift Combine - perform tasks in given sequence
我正在尝试使用 Combine
框架创建网络层。我已经实现了一些功能,它们运行良好。其中一些 api 调用需要 access token
,并且存在获取和刷新此令牌的调用。现在我面临的问题是,我希望需要此令牌的函数在执行前自动尝试刷新它——仅当它需要刷新或出现错误时。现在我有这样的功能:
static func login(with parameters: CredentialsRequest) -> AnyPublisher<LoggedUser, RequestError>
static func refreshToken(_ token: Token) -> AnyPublisher<Token, RequestError>
static func activeGames(token: Token) -> AnyPublisher<[Game], RequestError>
令牌有一个属性:requiresRefresh: Bool
我怎样才能使函数 activeGames
仍然是 return 类型 AnyPublisher<[Game], RequestError>
,但以这样的方式工作,如果首先检查令牌是否需要刷新,如果需要, 它在刷新之前等待?
有点离题,但我建议使用 Future
而不是 AnyPublisher
,至少为了更好的语义,因为期货提供单一价值,就像网络电话一样。
一种方法是提升任何接收令牌作为参数的函数:
func authenticated<P: Publisher, T>(_ source: @escaping (Token) -> P) -> (Token) -> AnyPublisher<T, RequestError> where P.Output == T, P.Failure == RequestError {
return { token in
let first: AnyPublisher<Token, RequestError>
if token.requiresRefresh {
// token needs refresh, let's create the refresh operation
first = refreshToken(token)
} else {
// token doesn't need refresh, just forward it downstream
first = Result<Token, RequestError>.Publisher(token).eraseToAnyPublisher()
}
// now, the Publisher chain
return first
.flatMap(source)
.catch { (error) -> AnyPublisher<T, RequestError> in
// assuming this error corresponds to the invalid/expired token
if case .invalidToken = error {
// and in this case we redo the authentication and the actual request
return refreshToken(token).flatMap(source).eraseToAnyPublisher()
} else {
// report the error otherwise
return Fail<T, RequestError>(error: error).eraseToAnyPublisher()
}
}.eraseToAnyPublisher()
}
}
然后你会像这样使用它 authenticated(activeGames)(token)
,所以基本上调用提升函数的结果而不是直接调用函数。
参数多于令牌的函数,也可以通过上述函数的一些重载来解除:
// for token + another argument
func authenticated<P: Publisher, T, A1>(_ source: @escaping (Token, A1) -> P) -> (Token, A1) -> AnyPublisher<T, RequestError> where P.Output == T, P.Failure == RequestError {
return { token, arg1 in
let src: (A1) -> (Token) -> P = { arg1 in { token in source(token, arg1) } }
return authenticated(src(arg1))(token)
}
}
// for token + two other arguments
func authenticated<P: Publisher, T, A1, A2>(_ source: @escaping (Token, A1, A2) -> P) -> (Token, A1, A2) -> AnyPublisher<T, RequestError> where P.Output == T, P.Failure == RequestError {
return { token, arg1, arg2 in
let src: (A1, A2) -> (Token) -> P = { arg1, arg2 in { token in source(token, arg1, arg2) } }
return authenticated(src(arg1, arg2))(token)
}
}
一种更简单的解决方案将涉及对最低级别的函数进行操作,例如 URLSession
堆栈之上的函数,但制定此解决方案需要了解您的网络堆栈实现。
我正在尝试使用 Combine
框架创建网络层。我已经实现了一些功能,它们运行良好。其中一些 api 调用需要 access token
,并且存在获取和刷新此令牌的调用。现在我面临的问题是,我希望需要此令牌的函数在执行前自动尝试刷新它——仅当它需要刷新或出现错误时。现在我有这样的功能:
static func login(with parameters: CredentialsRequest) -> AnyPublisher<LoggedUser, RequestError>
static func refreshToken(_ token: Token) -> AnyPublisher<Token, RequestError>
static func activeGames(token: Token) -> AnyPublisher<[Game], RequestError>
令牌有一个属性:requiresRefresh: Bool
我怎样才能使函数 activeGames
仍然是 return 类型 AnyPublisher<[Game], RequestError>
,但以这样的方式工作,如果首先检查令牌是否需要刷新,如果需要, 它在刷新之前等待?
有点离题,但我建议使用 Future
而不是 AnyPublisher
,至少为了更好的语义,因为期货提供单一价值,就像网络电话一样。
一种方法是提升任何接收令牌作为参数的函数:
func authenticated<P: Publisher, T>(_ source: @escaping (Token) -> P) -> (Token) -> AnyPublisher<T, RequestError> where P.Output == T, P.Failure == RequestError {
return { token in
let first: AnyPublisher<Token, RequestError>
if token.requiresRefresh {
// token needs refresh, let's create the refresh operation
first = refreshToken(token)
} else {
// token doesn't need refresh, just forward it downstream
first = Result<Token, RequestError>.Publisher(token).eraseToAnyPublisher()
}
// now, the Publisher chain
return first
.flatMap(source)
.catch { (error) -> AnyPublisher<T, RequestError> in
// assuming this error corresponds to the invalid/expired token
if case .invalidToken = error {
// and in this case we redo the authentication and the actual request
return refreshToken(token).flatMap(source).eraseToAnyPublisher()
} else {
// report the error otherwise
return Fail<T, RequestError>(error: error).eraseToAnyPublisher()
}
}.eraseToAnyPublisher()
}
}
然后你会像这样使用它 authenticated(activeGames)(token)
,所以基本上调用提升函数的结果而不是直接调用函数。
参数多于令牌的函数,也可以通过上述函数的一些重载来解除:
// for token + another argument
func authenticated<P: Publisher, T, A1>(_ source: @escaping (Token, A1) -> P) -> (Token, A1) -> AnyPublisher<T, RequestError> where P.Output == T, P.Failure == RequestError {
return { token, arg1 in
let src: (A1) -> (Token) -> P = { arg1 in { token in source(token, arg1) } }
return authenticated(src(arg1))(token)
}
}
// for token + two other arguments
func authenticated<P: Publisher, T, A1, A2>(_ source: @escaping (Token, A1, A2) -> P) -> (Token, A1, A2) -> AnyPublisher<T, RequestError> where P.Output == T, P.Failure == RequestError {
return { token, arg1, arg2 in
let src: (A1, A2) -> (Token) -> P = { arg1, arg2 in { token in source(token, arg1, arg2) } }
return authenticated(src(arg1, arg2))(token)
}
}
一种更简单的解决方案将涉及对最低级别的函数进行操作,例如 URLSession
堆栈之上的函数,但制定此解决方案需要了解您的网络堆栈实现。