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?
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.
Most helpful comment
There is no standard way to do this, (though it would be a welcome addition). I suggest:
It is safe to call
rejectorfulfillafter the promise is already resolved, it is anoop.However if you want to avoid some work, then you can ask the promise if it is resolved:
You don’t need to switch back to the main queue with Promises. See http://promisekit.org/dispatch-queues/