I'm trying to tame some callback hell pyramid of doom code using PromiseKit.
To do this I wrap my asynchronous code in promises, but depending on how I return dependent promises, I encounter problems. If I unwrap the promise and fulfill/reject then all is well, though more verbose than I'd like. If I return a new dependent promise, then I get an early allocation and the promises are silently broken.
I realise this might not be idiomatic PromiseKit, which appears to be
{ a }.then { b }.then { c } // in temporal order, take that, callbacks!
but as part of this work it's convenient for me to refactor with functions Promise<A> -> Promise<B>, and I don't understand why I must unwrap at every step. Does anyone know?
Here is some simplified code that reproduces the problem. Try to imagine that there's a good reason that badStringFromInt can't fulfil immediately.
func badStringFromInt(_ intPromise: Promise<Int>) -> Promise<String> {
return Promise { _, reject in
intPromise.then { i -> Promise<String> in
return Promise { fulfill, _ in
fulfill("\(i)")
}
}.catch { error in
reject(error)
}
}
}
func goodStringFromInt(_ intPromise: Promise<Int>) -> Promise<String> {
return Promise { fulfill, reject in
intPromise.then { i in
fulfill("\(i)")
}.catch { error in
reject(error)
}
}
}
func getInt() -> Promise<Int> {
return Promise{ fulfill, reject in
fulfill(5)
}
}
func doIt() {
// "then" never called, and this is logged:
// PromiseKit: Pending Promise deallocated! This is usually a bug
badStringFromInt(getInt()).then { s in
print("bad string is :" + s)
}.catch { error in
print("error: \(error)")
}
// all good!
goodStringFromInt(getInt()).then { s in
print("good string is :" + s)
}.catch { error in
print("error: \(error)")
}
}
I must be missing something. Why not just add on to the chain? What does creating the intermediate promise do for you?
betterStringFromInt waits for intPromise to fulfill, then returns a string promise.
func betterStringFromInt(_ intPromise: Promise<Int>) -> Promise<String> {
return intPromise.then { i in
DispatchQueue.main.promise {
return "\(i)"
}
}
}
Related
I am trying to chain some API calls and I think I am confusing some concepts. Would love some clarification & code samples.
I have implemented these functions...
func promiseFetchPayments(for accountId: String) -> Promise <[OperationResponse]> {
return Promise <[OperationResponse]> { seal in
payments(for: accountId) { (records, error) in
if let recs = records {
seal.resolve(.fulfilled(recs))
return
}
if let e = error {
seal.reject(e)
return
}
}
}
}
and
func payments(for accountId: String, completion: #escaping (_ records: [OperationResponse]?, _ error: Error?) -> Void) {
stellar.payments.getPayments(
forAccount: accountId,
order: Order.descending,
limit: 10
) { response in
switch response {
case .success(let paymentsResponse):
DispatchQueue.main.async {
completion(paymentsResponse.records, nil)
}
case .failure(let error):
DispatchQueue.main.async {
completion(nil, error)
}
}
}
}
I am trying to use it like so:
firstly {
promiseFetchPayments(for: "XXX")
}.done { records in
print(records)
} .catch { error in
print(error)
}
Now this actually ^^^ works OK!!!! My problem is I want to be able to change done to then and be able to chain another function / response or many more.
But the error I keep getting is:
Cannot conform to Thenable.
I am looking for something very similar to this (I know the syntax isn't right just logically follow the chain....
firstly {
stellar.promiseFetchPayments(for: "")
}.done { records in
print(records)
}.then {
// call some other method
}.done { data in
// more data
}.catch { error in
print(error)
}
Is this actually possible? Can't seem to get any tutorials on the interwebs to compile. Seems Swift compiler really doesn't like PMK syntax or something.
Any ideas?
The problem is because you're chaining off of a done, which doesn't like that you're trying to then do a call to then off of that.
Instead, you'll want to save the promise and use it for the later calls. You can do something like this:
let promise = firstly {
stellar.promiseFetchPayments(for: "")
}
promise.done { records in
print(records)
}
promise.then {
// call some other method
}.done { data in
// more data
}.catch { error in
print(error)
}
You can even return that promise from a method to use in other places, or pass it around to another method.
I have a custom pipeline where I want to have 3 retry attempt for some error codes which are recoverable plus I want to add some short delay for the recoverable error. Anyone has an idea how I can do it?
func createRequest(for message: Message) -> AnyPublisher<ResponseMessage, Error> {
Future<ResponseMessage, Error> { promise in
.....
}
.tryCatch({ error -> AnyPublisher<ResponseMessage, Error> in
// If error is a recoverable error retry, otherwise fail directly
if case let MessageBusError.messageError(responseError) = error {
if responseError.isRecoverable {
// Make a next attempt only for recoverable error
throw error
}
}
//Should fail directly if the error code is not recoverable
return Fail<ResponseMessage, Error>(error: error)
.eraseToAnyPublisher()
})
.retry(3)
.eraseToAnyPublisher()
}
Basically, you need a retryIf operator, so you can provide a closure to tell Combine which errors should be retried, and which not. I'm not aware of such an operator, but it's not hard to build one for yourself.
The idiomatic way is to extend the Publishers namespace with a new type for your operator, and then extend Publisher to add support for that operator so that yo can chain it along with other operators.
The implementation could look like this:
extension Publishers {
struct RetryIf<P: Publisher>: Publisher {
typealias Output = P.Output
typealias Failure = P.Failure
let publisher: P
let times: Int
let condition: (P.Failure) -> Bool
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
guard times > 0 else { return publisher.receive(subscriber: subscriber) }
publisher.catch { (error: P.Failure) -> AnyPublisher<Output, Failure> in
if condition(error) {
return RetryIf(publisher: publisher, times: times - 1, condition: condition).eraseToAnyPublisher()
} else {
return Fail(error: error).eraseToAnyPublisher()
}
}.receive(subscriber: subscriber)
}
}
}
extension Publisher {
func retry(times: Int, if condition: #escaping (Failure) -> Bool) -> Publishers.RetryIf<Self> {
Publishers.RetryIf(publisher: self, times: times, condition: condition)
}
}
Usage:
func createRequest(for message: Message) -> AnyPublisher<ResponseMessage, Error> {
Deferred {
Future<ResponseMessage, Error> { promise in
// future code
}
}
.retry(times: 3) { error in
if case let MessageBusError.messageError(responseError) = error, responseError.isRecoverable {
return true
}
return false
}
.eraseToAnyPublisher()
}
Note that I wrapped your Future within a Deferred one, otherwise the retry operator would be meaningless, as the closure will not be executed multiple times. More details about that behaviour here: Swift. Combine. Is there any way to call a publisher block more than once when retry?.
Alternatively, you can write the Publisher extension like this:
extension Publisher {
func retry(_ times: Int, if condition: #escaping (Failure) -> Bool) -> Publishers.RetryIf<Self> {
Publishers.RetryIf(publisher: self, times: times, condition: condition)
}
func retry(_ times: Int, unless condition: #escaping (Failure) -> Bool) -> Publishers.RetryIf<Self> {
retry(times, if: { !condition($0) })
}
}
, which enables some funky stuff, like this:
extension Error {
var isRecoverable: Bool { ... }
var isUnrecoverable: Bool { ... }
}
// retry at most 3 times while receiving recoverable errors
// bail out the first time when encountering an error that is
// not recoverable
somePublisher
.retry(3, if: \.isRecoverable)
// retry at most 3 times, bail out the first time when
// an unrecoverable the error is encountered
somePublisher
.retry(3, unless: \.isUnrecoverable)
Or even funkier, ruby-style:
extension Int {
var times: Int { self }
}
somePublisher
.retry(3.times, unless: \.isUnrecoverable)
Typically, I try to avoid building new publishers, and instead prefer to compose publishers from built-in operators. I found it to be rather tricky to do here. Maybe someone can suggest a better approach.
Retry resubscribes on any failure, so to trick it, I packaged any non-recoverable errors into a Result value containing the error, but leaving recoverable errors as failures to .retry; then eventually unpack the Result back into the corresponding value/error.
Here's how it would work in your case:
func createRequest(for message: Message)-> AnyPublisher<ResponseMessage, Error> {
Future<ResponseMessage, Error> { promise in
.....
}
// pack a value into Result
.map { v -> Result<ResponseMessage, Error> in .success(v) }
.tryCatch { error -> AnyPublisher<Result<ResponseMessage, Error>, Error> in
if case let MessageBusError.messageError(responseError) = error {
if responseError.isRecoverable {
// Keep recoverable errors as failures
throw error
}
}
// pack a non-recoverable error into Result with a failure
return Just(.failure(error)).setFailureType(Error.self)
.eraseToAnyPublisher()
}
.retry(3)
// unpack back
.flatMap { result in result.publisher }
.eraseToAnyPublisher()
}
For completeness, to extend Publisher with the above approach:
extension Publisher {
private func retryOnly<U: Publisher>(
upstream: U,
retries: Int,
when predicate: #escaping (U.Failure) -> Bool
) -> AnyPublisher<U.Output, U.Failure> {
upstream
.map { v -> Result<U.Output, U.Failure> in .success(v) }
.catch { err -> AnyPublisher<Result<U.Output, U.Failure>, U.Failure> in
if predicate(err) {
return Fail(error: err).eraseToAnyPublisher()
} else {
return Just(.failure(err))
.setFailureType(to: U.Failure.self)
.eraseToAnyPublisher()
}
}
.retry(retries)
.flatMap { result in result.publisher }
.eraseToAnyPublisher()
}
func retry(_ retries: Int, when predicate: #escaping (Failure) -> Bool)
-> AnyPublisher<Output, Failure> {
return retryOnly(upstream: self, retries: retries, when: predicate)
}
}
failingPublisher.retry(3, when: { $0 is RecoverableError })
I am finding extremely hard to use PromiseKit 6.13.1 in an apparently simple situation.
I have the following two functions returning a Promise<String> but I cannot seem to find a way to use them with ```firstly{}.then{} syntax:
func promiseGetJWTToken() -> Promise<String> {
return Promise<String> { seal in
let error: Error = NSError(domain: "", code: 2000)
getJWTToken { tokenJWT in
guard let tokenJWT = tokenJWT else {
seal.resolve(.rejected(error))
return
}
seal.resolve(.fulfilled(tokenJWT))
}
}
}
func promiseGetBEToken() -> Promise<String> {
return Promise<String> { seal in
let error: Error = NSError(domain: "", code: 2000)
getBEToken { result in
switch result {
case .success(let response):
guard let tokenBE = response.token else {
seal.resolve(.rejected(error))
return
}
seal.fulfill(tokenBE)
case .failure(let error):
debugPrint(error)
seal.resolve(.rejected(error))
case .none:
seal.resolve(.rejected(error))
}
}
}
}
When I try to use the following as follows
firstly {
promiseGetJWTToken()
}.then { tokenJWT in
// no need go on because I have issues already here
}
I receive:
Type '()' cannot conform to 'Thenable'; only struct/enum/class types can conform to protocols
I have also tried, which comes from autocompletion:
promiseGetJWTToken().then { token -> Thenable in
// no need go on because I have issues already here
}
In this case I receive:
Protocol 'Thenable' can only be used as a generic constraint because it has Self or associated type requirements
I decided to give PromiseKit a try because I have three network calls dependent on each other on cascade, but I wouldn't expect this to be so hard. Can anyone show me what am I doing wrong?
The error message is misleading; the real issue is that the .then closure should return a new Thenable. In your examples, the .then closures are empty.
Or just use .done, if not chaining promises.
They replaced that usage of then { } with done { }.
firstly {
promiseGetJWTToken()
}.done { tokenJWT in
// use your token
}
You can try
firstly {
promiseGetJWTToken()
}.then { tokenJWT -> Promise<String> in
return promiseGetBEToken()
}
or
firstly {
promiseGetJWTToken()
}.then { tokenJWT -> Promise<String> in
promiseGetBEToken()
return Promise.value(tokenJWT)
}
How to convert:
func getResults(completion: ([Result]?, Error) -> Void)
Into
var resultsPublisher: AnyPublisher<[Result], Error>
Just a scheme how I see it is (this syntax doesn't exist):
var resultsPublisher: AnyPublisher<[Result], Error> {
let publisher: AnyPublisher = ... // init
getResults { results, error in
guard let results = results else {
publisher.produce(error: error) // this syntax doesn't exist
return
}
publisher.produce(results: results) // this syntax doesn't exist
}
return publisher
}
I need that because some 3d party SDKs use completion closures and I want to write extensions to them that return Publishers.
The answer is Future Publisher as matt explained:
var resultsPublisher: AnyPublisher<[Result], Error> {
// need deferred when want
// to start request only when somebody subscribes
// + without Deferred it's impossible to .retry() later
Deferred {
Future { promise in
self.getResults { results, error in
guard let results = results else {
promise(.failure(error))
return
}
promise(.success(results))
}
}
}
.eraseToAnyPublisher()
}
Here's an example of the code this question is about:
func executeTask() {
fetchApiData().then { foos in
return filterData(foos)
}.then { foos in
return saveData(foos)
}.catch {
/** handle error **/
}
}
func fetchApiData() -> Promise<[Foo]> {
return Promise<[Foo]> { fulfil, reject in
/* Do a network request, and run through object mapper */
fulfil( myFooCollection )
}
}
func filterData(_ data: [Foo]) -> Promise<[Foo]> {
return Promise<[Foo]> { fulfil, _ in
_ = getIdsToFilter().then { ids -> Void in
let filteredData = data.filter { foo in
return ids.contains(foo.id)
}
fulfil(filteredData)
}
}
}
func getIdsToFilter() -> Promise<[Int]> {
return Promise<[Int]> { fulfil, _ in
/* Do some task here */
fulfil([10,20,30,40])
}
}
func saveData(_ data: [Foo]) -> Promise<[Foo]> {
return Promise<[Foo]> { fulfil, reject in
/* Do some save task here */
fulfil(data)
}
}
The function in particular I'm querying is the filterData one.
This is a pretty simple promise chain, get some data, filter it, then save it. However the filtering process requires the response from another promise before it can do it's thing.
As you can see, i've implemented this as a type of wrapper. I return the promise I need, but that promise calls another one from within itself, before doing it's thing.
I feel like this is a bit ugly and goes against the whole composition idea of Promises. I'm thinking there should be a way of doing something like this instead:
func filterData(_ data: [Foo]) -> Promise<[Foo]> {
return getIdsToFilter().then { ids in
return data.filter { foo in
return ids.contains(foo.id)
}
}
}
However that of course doesn't work as xcode says:
Cannot convert return expression of type '[Foo]' to return type 'Promise<[Foo]>' (aka 'Promise<Array<Foo>>')
So... is there a way of flattening out that function? Or maybe what i'm doing is correct and that's how it should be?
P.S: I'm not looking for some overly complex, over-engineered solution
to flattening that out. I'm just trying to follow some sort of best
practice, being nested isn't the problem, I just want to be doing
things "properly"
Function func filterData(_ data: [Foo]) -> Promise<[Foo]> expects to return promise.
In you case the expression
data.filter { foo in
return ids.contains(foo.id)
}
returns Array of Foo objects. You have to wrap [Foo] to Promise object. See the example below
func filterData(_ data: [Foo]) -> Promise<[Foo]> {
return Promise { _ in
return getIdsToFilter().then { ids in
return Promise(value: data.filter { return ids.contains($0.id) })
}
}
}
Update:
To understand what is wrong you can explicit declare the type of return parameters. Then you will see what type expects swift compiler and what type expect you. This tactic helps me understand compiler errors
func filterData(_ data: [Foo]) -> Promise<[Foo]> {
return Promise { _ in
return getIdsToFilter().then { ids -> Promise<[Foo]> in
return Promise(value: data.filter { return ids.contains($0.id) })
}
}
}