Commit bb386f1b authored by Kenton Varda's avatar Kenton Varda

Add various convenience features to async APIs:

- Promise<T>::catch_() method which is equivalent to then() but you only specify an error handler; the non-error continuation is the identity function.
- kj::evalNow() function which synchronously calls a function and returns a promise for the result, or a broken promise if the function threw an exception. This is convenient for making sure exceptions get pushed into the promise chain.
- KJ_WARN_UNUSED_RESULT on various methods that return promises.
parent 628559ad
......@@ -683,6 +683,42 @@ PromiseForResult<Func, T> Promise<T>::then(Func&& func, ErrorFunc&& errorHandler
_::maybeChain(kj::mv(intermediate), implicitCast<ResultT*>(nullptr)));
}
namespace _ { // private
template <typename T>
struct IdentityFunc {
inline T operator()(T&& value) const {
return kj::mv(value);
}
};
template <typename T>
struct IdentityFunc<Promise<T>> {
inline Promise<T> operator()(T&& value) const {
return kj::mv(value);
}
};
template <>
struct IdentityFunc<void> {
inline void operator()() const {}
};
template <>
struct IdentityFunc<Promise<void>> {
inline Promise<void> operator()() const { return READY_NOW; }
};
} // namespace _ (private)
template <typename T>
template <typename ErrorFunc>
Promise<T> Promise<T>::catch_(ErrorFunc&& errorHandler) {
// then()'s ErrorFunc can only return a Promise if Func also returns a Promise. In this case,
// Func is being filled in automatically. We want to make sure ErrorFunc can return a Promise,
// but we don't want the extra overhead of promise chaining if ErrorFunc doesn't actually
// return a promise. So we make our Func return match ErrorFunc.
return then(_::IdentityFunc<decltype(errorHandler(instance<Exception&&>()))>(),
kj::fwd<ErrorFunc>(errorHandler));
}
template <typename T>
T Promise<T>::wait(WaitScope& waitScope) {
_::ExceptionOr<_::FixVoid<T>> result;
......@@ -727,15 +763,10 @@ Promise<T> Promise<T>::attach(Attachments&&... attachments) {
template <typename T>
template <typename ErrorFunc>
Promise<T> Promise<T>::eagerlyEvaluate(ErrorFunc&& errorHandler) {
return Promise(false, _::spark<_::FixVoid<T>>(
then([](T&& value) -> T { return kj::mv(value); }, kj::fwd<ErrorFunc>(errorHandler)).node));
}
template <>
template <typename ErrorFunc>
Promise<void> Promise<void>::eagerlyEvaluate(ErrorFunc&& errorHandler) {
return Promise(false, _::spark<_::Void>(
then([]() {}, kj::fwd<ErrorFunc>(errorHandler)).node));
// See catch_() for commentary.
return Promise(false, _::spark<_::FixVoid<T>>(then(
_::IdentityFunc<decltype(errorHandler(instance<Exception&&>()))>(),
kj::fwd<ErrorFunc>(errorHandler)).node));
}
template <typename T>
......@@ -753,6 +784,17 @@ inline PromiseForResult<Func, void> evalLater(Func&& func) {
return _::yield().then(kj::fwd<Func>(func), _::PropagateException());
}
template <typename Func>
inline PromiseForResult<Func, void> evalNow(Func&& func) {
PromiseForResult<Func, void> result = nullptr;
KJ_IF_MAYBE(e, kj::runCatchingExceptions([&]() {
result = func();
})) {
result = kj::mv(*e);
}
return result;
}
template <typename T>
template <typename ErrorFunc>
void Promise<T>::detach(ErrorFunc&& errorHandler) {
......
......@@ -194,6 +194,11 @@ public:
// actual I/O. To solve this, use `kj::evalLater()` to yield control; this way, all other events
// in the queue will get a chance to run before your callback is executed.
template <typename ErrorFunc>
Promise<T> catch_(ErrorFunc&& errorHandler) KJ_WARN_UNUSED_RESULT;
// Equivalent to `.then(identityFunc, errorHandler)`, where `identifyFunc` is a function that
// just returns its input.
T wait(WaitScope& waitScope);
// Run the event loop until the promise is fulfilled, then return its result. If the promise
// is rejected, throw an exception.
......@@ -327,7 +332,7 @@ constexpr _::NeverDone NEVER_DONE = _::NeverDone();
// forever (useful for servers).
template <typename Func>
PromiseForResult<Func, void> evalLater(Func&& func);
PromiseForResult<Func, void> evalLater(Func&& func) KJ_WARN_UNUSED_RESULT;
// Schedule for the given zero-parameter function to be executed in the event loop at some
// point in the near future. Returns a Promise for its result -- or, if `func()` itself returns
// a promise, `evalLater()` returns a Promise for the result of resolving that promise.
......@@ -344,6 +349,12 @@ PromiseForResult<Func, void> evalLater(Func&& func);
// If you schedule several evaluations with `evalLater` during the same callback, they are
// guaranteed to be executed in order.
template <typename Func>
PromiseForResult<Func, void> evalNow(Func&& func) KJ_WARN_UNUSED_RESULT;
// Run `func()` and return a promise for its result. `func()` executes before `evalNow()` returns.
// If `func()` throws an exception, the exception is caught and wrapped in a promise -- this is the
// main reason why `evalNow()` is useful.
template <typename T>
Promise<Array<T>> joinPromises(Array<Promise<T>>&& promises);
// Join an array of promises into a promise for an array.
......
......@@ -82,13 +82,13 @@ public:
// Equivalent to atTime(now() + delay).
template <typename T>
Promise<T> timeoutAt(TimePoint time, Promise<T>&& promise);
Promise<T> timeoutAt(TimePoint time, Promise<T>&& promise) KJ_WARN_UNUSED_RESULT;
// Return a promise equivalent to `promise` but which throws an exception (and cancels the
// original promise) if it hasn't completed by `time`. The thrown exception is of type
// "OVERLOADED".
template <typename T>
Promise<T> timeoutAfter(Duration delay, Promise<T>&& promise);
Promise<T> timeoutAfter(Duration delay, Promise<T>&& promise) KJ_WARN_UNUSED_RESULT;
// Return a promise equivalent to `promise` but which throws an exception (and cancels the
// original promise) if it hasn't completed after `delay` from now. The thrown exception is of
// type "OVERLOADED".
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment