4

According to migration guide PromiseKit 6.x changed his policy about catch blocks. In PMK 4 catch returned the promise it was attached to. Now catch is a chain- terminator. I understand why these changes were made but...

In my codebase (connected with PK4) I take some advantages from that catch returns promise.

func loginAndSync(withServerAddress address: String, port: String) -> Promise<()> {
    synchronizationService.stopAllRunningSingleSynchronizations()

    return
        authorizationService.save(serverAddress: address, andPort: port)
            .then {
                self.synchronizationService.startSingleFullSynchronization()
            }
            .then {
                self.authorizationService.markAsServerSynced()
            }
            .catch { error in
                log.error(error)
                _ = self.authorizationService.markAsServerUnsynced()
            }
}

In this function I make some logic which is in some cases failable. In case of error catch block should make some logic but I want to also send to loginAndSync function`s caller a result of this promise (fulfill or reject). Above function can be called for example by ViewController and in ViewController I want to show for example Error or Success Dialog.

This is a reason where I need a two catches for one Promise-chain. One for authorizationService and one for UI.

Is there any workaround in PromiseKit6 to achieve this?

Edit

I found two solutions (workarounds). I have created two answers to separate them. Future readers can decide which one is better or provide new one.

Kamil Harasimowicz
  • 4,684
  • 5
  • 32
  • 58

2 Answers2

6

I found (IMO) better workaround. PromiseKit6 introduces very handy method tap. I have tried use it to resolve my problem.

func loginAndSync(withServerAddress address: String, port: String) -> Promise<()> {
    synchronizationService.stopAllRunningSingleSynchronizations()

    return authorizationService.save(serverAddress: address, andPort: port)
        .then {
            self.synchronizationService.startSingleFullSynchronization()
        }
        .then {
            self.authorizationService.markAsServerSynced()
        }
        .tap { result in
            switch result {
            case .rejected(let error):
                log.error(error)
                _ = self.authorizationService.markAsServerUnsynced()
            default: break
            }
        }
        .done {
            self.coordinatorDelegate?.handleLoginServerSuccess()
        }
}

For doc:

tap feeds you the current Result for the chain, so is called if the chain is succeeding or if it is failing

So I can provide custom error handling for current state of Promise whit terminating the chain. Promise can be send to sender where I can do another tap or terminate chain by catch.

Kamil Harasimowicz
  • 4,684
  • 5
  • 32
  • 58
  • 2
    You could also use 'recover' instead of 'tap'. Then you won't have to switch on the result each time, since you receive the error itself. So you can respond to the error by doing some logic, then throw back the error (for a subsequent recover block, or someone else's catch block). The task of 'responding to errors' at multiple layers is more powerful with recover because you can do more stuff like return the root of a sub-branch of promises – Peter Parker Aug 04 '19 at 12:16
0

I have wrapped my Promise chain in another Promise:

func loginAndSync(withServerAddress address: String, port: String) -> Promise<()> {
    synchronizationService.stopAllRunningSingleSynchronizations()

    return Promise { seal in
        authorizationService.save(serverAddress: address, andPort: port)
            .then {
                self.synchronizationService.startSingleFullSynchronization()
            }
            .then {
                self.authorizationService.markAsServerSynced()
            }
            .done {
                self.coordinatorDelegate?.handleLoginServerSuccess()
                seal.fulfill(())
            }
            .catch {
                log.error($0)
                _ = self.authorizationService.markAsServerUnsynced()
                seal.reject($0)
            }
    }
}

It should work but I don't know is it a good approach.

Kamil Harasimowicz
  • 4,684
  • 5
  • 32
  • 58
  • I recently had the same problem and solved it like this as well, by wrapping the promise in another promise. Is there a better solution to this problem? – rodskagg Jan 29 '19 at 11:01