Promisekit: what's the best way to have a timeout in the promise?

Created on 5 Aug 2015  ·  9Comments  ·  Source: mxcl/PromiseKit

Hello. I would like to add a timeout to some promises that make them to "reject" if waiting for some conditions takes too long. Is there any "suggested" or "standard" way to do this?

is something like this correct:

let p=Promise<Void>(resolvers:
{
    (fulfill, reject) -> Void in
    NSOperationQueue().addOperationWithBlock({
        () -> Void in
        NSThread.sleepForTimeInterval(10)
        NSOperationQueue().mainQueue().addOperationWithBlock({
            () -> Void in
            reject(NSError(... TIMEOUT MESSAGE ...))
        })
    })
    var finished = false

    NSOperationQueue().addOperationWithBlock({
        () -> Void in
        while true {
            NSOperationQueue().mainQueue().addOperationWithBlock({
                () -> Void in
                if condition {
                    finished = true
                    fulfill()
                }
            })
            if finished {
                break
            }
        }
    })
}

and, if it's correct, it's still awful; is there a better way?

Most helpful comment

There is no standard way to do this, (though it would be a welcome addition). I suggest:

let p = Promise<Void> { fulfill, reject in

    after(10).then {
        reject(NSError(…))
    }

    someAsyncFunctionWithCompletionHandler {

        // do stuff

        if condition {
            fulfill()
        }
    }
}

It is safe to call reject or fulfill after the promise is already resolved, it is a noop.

However if you want to avoid some work, then you can ask the promise if it is resolved:

var promise: Promise<Void>!
promise = Promise<Void> { fulfill, reject in

    after(10).then {
        reject(NSError(…))
    }

    someAsyncFunctionWithCompletionHandler {

        while promise.pending {
            // do stuff
        }

        if condition {
            fulfill()
        }
    }
}

You don’t need to switch back to the main queue with Promises. See http://promisekit.org/dispatch-queues/

All 9 comments

There is no standard way to do this, (though it would be a welcome addition). I suggest:

let p = Promise<Void> { fulfill, reject in

    after(10).then {
        reject(NSError(…))
    }

    someAsyncFunctionWithCompletionHandler {

        // do stuff

        if condition {
            fulfill()
        }
    }
}

It is safe to call reject or fulfill after the promise is already resolved, it is a noop.

However if you want to avoid some work, then you can ask the promise if it is resolved:

var promise: Promise<Void>!
promise = Promise<Void> { fulfill, reject in

    after(10).then {
        reject(NSError(…))
    }

    someAsyncFunctionWithCompletionHandler {

        while promise.pending {
            // do stuff
        }

        if condition {
            fulfill()
        }
    }
}

You don’t need to switch back to the main queue with Promises. See http://promisekit.org/dispatch-queues/

This doesn't seem to work with PromiseKit 6 because after now returns a Guarantee instead of Promise. What do I need to change to make this code work with PK6?
I'm doing this:

return Promise { seal in
            // automatic rejection after the timeout?
            after(seconds: MyTimeoutInterval)
                .then { Guarantee { $0(TimeoutError) }
            }
            self.doLongOperation() { success, error in
               seal.resolve(success, error)
           }
        }

and it has no effect (I did notice however that a breakpoint set at .then in the after clause does get hit after roughly MyTimeoutInterval seconds)
If I do

after(seconds: MyTimeoutInterval)
                .then { seal.reject(TimeoutError) }

it first complains that Contextual type for closure argument list expects 1 argument, which cannot be implicitly ignored and suggests to insert _ in before seal.reject. However, it brings up another error:
Cannot convert value of type '(_) -> ()' to expected argument type '(Void) -> Guarantee<_>'

PromiseKit 6:

let p = Promise<Void> { seal in

    after(.seconds(10)).done {
        seal.reject(MyError)
    }

    someAsyncFunctionWithCompletionHandler {

        // do stuff

        if condition {
            seal.fulfill()
        }
    }
}

Whoa, that was fast! Will try this now.
UPD: it works! Thanks!

Glad to hear 👍🏻

If you found my work helpful, please consider supporting its development.

I have a similar situation. In PK4, I would race(promise,timeout) but now that timeout is a Guarantee, the generic parameter on race will not work out. Any opinion on this as a solution:

extension Promise {
    func timeout(seconds: TimeInterval, timeoutError: Error) -> Promise<T> {
        return Promise<T> { seal in
            after(seconds: seconds).done {
                seal.reject(timeoutError)
            }
            self.done { result in
                seal.fulfill(result)
                }.catch { err in
                    seal.reject(err)
            }
        }
    }
}

I assume it's safe (no-op) to resolve a seal more than once.

There's a conversion initializer for Guarantee to Promise:

race(promise, Promise(timeout))

I assume it's safe (no-op) to resolve a seal more than once.

Yes, sources are easy to read if you want confirmation.

Any opinion on this as a solution:

Can be simpler, also weird to provide an error yourself imo:

enum E: Error {
    case timeout
}

extension Promise {
    func timeout(after seconds: TimeInterval) -> Promise<T> {
        return race(asVoid(), after(seconds: seconds).done {
            throw E.timeout
        }).map {
            self.value!
        }
    }
}

Thanks, ended up doing something similar. In 6.8.x, this is triggering the new "PromiseKit: warning: pending guarantee deallocated" on the after guarantee. Not sure why exactly. I will update if I find the cause.

If you have an issue please open a new ticket.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mxcl picture mxcl  ·  4Comments

daanporon picture daanporon  ·  6Comments

anilabsinc-ajay picture anilabsinc-ajay  ·  6Comments

drekka picture drekka  ·  5Comments

emreond picture emreond  ·  5Comments