PromiseKit fulfill and reject convention - swift

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

Related

How do I reverse a promise?

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(()) }
}

Why use the responseWith method?

In the process of reading the RXAlamofire source code, there is a place that I don't understand very well.
Since this method is an observable object for creating a DataRequest, why call the responseWith method?
func request<R: RxAlamofireRequest>(_ createRequest: #escaping (SessionManager) throws -> R) -> Observable<R> {
return Observable.create { observer -> Disposable in
let request: R
do {
request = try createRequest(self.base)
observer.on(.next(request))
request.responseWith(completionHandler: { response in
if let error = response.error {
observer.on(.error(error))
} else {
observer.on(.completed)
}
})
if !self.base.startRequestsImmediately {
request.resume()
}
return Disposables.create {
request.cancel()
}
} catch {
observer.on(.error(error))
return Disposables.create()
}
}
}
I believe the authors of RXAlamofire use this as their convention. If you look at there request implementation All of the request methods return the result of a method responseXYZ. The response methods typically execute the request and respond with something (JSON, String, etc.) Sounds a bit confusing but its kind of like this request some data respond with something.

PromiseKit firstly around code, not function call

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)
}

PromiseKit wrapping external closure in Promises

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())
}
})
}
}

PromiseKit with optional promises

I am currently investigating if I should integrate PromiseKit in an existing project.
My main issue is that I need to implement a business logic that can call up to 5 web-services. Some of them are called depending on the result of previous ones.
My current architecture is based on breaking up the code in several functions with closures that call each other.
I am trying to find out if I could write an easier to manage code with PromiseKit (or anything else).
Here is some pseudo-code of what I need done:
// if true, the phone validation is skipped
let forceRequest = false
// true if a 3rd party web-service has checked the phone number
let isVerified = true
// true if the 3rd party checked the phone number and it is valid
var isValid = false
if !isVerified {
// update value from 3rd party web-service
isValid = isValidPhoneNumberPromise()
}
// If the phone no is invalid stop execution (unless forced)
if !isValid && !force {
throw MyError.error1
}
// web request to create order
createOrderPromise()
// if we have a valid phone number, first send an SMS, then update log
if isValid {
sendSmsPromise()
updateLogPromise()
}
Based on totiG's answer, I came with the following variation:
var isValid = isValid
firstly
{
return Controller.verify(isVerified: isVerified, isValid: isValid)
}
.then { _isValid -> Promise<Int> in
isValid = _isValid
return Controller.createOrder()
}
.then
{ _ -> Promise<Bool> in
if isValid {
return Controller.isSendSms()
}
return Promise (value: true)
}
.then
{ _ -> Promise<Bool> in
if isValid {
return Controller.updateLog()
}
return Promise (value: true)
}
.catch
{ error in
print (error)
}
Yes you can do this using PromiseKit. I have written a basic example showing what you might need. Remember that you would throw an Error when a step fails and handle those failures in the catch block. In my example the verify step passes, but if isValidPhoneNumber was called it would stop the other steps from running. Where I have put the Promise(value: ) you would put your actual web service calls. If the final step to update log is always required to run, you could put this in a .always
enum Errors: Error {
case invalidPhone
case orderFailed
}
func orderPromise() {
firstly {
self.verify(isVerified: false, force: true)
}.then { _ in
self.createOrder()
}.then { orderNumber in
self.sendSms(orderNumber: orderNumber)
}.then { smsSent in
self.updateLog(smsSent: smsSent)
}.catch { error in
//Do something with the error
}
}
private func verify(isVerified: Bool, force: Bool) -> Promise<Bool> {
if isVerified || force {
return Promise(value: true)
}
return isValidPhoneNumber()
}
private func isValidPhoneNumber() -> Promise<Bool> {
return Promise(error: Errors.invalidPhone) //Assume that this fails, then catch in 'orderPromise' will be run
}
private func createOrder() -> Promise<String> {
//Assume an order number is being passed back to use in the message
return Promise(value: "Order100")
}
private func sendSms(orderNumber: String) -> Promise<Bool> {
return Promise(value: true)
}
private func updateLog(smsSent: Bool) -> Promise<Bool> {
return Promise(value: true)
}