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.
Related
I am trying to pass the value of gyroX to another function but it just ends up in it having a value of 0 when I use it as gyroX in that other function.
Here is the code:
var gyroX = Float()
motion.startGyroUpdates(to: .main) { (data, error) in
if let myData = data {
gyroX = Float(myData.rotationRate.x)
}
}
With Xcode 13 Beta and Swift 5.5
This is a problem that we can now solve with Async/Await's Continuations
We would first make a function that converts the callback into an awaitable result like:
func getXRotation(from motion: CMMotionManager) async throws -> Float {
try await withCheckedThrowingContinuation { continuation in
class GyroUpdateFailure: Error {} // make error to throw
motion.startGyroUpdates(to: .main) { (data, error) in
if let myData = data {
continuation.resume(returning: Float(myData.rotationRate.x))
} else {
throw GyroUpdateFailure()
}
}
}
}
Then we can assign the variable and use it like so:
let gyroX = try await getXRotation(from: motion)
callSomeOtherFunction(with: gyroX)
With Xcode <= 12 and Combine
In the current release of Swift and Xcode we can use the Combine framework to make callback handling a little easier for us. First we'll convert the closure from the motion manager into a "Future". Then we can use that future in a combine chain.
func getXRotation(from motion: CMMotionManager) -> Future<CMGyroData, Error> {
Future { promise in
class GyroUpdateFailure: Error {} // make error to throw
motion.startGyroUpdates(to: .main) { (data, error) in
if let myData = data {
promise(.success(myData))
} else {
promise(.failure(GyroUpdateFailure()))
}
}
}
}
// This is the other function you want to call
func someOtherFunction(_ x: Float) {}
// Then we can use it like so
_ = getXRotation(from: motion)
.eraseToAnyPublisher()
.map { Float($0.rotationRate.x) }
.map(someOtherFunction)
.sink { completion in
switch completion {
case .failure(let error):
print(error.localizedDescription)
default: break
}
} receiveValue: {
print($0)
}
There are some important parts to the combine flow. The _ = is one of them. The result of "sinking" on a publisher is a "cancellable" object. If we don't store that in a local variable the system can clean up the task before it fishes executing. So you will want to do that for sure.
I highly recommend you checkout SwiftBySundell.com to learn more about Combine or Async/Await and RayWenderlich.com for mobile development in general.
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()
}
I have a pseudo RxSwift implementation of a repository pattern that supports offline and remote operations. I'm trying to migrate it from using our homegrown implementation of Dynamic<T> to using promises from the PromiseKit library; the code I'm trying to migrate looks like the following:
class Repository {
private var realmRepo: RealmRepo!
private var httpRepo: HTTPRepo!
private var offlineCache: OfflineCache!
func complete(step: Entity, options: Options) -> Dynamic<Entity> {
let result = Dynamic<Entity>()
realmRepo.complete(step: step, options: options).do(onNext: {[result] value in
guard let realmStep = value else {
return result.complete(with: Error.unknown("realm result was `nil`"))
}
self.httpRepo.complete(step: realmStep, options: options).do(onNext: { value in
result.complete(with: value)
}).do(onError: { error in
switch error {
case HTTPRepo.Error.noNetworkConnection(let request):
try? self.offlineCache.add(object: createOfflineObject(request as! URLRequest))
result.complete(with: realmStep)
default:
result.complete(with: error)
}
})
}).do(onError: {[result] error in
result.complete(with: error)
})
return result
}
The Dynamic<T> class looks like the following (implementation omitted):
class Dynamic<T> {
func `do`(onNext: (T?) -> Void) -> Dynamic<T> // returns `self`
func `do`(onError: (Error) -> Void) -> Dynamic<T> // returns `self`
func complete(with: Error)
func complete(with: T?)
}
I'm trying to rewrite the return values for the repository from Dynamic<Entity> to Promise<Entity>, using the PromiseKit library, I'm not sure how to replicate the following chain of events using promises:
Attempt the Realm call, if it fails send an error to the returned dynamic object (or fail the promise)
If the RealmRepo.complete call succeeded, then attempt the HTTPRepo.complete call.
If the HTTPRepo.complete call succeeds then emit a "next" value to the dynamic (promise succeeds)
If the HTTPRepo.complete call fails then catch the error which will have the failure reason and if it was a lack of network connection then perform another call to the OfflineCache.add method and resolve the Dynamic value with the result from the RealmRepo.complete call.
So far I've managed the following:
import PromiseKit
// ... other code ...
class Repository {
// ... repository fields ...
func complete(step: Entity, options: Options) -> Promise<Entity> {
return self.realmRepo.complete(step: step, options).then {
return self.httpRepo.complete(step: $0, options: options)
}.catch { error in
switch error {
case HTTPRepo.Error.noNetworkConnection(let request):
try? self.offlineCache.add(object: createOfflineObject(request as! URLRequest))
default:
result.complete(with: error)
}
}
}
}
Such code gives me a compile error in the catch block, I'm not even sure of how I'd handle or recover from the error that httpRepo would return if no connection is present.
Any help is really appreciated!
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)"
}
}
}