Commit 95016602 authored by Lee Howes's avatar Lee Howes Committed by Facebook Github Bot

Consistency with folly::SemiFuture continuations 4/n: thenError

Summary:
Step 4 in adding full set of r-value-qualified unambiguous continuation methods to folly::Future for consistency with folly::SemiFuture.
 * Adds r-value qualified thenError that just call onError internally but in a more type safe obvious way.
 * Makes exception selection explicit to avoid exception_wrapper exception-type ambiguity in continuations.

Reviewed By: yfeldblum

Differential Revision: D7971326

fbshipit-source-id: 8aa94161abb629d94ee8da6fb0a4bca991cf24e4
parent c7fd4b4b
......@@ -972,6 +972,33 @@ Future<T>::thenValue(F&& func) && {
});
}
template <class T>
template <class ExceptionType, class F>
Future<T> Future<T>::thenError(F&& func) && {
// Forward to onError but ensure that returned future carries the executor
// Allow for applying to future with null executor while this is still
// possible.
auto* e = this->getExecutor();
return onError([func = std::forward<F>(func)](ExceptionType& ex) mutable {
return std::forward<F>(func)(ex);
})
.via(e ? e : &folly::InlineExecutor::instance());
}
template <class T>
template <class F>
Future<T> Future<T>::thenError(F&& func) && {
// Forward to onError but ensure that returned future carries the executor
// Allow for applying to future with null executor while this is still
// possible.
auto* e = this->getExecutor();
return onError([func = std::forward<F>(func)](
folly::exception_wrapper&& ex) mutable {
return std::forward<F>(func)(std::move(ex));
})
.via(e ? e : &folly::InlineExecutor::instance());
}
template <class T>
Future<Unit> Future<T>::then() {
return then([]() {});
......
......@@ -439,10 +439,10 @@ class SemiFuture : private futures::detail::FutureBase<T> {
return std::move(*this).deferValue(&func);
}
/// Set an error callback for this SemiFuture. The callback should take a
/// single argument of the type that you want to catch, and should return a
/// value of the same type as this SemiFuture, or a SemiFuture of that type
/// (see overload below). For instance,
/// Set an error continuation for this SemiFuture. The continuation should
/// take a single argument of the type that you want to catch, and should
/// return a value of the same type as this SemiFuture, or a SemiFuture of
/// that type (see overload below). For instance,
///
/// makeSemiFuture()
/// .defer([] {
......@@ -461,7 +461,7 @@ class SemiFuture : private futures::detail::FutureBase<T> {
SemiFuture<T>>::type
deferError(F&& func) &&;
/// Overload of deferError where the error callback returns a Future<T>
/// Overload of deferError where the error continuation returns a Future<T>
template <class F>
typename std::enable_if<
!futures::detail::callableWith<F, exception_wrapper>::value &&
......@@ -804,6 +804,40 @@ class Future : private futures::detail::FutureBase<T> {
return std::move(*this).thenValue(&func);
}
/// Set an error callback for this Future. The callback should take a
/// single argument of the type that you want to catch, and should return
/// T, SemiFuture<T> or Future<T>
/// (see overload below). For instance,
///
/// makeFuture()
/// .thenTry([] {
/// throw std::runtime_error("oh no!");
/// return 42;
/// })
/// .thenError<std::runtime_error>([] (auto const& e) {
/// LOG(INFO) << "std::runtime_error: " << e.what();
/// return -1; // or makeSemiFuture<int>(-1)
/// });
/// Overload of thenError where continuation can be called with a known
/// exception type and returns T or Future<T>
template <class ExceptionType, class F>
Future<T> thenError(F&& func) &&;
template <class ExceptionType, class R, class... Args>
Future<T> thenError(R (&func)(Args...)) && {
return std::move(*this).template thenError<ExceptionType>(&func);
}
/// Overload of thenError where continuation can be called with
/// exception_wrapper&& and returns T or Future<T>
template <class F>
Future<T> thenError(F&& func) &&;
template <class R, class... Args>
Future<T> thenError(R (&func)(Args...)) && {
return std::move(*this).thenError(&func);
}
/// Convenience method for ignoring the value and creating a Future<Unit>.
/// Exceptions still propagate.
/// This function is identical to .unit().
......
......@@ -292,6 +292,9 @@ Future<int> onErrorHelperEggs(const eggs_t&) {
Future<int> onErrorHelperGeneric(const std::exception&) {
return makeFuture(20);
}
Future<int> onErrorHelperWrapper(folly::exception_wrapper&&) {
return makeFuture(30);
}
} // namespace
TEST(Future, onError) {
......@@ -430,6 +433,26 @@ TEST(Future, onError) {
.onError(onErrorHelperEggs);
EXPECT_THROW(f.value(), std::runtime_error);
}
{
auto f = makeFuture()
.then([]() -> int { throw eggs; })
.thenError<eggs_t>(onErrorHelperEggs)
.thenError<std::exception>(onErrorHelperGeneric);
EXPECT_EQ(10, f.value());
}
{
auto f = makeFuture()
.then([]() -> int { throw std::runtime_error("test"); })
.thenError<eggs_t>(onErrorHelperEggs)
.thenError(onErrorHelperWrapper);
EXPECT_EQ(30, f.value());
}
{
auto f = makeFuture()
.then([]() -> int { throw std::runtime_error("test"); })
.thenError<eggs_t>(onErrorHelperEggs);
EXPECT_THROW(f.value(), std::runtime_error);
}
// No throw
{
......@@ -574,6 +597,280 @@ TEST(Future, onError) {
#undef EXPECT_NO_FLAG
}
TEST(Future, thenError) {
bool theFlag = false;
auto flag = [&] { theFlag = true; };
#define EXPECT_FLAG() \
do { \
EXPECT_TRUE(theFlag); \
theFlag = false; \
} while (0);
#define EXPECT_NO_FLAG() \
do { \
EXPECT_FALSE(theFlag); \
theFlag = false; \
} while (0);
// By reference
{
auto f = makeFuture()
.then([] { throw eggs; })
.thenError<eggs_t>([&](const eggs_t& /* e */) { flag(); });
EXPECT_FLAG();
EXPECT_NO_THROW(f.value());
}
// By auto reference
{
auto f = makeFuture()
.then([] { throw eggs; })
.thenError<eggs_t>([&](auto const& /* e */) { flag(); });
EXPECT_FLAG();
EXPECT_NO_THROW(f.value());
}
{
auto f =
makeFuture().then([] { throw eggs; }).onError([&](eggs_t& /* e */) {
flag();
return makeFuture();
});
EXPECT_FLAG();
EXPECT_NO_THROW(f.value());
}
// By value
{
auto f = makeFuture().then([] { throw eggs; }).onError([&](eggs_t /* e */) {
flag();
});
EXPECT_FLAG();
EXPECT_NO_THROW(f.value());
}
{
auto f = makeFuture().then([] { throw eggs; }).onError([&](eggs_t /* e */) {
flag();
return makeFuture();
});
EXPECT_FLAG();
EXPECT_NO_THROW(f.value());
}
// Polymorphic
{
auto f = makeFuture()
.then([] { throw eggs; })
.onError([&](std::exception& /* e */) { flag(); });
EXPECT_FLAG();
EXPECT_NO_THROW(f.value());
}
{
auto f = makeFuture()
.then([] { throw eggs; })
.onError([&](std::exception& /* e */) {
flag();
return makeFuture();
});
EXPECT_FLAG();
EXPECT_NO_THROW(f.value());
}
// Non-exceptions
{
auto f = makeFuture().then([] { throw - 1; }).onError([&](int /* e */) {
flag();
});
EXPECT_FLAG();
EXPECT_NO_THROW(f.value());
}
{
auto f = makeFuture().then([] { throw - 1; }).onError([&](int /* e */) {
flag();
return makeFuture();
});
EXPECT_FLAG();
EXPECT_NO_THROW(f.value());
}
// Mutable lambda
{
auto f = makeFuture()
.then([] { throw eggs; })
.onError([&](eggs_t& /* e */) mutable { flag(); });
EXPECT_FLAG();
EXPECT_NO_THROW(f.value());
}
{
auto f = makeFuture()
.then([] { throw eggs; })
.onError([&](eggs_t& /* e */) mutable {
flag();
return makeFuture();
});
EXPECT_FLAG();
EXPECT_NO_THROW(f.value());
}
// Function pointer
{
auto f = makeFuture()
.then([]() -> int { throw eggs; })
.onError(onErrorHelperEggs)
.onError(onErrorHelperGeneric);
EXPECT_EQ(10, f.value());
}
{
auto f = makeFuture()
.then([]() -> int { throw std::runtime_error("test"); })
.onError(onErrorHelperEggs)
.onError(onErrorHelperGeneric);
EXPECT_EQ(20, f.value());
}
{
auto f = makeFuture()
.then([]() -> int { throw std::runtime_error("test"); })
.onError(onErrorHelperEggs);
EXPECT_THROW(f.value(), std::runtime_error);
}
// No throw
{
auto f = makeFuture().then([] { return 42; }).onError([&](eggs_t& /* e */) {
flag();
return -1;
});
EXPECT_NO_FLAG();
EXPECT_EQ(42, f.value());
}
{
auto f = makeFuture().then([] { return 42; }).onError([&](eggs_t& /* e */) {
flag();
return makeFuture<int>(-1);
});
EXPECT_NO_FLAG();
EXPECT_EQ(42, f.value());
}
// Catch different exception
{
auto f = makeFuture()
.then([] { throw eggs; })
.onError([&](std::runtime_error& /* e */) { flag(); });
EXPECT_NO_FLAG();
EXPECT_THROW(f.value(), eggs_t);
}
{
auto f = makeFuture()
.then([] { throw eggs; })
.onError([&](std::runtime_error& /* e */) {
flag();
return makeFuture();
});
EXPECT_NO_FLAG();
EXPECT_THROW(f.value(), eggs_t);
}
// Returned value propagates
{
auto f = makeFuture()
.then([]() -> int { throw eggs; })
.onError([&](eggs_t& /* e */) { return 42; });
EXPECT_EQ(42, f.value());
}
// Returned future propagates
{
auto f = makeFuture()
.then([]() -> int { throw eggs; })
.onError([&](eggs_t& /* e */) { return makeFuture<int>(42); });
EXPECT_EQ(42, f.value());
}
// Throw in callback
{
auto f = makeFuture()
.then([]() -> int { throw eggs; })
.onError([&](eggs_t& e) -> int { throw e; });
EXPECT_THROW(f.value(), eggs_t);
}
{
auto f = makeFuture()
.then([]() -> int { throw eggs; })
.onError([&](eggs_t& e) -> Future<int> { throw e; });
EXPECT_THROW(f.value(), eggs_t);
}
// exception_wrapper, return Future<T>
{
auto f = makeFuture()
.then([] { throw eggs; })
.onError([&](exception_wrapper /* e */) {
flag();
return makeFuture();
});
EXPECT_FLAG();
EXPECT_NO_THROW(f.value());
}
// exception_wrapper, return Future<T> but throw
{
auto f = makeFuture()
.then([]() -> int { throw eggs; })
.onError([&](exception_wrapper /* e */) -> Future<int> {
flag();
throw eggs;
});
EXPECT_FLAG();
EXPECT_THROW(f.value(), eggs_t);
}
// exception_wrapper, return T
{
auto f = makeFuture()
.then([]() -> int { throw eggs; })
.onError([&](exception_wrapper /* e */) {
flag();
return -1;
});
EXPECT_FLAG();
EXPECT_EQ(-1, f.value());
}
// exception_wrapper, return T but throw
{
auto f = makeFuture()
.then([]() -> int { throw eggs; })
.onError([&](exception_wrapper /* e */) -> int {
flag();
throw eggs;
});
EXPECT_FLAG();
EXPECT_THROW(f.value(), eggs_t);
}
// const exception_wrapper&
{
auto f = makeFuture()
.then([] { throw eggs; })
.onError([&](const exception_wrapper& /* e */) {
flag();
return makeFuture();
});
EXPECT_FLAG();
EXPECT_NO_THROW(f.value());
}
#undef EXPECT_FLAG
#undef EXPECT_NO_FLAG
}
TEST(Future, special) {
EXPECT_FALSE(std::is_copy_constructible<Future<int>>::value);
EXPECT_FALSE(std::is_copy_assignable<Future<int>>::value);
......
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