I'm using PromiseKit to handle flow through a process.
Prior, I did a similar app without promises but decided frick it I'm gonna try promises just because, well, why not?
So I'm throwing a back button in the mix as I did in the prior app. Only problem is, I'm not exactly sure how to handle "reversing" if you want to call it that.
So say I have a flow of
doSomething().then {
// do something else
}.then {
// do something else
}.done {
// wrap it up, boss
}.catch {
// you're an idiot, bud
}
Say I'm in the first or second part of the chain then and I want to go back up the chain - is this possible?
Is there a link y'all can give me that I can use to read up on how to do that?
I'm thinking I might have to restart the "chain", but then how would I step through the flow....WAIT (light bulb), I can programmatically fulfill the necessary promises with whatever the data is that initially was fulfilled with until I get to the point in the "chain" where I needed to go back to, right?
Advice D:?
You can always have a catch and a then on the same promise.
var somePromise = doSomething()
// first chain
somePromise.catch { error in
// handle error
}
// second chain from the same starting point
somePromise.then {
// do something else
}.then {
// do something else
}.catch {
// you can still catch the error here too
}
You're basically creating two promise chains from the same original promise.
No, you can not do that. Once you commit a promise, you can not reverse that. Because the chain is supposed to finish in the descending order, it's cumbersome to track the order in each .then block.
What you can do is, handle the internal logic responsible to fulfill or reject a promise and start the chain from the beginning.
func executeChain() {
doSomething().then {
// do something else
}.then {
// do something else
}.done {
// condition to
executeChain()
}.catch {
// you're an idiot, bud
}
}
func doSomething() -> Promise<SomeThing>{
if (condition to bypass for reversing) {
return .value(something)
}
// Normal execution
}
But if you can improve your question with an actual use case and code then it could help providing more suitable explanation.
No you can't but you can set order in array.
bar(promises: [foo1(), foo2(), foo3()])
func bar<T>(promises: [Promise<T>]) {
when(fulfilled: promises)
.done { _ in
// TODO
}
.catch { error in
// When get error reverse array and call it again
self.bar(promises: promises.reversed())
}
}
func foo1() -> Promise<Void> {
return Promise { $0.fulfill(()) }
}
func foo2() -> Promise<Void> {
return Promise { $0.fulfill(()) }
}
func foo3() -> Promise<Void> {
return Promise { $0.fulfill(()) }
}
or alternatively
bar(foo1, foo2, foo3)
.done { _ in
// TODO
}
.catch { error in
print(error.localizedDescription)
self.bar(self.foo3, self.foo2, self.foo1)
.done { _ in
// TODO
}
.catch { error2 in
print(error2.localizedDescription)
}
}
func bar<T>(_ promise1: () -> Promise<T>,
_ promise2: #escaping () -> Promise<T>,
_ promise3: #escaping () -> Promise<T>) -> Promise<T> {
return Promise { seal in
promise1()
.then { _ in return promise2() }
.then { _ in return promise3() }
.done { model in
seal.fulfill(model)
}
.catch {
seal.reject($0)
}
}
}
func foo1() -> Promise<Void> {
return Promise { $0.fulfill(()) }
}
func foo2() -> Promise<Void> {
return Promise { $0.fulfill(()) }
}
func foo3() -> Promise<Void> {
return Promise { $0.fulfill(()) }
}
Related
I am stuck with somewhere to call same function again in promise and because of calling multiple time it's deallocate promise. Actually in my case I have API with multiple page request and I want to call it with promise. I was implemented it as below.
func fetchContacts() -> Promise<FPGetContactResponse?> {
return Promise { seal in
let contactrequest = FPGetContactRequest()
contactrequest.pageNo = getAPICurrentPageNo(Api.API_CONTACTS) + 1
contactrequest.pageSize = SMALL_PAGE_SIZE
contactrequest.doGetContacts(parameter: [:], response: { (response) in
print("Contacts Count : \(response.Contacts?.count ?? 0)")
if(response.Contacts?.count ?? 0 != 0){
_ = self.fetchContacts()
}else{
seal.fulfill(response)
}
})
{ (error) in
print(error.localizedDescription)
seal.reject(error)
}
}
}
In above function I check for contact count != 0 then I need to call same function again. But unfortunately it's deallocate promise.
I call promise sequence like below.
func startSyncData(handler:#escaping SyncAPIHandler){
firstly {
self.fetchContacts().ensure {
handler(false,0.5,nil)
}
}.then { data in
self.fetchInteractions().ensure {
handler(false,0.7,nil)
}
}.then { data in
self.fetchAddresses().ensure {
handler(false,0.8,nil)
}
}.then { data in
self.fetchLookupQuery().ensure {
}
}
.done { contacts -> Void in
//Do something with the JSON info
print("Contacts Done")
handler(true,0.8,nil)
}
.catch(policy: .allErrors) { error in
print(error.localizedDescription)
}
}
Please provide me the right way to call same function again in promise.
Instead of using recursion you should return a response within your promise and check it inside next .then and call fetchContacts again if it's needed:
fetchContacts()
.then { response -> Promise<FPGetContactResponse> in
if (response.Contacts?.count ?? 0 != 0) {
return fetchContacts() // Make the second call
}
return .value(response) // Return fullfilled promise
}
.then {
...
}
Also you can make a special wrapper for your case using the next approach - https://github.com/mxcl/PromiseKit/blob/master/Documentation/CommonPatterns.md#retry--polling
I implemented things with following solution.
func syncContacts() -> Promise<FPGetContactResponse?> {
return fetchContacts().then{ seal -> Promise<FPGetContactResponse?> in
if(seal?.Contacts?.count ?? 0 != 0){
return self.syncContacts()
}else{
return Promise.value(seal)
}
}
}
Now just call syncContacts() method in promise sequence, like below.
func startSyncData(handler:#escaping SyncAPIHandler){
firstly {
self.syncContacts().ensure {
handler(false,0.5,nil)
}
}.then { data in
self.syncInterections().ensure {
handler(false,0.7,nil)
}
}.then { data in
self.syncAddresses().ensure {
handler(false,0.8,nil)
}
}.then { data in
self.syncLookupQuery().ensure {
}
}
.done { contacts -> Void in
//Do something with the JSON info
print("Contacts Done")
handler(true,0.8,nil)
}
.catch(policy: .allErrors) { error in
print(error.localizedDescription)
}
}
Im new to RXSwift and I've begun investigating how I can perform Promise like function chaining.
I think I'm on the right track by using flatmap but my implementation is very difficult to read so I suspect theres a better way to accomplish it.
What I have here seems to work but I'm getting a headache thinking about what It might looks like if I added another 3 or functions to the chain.
Here Is where I declare my 'promise chain'(hard to read)
LOGIN().flatMap{ (stuff) -> Observable<Int> in
return API(webSiteData: stuff).flatMap
{ (username) -> Observable<ProfileResult> in
return accessProfile(userDisplayName: username) }
}.subscribe(onNext: { event in
print("The Chain Completed")
print(event)
}, onError:{ error in
print("An error in the chain occurred")
})
These are the 3 sample functions I'm chaining
struct apicreds
{
let websocket:String
let token:String
}
typealias APIResult = String
typealias ProfileResult = Int
// FUNCTION 1
func LOGIN() -> Observable<apicreds> {
return Observable.create { observer in
print("IN LOGIn")
observer.onNext(apicreds(websocket: "the web socket", token: "the token"))
observer.on(.completed)
return Disposables.create()
}
}
// FUNCTION 2
func API(webSiteData: apicreds) -> Observable<APIResult> {
return Observable.create { observer in
print("IN API")
print (webSiteData)
// observer.onError(myerror.anError)
observer.on(.next("This is the user name")) // assiging "1" just as an example, you may ignore
observer.on(.completed)
return Disposables.create()
}
}
//FUNCTION 3
func accessProfile(userDisplayName:String) -> Observable<ProfileResult>
{
return Observable.create { observer in
// Place your second server access code
print("IN Profile")
print (userDisplayName)
observer.on(.next(200)) // 200 response from profile call
observer.on(.completed)
return Disposables.create()
}
}
This is a very common problem we run into while chaining operations. As a beginner I had written similar code using RxSwift in my projects as well. And there are two areas of improvement -
1. Refactor the code to remove nested flatMaps
2. Format it differently to make the sequence easier to follow
LOGIN()
.flatMap{ (stuff) -> Observable<APIResult> in
return API(webSiteData: stuff)
}.flatMap{ (username) -> Observable<ProfileResult> in
return accessProfile(userDisplayName: username)
}.subscribe(onNext: { event in
print("The Chain Completed")
print(event)
}, onError:{ error in
print("An error in the chain occurred")
})
In addition to nested flatMap and code formatting, you could omit return and explicit return types:
LOGIN()
.flatMap { webSiteData in API(webSiteData: webSiteData) }
parameter names
LOGIN()
.flatMap { API(webSiteData: $0) }
or even remove parameters at all where appropriate:
LOGIN()
.flatMap(API)
.flatMap(accessProfile)
.subscribe(
onNext: { event in
print(event)
}, onError:{ error in
print(error)
}
)
FYI there is Observable.just method which would be convenient here:
struct ApiCredentials {
let websocket: String
let token: String
}
func observeCredentials() -> Observable<ApiCredentials> {
let credentials = ApiCredentials(websocket: "the web socket", token: "the token")
return Observable.just(credentials)
}
Try to follow official Swift API Guidelines to make your code more readable.
You can also use the point-free style and just pass function references to flatMap:
LOGIN()
.flatMap(API)
.flatMap(accessProfile)
.subscribe(onNext: { event in
print("The Chain Completed")
print(event)
}, onError:{ error in
print("An error in the chain occurred")
})
I don't want to write a separate function to return a Promise in my firstly call. I just want to write this:
firstly
{
return Promise<Bool>
{ inSeal in
var isOrderHistory = false
let importTester = CSVImporter<String>(url: url)
importTester?.startImportingRecords(structure:
{ (inFieldNames) in
if inFieldNames[2] == "Payment Instrument Type"
{
isOrderHistory = true
}
}, recordMapper: { (inRecords) -> String in
return "" // Don't care
}).onFinish
{ (inItems) in
inSeal.resolve(isOrderHistory)
}
}
}
.then
{ inIsOrderHistory in
if inIsOrderHistory -> Void
{
}
else
{
...
But I'm getting something wrong. ImportMainWindowController.swift:51:5: Ambiguous reference to member 'firstly(execute:)'
None of the example code or docs seems to cover this (what I thought was a) basic use case. In the code above, the CSVImporter operates on a background queue and calls the methods asynchronously (although in order).
I can't figure out what the full type specification should be for Promise or firstly, or what.
According to my understanding, since you are using then in the promise chain, it is also meant to return a promise and hence you are getting this error. If you intend not to return promise from your next step, you can directly use done after firstly.
Use below chain if you want to return Promise from then
firstly {
Promise<Bool> { seal in
print("hello")
seal.fulfill(true)
}
}.then { (response) in
Promise<Bool> { seal in
print(response)
seal.fulfill(true)
}
}.done { _ in
print("done")
}.catch { (error) in
print(error)
}
If you do not want to return Promise from then, you can use chain like below.
firstly {
Promise<Bool> { seal in
print("hello")
seal.fulfill(true)
}
}.done { _ in
print("done")
}.catch { (error) in
print(error)
}
I hope it helped.
Updated:
In case you do not want to return anything and then mandates to return a Promise, you can return Promise<Void> like below.
firstly {
Promise<Bool> { seal in
print("hello")
seal.fulfill(true)
}
}.then { (response) -> Promise<Void> in
print(response)
return Promise()
}.done { _ in
print("done")
}.catch { (error) in
print(error)
}
I am using an external library in Swift so I cannot control the return statements. My understanding is that I should wrap these returns in promises in order to use PromiseKit. Is this correct?
Assuming so, I have working code as follows:
private func getChannelImage(for channel: TCHChannel, completion: #escaping (UIImage?, CAProfileError?) -> Void) {
if let members = channel.members {
members.members(completion: { (result, paginator) in
if result.isSuccessful() {
// ... do something
}
else {
completion(nil, CAProfileError.UnknownError)
}
})
}
}
This can be difficult to read. I am trying to simplify this using PromiseKit. First, I want to simplify members.members(completion: { (result, paginator) in to a promise that I can call with the firstly { ... } syntax. I thus try and do as follows:
private func asPromise(members: TCHMembers) -> Promise<TCHMemberPaginator> {
return Promise<TCHMemberPaginator> { fulfill, reject in
members.members(completion: { (result, paginator) in
if result.isSuccesful() {
fulfill(paginator)
} else {
reject()
}
})
}
}
But this approach does not work and I get "Unable to infer closure type in the current context". I'm trying to find a good example of this use case done online but am having trouble. Any thoughts on how to properly return promises?
Assuming the TCHMemberPaginator and TCHMembers as below,
class TCHMemberPaginator {}
class TCHMembers {
func members(completion: (Bool, TCHMemberPaginator?) -> Void) {}
}
Here is the method to return a Promise,
private func asPromise(members: TCHMembers) -> Promise<TCHMemberPaginator> {
return Promise { seal in
members.members(completion: { (result, paginator) in
if result == true, let p = paginator {
seal.fulfill(p)
} else {
seal.reject(NSError())
}
})
}
}
I'm using PromiseKit to handle my network calls. I'm trying to see if there's a convention or a cleaner way to either fulfill or reject the promise early. As illustrated below, there are a few conditions that would require me to fulfill or reject early. I'm currently doing this by putting a return statement right afterward. I feel like this is rather clunky and am wondering if there's a better way to do this. Thanks!
return PromiseKit { fulfill, reject in
if statusCode == 200 {
if conditionA {
if conditionB {
fulfill(...) // How do I stop the execution chain from here
return
} else {
reject(...) // Or here, without having to call return every time
return
}
}
reject(...)
}
}
Rather than using fulfill and reject, you could return the Promise result. Below I have created a function showing you how it can be done:
func someMethod(statusCode: Int, conditionA: Bool, conditionB: Bool) -> Promise<Any> {
if statusCode == 200 {
if conditionA {
if conditionB {
return Promise(value: "Return value")
} else {
return Promise(error: PromiseErrors.conditionBInvalid)
}
}
}
return Promise(error: PromiseErrors.invalidStatusCode)
}
enum PromiseErrors: Error {
case invalidStatusCode
case conditionBInvalid
}
By not using fullfill and reject, you can also clean up the code and move the conditionB check to a new function, such as:
func someMethod(statusCode: Int, conditionA: Bool, conditionB: Bool) -> Promise<Any> {
if statusCode == 200 {
if conditionA {
return conditionASuccess(conditionB: conditionB)
}
}
return Promise(error: PromiseErrors.invalidStatusCode)
}
func conditionASuccess(conditionB: Bool) -> Promise<Any> {
if conditionB {
return Promise(value: "Return value")
}
return Promise(error: PromiseErrors.conditionBInvalid)
}
Are you using the PromiseKit extension for Foundation? It helps to simplify networking calls with Promises. You can get the extension here: https://github.com/PromiseKit/Foundation