Commit 63ac6323 authored by Alex Clarke's avatar Alex Clarke Committed by Commit Bot

Promises: Implement Promise<> handle with Then and Catch

Implements Then and Catch on top of AbstractPromise as well as
ManualPromiseResolver<> which is needed for testing at this
stage (PostTask doesn't yet return a promise). It's also
necessary for promises to interface with old style callback
based libraries which are extremly common in Chromium.

Design: https://docs.google.com/document/d/1l12PAJgEtlrqTXKiw6mk2cR2jP7FAfCCDr-DGIdiC9w/edit

Bug: 906125
Change-Id: Id7206698f37c59cfc201f3970cbf66a2ef89ee4e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1599620
Commit-Queue: Alex Clarke <alexclarke@chromium.org>
Reviewed-by: default avatarEtienne Pierre-Doray <etiennep@chromium.org>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/master@{#661045}
parent 435bb171
...@@ -755,9 +755,14 @@ jumbo_component("base") { ...@@ -755,9 +755,14 @@ jumbo_component("base") {
"task/promise/abstract_promise.h", "task/promise/abstract_promise.h",
"task/promise/dependent_list.cc", "task/promise/dependent_list.cc",
"task/promise/dependent_list.h", "task/promise/dependent_list.h",
"task/promise/helpers.h",
"task/promise/no_op_promise_executor.cc", "task/promise/no_op_promise_executor.cc",
"task/promise/no_op_promise_executor.h", "task/promise/no_op_promise_executor.h",
"task/promise/promise.h",
"task/promise/promise_result.h",
"task/promise/small_unique_object.h", "task/promise/small_unique_object.h",
"task/promise/then_and_catch_executor.cc",
"task/promise/then_and_catch_executor.h",
"task/scoped_set_task_priority_for_current_thread.cc", "task/scoped_set_task_priority_for_current_thread.cc",
"task/scoped_set_task_priority_for_current_thread.h", "task/scoped_set_task_priority_for_current_thread.h",
"task/sequence_manager/associated_thread_id.cc", "task/sequence_manager/associated_thread_id.cc",
...@@ -2631,6 +2636,8 @@ test("base_unittests") { ...@@ -2631,6 +2636,8 @@ test("base_unittests") {
"task/post_task_unittest.cc", "task/post_task_unittest.cc",
"task/promise/abstract_promise_unittest.cc", "task/promise/abstract_promise_unittest.cc",
"task/promise/dependent_list_unittest.cc", "task/promise/dependent_list_unittest.cc",
"task/promise/helpers_unittest.cc",
"task/promise/promise_unittest.cc",
"task/scoped_set_task_priority_for_current_thread_unittest.cc", "task/scoped_set_task_priority_for_current_thread_unittest.cc",
"task/sequence_manager/atomic_flag_set_unittest.cc", "task/sequence_manager/atomic_flag_set_unittest.cc",
"task/sequence_manager/lazily_deallocated_deque_unittest.cc", "task/sequence_manager/lazily_deallocated_deque_unittest.cc",
...@@ -3034,6 +3041,7 @@ if (enable_nocompile_tests) { ...@@ -3034,6 +3041,7 @@ if (enable_nocompile_tests) {
"observer_list_unittest.nc", "observer_list_unittest.nc",
"optional_unittest.nc", "optional_unittest.nc",
"strings/string16_unittest.nc", "strings/string16_unittest.nc",
"task/promise/promise_unittest.nc",
"task/task_traits_extension_unittest.nc", "task/task_traits_extension_unittest.nc",
"task/task_traits_unittest.nc", "task/task_traits_unittest.nc",
"thread_annotations_unittest.nc", "thread_annotations_unittest.nc",
......
...@@ -19,14 +19,15 @@ struct FakeBindState; ...@@ -19,14 +19,15 @@ struct FakeBindState;
namespace internal { namespace internal {
class CallbackBase; class BaseThenAndCatchExecutor;
class CallbackBaseCopyable;
class BindStateBase; class BindStateBase;
template <typename Functor, typename... BoundArgs> template <typename Functor, typename... BoundArgs>
struct BindState; struct BindState;
class CallbackBase;
class CallbackBaseCopyable;
struct BindStateBaseRefCountTraits { struct BindStateBaseRefCountTraits {
static void Destruct(const BindStateBase*); static void Destruct(const BindStateBase*);
}; };
...@@ -135,6 +136,8 @@ class BASE_EXPORT CallbackBase { ...@@ -135,6 +136,8 @@ class BASE_EXPORT CallbackBase {
void Reset(); void Reset();
protected: protected:
friend class BaseThenAndCatchExecutor;
using InvokeFuncStorage = BindStateBase::InvokeFuncStorage; using InvokeFuncStorage = BindStateBase::InvokeFuncStorage;
// Returns true if this callback equals |other|. |other| may be null. // Returns true if this callback equals |other|. |other| may be null.
......
...@@ -47,6 +47,11 @@ const AbstractPromise* AbstractPromise::FindNonCurriedAncestor() const { ...@@ -47,6 +47,11 @@ const AbstractPromise* AbstractPromise::FindNonCurriedAncestor() const {
void AbstractPromise::AddAsDependentForAllPrerequisites() { void AbstractPromise::AddAsDependentForAllPrerequisites() {
DCHECK(prerequisites_); DCHECK(prerequisites_);
// Note a curried promise will eventually get to all its children and pass
// them catch responsibility through AddAsDependentForAllPrerequisites,
// although that'll be done lazily (only once they resolve/reject, so there
// is a possibility the DCHECKs might be racy.
for (AdjacencyListNode& node : prerequisites_->prerequisite_list) { for (AdjacencyListNode& node : prerequisites_->prerequisite_list) {
node.dependent_node.dependent = this; node.dependent_node.dependent = this;
...@@ -196,20 +201,6 @@ void AbstractPromise::MaybeInheritChecks(AbstractPromise* prerequisite) { ...@@ -196,20 +201,6 @@ void AbstractPromise::MaybeInheritChecks(AbstractPromise* prerequisite) {
prerequisite->passed_catch_responsibility_ = true; prerequisite->passed_catch_responsibility_ = true;
} }
void AbstractPromise::PassCatchResponsibilityOntoDependentsForCurriedPromise(
DependentList::Node* dependent_list) {
CheckedAutoLock lock(GetCheckedLock());
if (!dependent_list)
return;
if (IsResolvedWithPromise()) {
for (DependentList::Node* node = dependent_list; node;
node = node->next.load(std::memory_order_relaxed)) {
node->dependent->MaybeInheritChecks(this);
}
}
}
AbstractPromise::LocationRef::LocationRef(const Location& from_here) AbstractPromise::LocationRef::LocationRef(const Location& from_here)
: from_here_(from_here) {} : from_here_(from_here) {}
...@@ -353,10 +344,6 @@ void AbstractPromise::OnResolvePostReadyDependents() { ...@@ -353,10 +344,6 @@ void AbstractPromise::OnResolvePostReadyDependents() {
DependentList::Node* dependent_list = dependents_.ConsumeOnceForResolve(); DependentList::Node* dependent_list = dependents_.ConsumeOnceForResolve();
dependent_list = NonThreadSafeReverseList(dependent_list); dependent_list = NonThreadSafeReverseList(dependent_list);
#if DCHECK_IS_ON()
PassCatchResponsibilityOntoDependentsForCurriedPromise(dependent_list);
#endif
// Propagate resolve to dependents. // Propagate resolve to dependents.
DependentList::Node* next; DependentList::Node* next;
for (DependentList::Node* node = dependent_list; node; node = next) { for (DependentList::Node* node = dependent_list; node; node = next) {
...@@ -374,10 +361,6 @@ void AbstractPromise::OnRejectPostReadyDependents() { ...@@ -374,10 +361,6 @@ void AbstractPromise::OnRejectPostReadyDependents() {
DependentList::Node* dependent_list = dependents_.ConsumeOnceForReject(); DependentList::Node* dependent_list = dependents_.ConsumeOnceForReject();
dependent_list = NonThreadSafeReverseList(dependent_list); dependent_list = NonThreadSafeReverseList(dependent_list);
#if DCHECK_IS_ON()
PassCatchResponsibilityOntoDependentsForCurriedPromise(dependent_list);
#endif
// Propagate rejection to dependents. We always propagate rejection // Propagate rejection to dependents. We always propagate rejection
// immediately. // immediately.
DependentList::Node* next; DependentList::Node* next;
...@@ -429,7 +412,8 @@ void AbstractPromise::OnCanceled() { ...@@ -429,7 +412,8 @@ void AbstractPromise::OnCanceled() {
void AbstractPromise::OnResolved() { void AbstractPromise::OnResolved() {
#if DCHECK_IS_ON() #if DCHECK_IS_ON()
DCHECK(executor_can_resolve_) << from_here_.ToString(); DCHECK(executor_can_resolve_ || IsResolvedWithPromise())
<< from_here_.ToString();
#endif #endif
if (IsResolvedWithPromise()) { if (IsResolvedWithPromise()) {
scoped_refptr<AbstractPromise> curried_promise = scoped_refptr<AbstractPromise> curried_promise =
......
...@@ -46,7 +46,7 @@ enum class RejectPolicy { ...@@ -46,7 +46,7 @@ enum class RejectPolicy {
// resolved. This lets us disambiguate promises with the same resolve and reject // resolved. This lets us disambiguate promises with the same resolve and reject
// type. // type.
template <typename T> template <typename T>
struct BASE_EXPORT Resolved { struct Resolved {
using Type = T; using Type = T;
static_assert(!std::is_same<T, NoReject>::value, static_assert(!std::is_same<T, NoReject>::value,
...@@ -64,7 +64,7 @@ struct BASE_EXPORT Resolved { ...@@ -64,7 +64,7 @@ struct BASE_EXPORT Resolved {
}; };
template <> template <>
struct BASE_EXPORT Resolved<void> { struct Resolved<void> {
using Type = void; using Type = void;
Void value; Void value;
}; };
...@@ -73,7 +73,7 @@ struct BASE_EXPORT Resolved<void> { ...@@ -73,7 +73,7 @@ struct BASE_EXPORT Resolved<void> {
// rejected. This lets us disambiguate promises with the same resolve and reject // rejected. This lets us disambiguate promises with the same resolve and reject
// type. // type.
template <typename T> template <typename T>
struct BASE_EXPORT Rejected { struct Rejected {
using Type = T; using Type = T;
T value; T value;
...@@ -93,7 +93,7 @@ struct BASE_EXPORT Rejected { ...@@ -93,7 +93,7 @@ struct BASE_EXPORT Rejected {
}; };
template <> template <>
struct BASE_EXPORT Rejected<void> { struct Rejected<void> {
using Type = void; using Type = void;
Void value; Void value;
}; };
...@@ -399,10 +399,6 @@ class BASE_EXPORT AbstractPromise ...@@ -399,10 +399,6 @@ class BASE_EXPORT AbstractPromise
void MaybeInheritChecks(AbstractPromise* source) void MaybeInheritChecks(AbstractPromise* source)
EXCLUSIVE_LOCKS_REQUIRED(GetCheckedLock()); EXCLUSIVE_LOCKS_REQUIRED(GetCheckedLock());
// Does nothing if this promise wasn't resolved by a promise.
void PassCatchResponsibilityOntoDependentsForCurriedPromise(
DependentList::Node* dependent_list);
// Controls how we deal with unhandled rejection. // Controls how we deal with unhandled rejection.
const RejectPolicy reject_policy_; const RejectPolicy reject_policy_;
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_PROMISE_HELPERS_H_
#define BASE_TASK_PROMISE_HELPERS_H_
#include <type_traits>
#include "base/bind.h"
#include "base/callback.h"
#include "base/task/promise/abstract_promise.h"
#include "base/task/promise/promise_result.h"
namespace base {
namespace internal {
// PromiseCallbackTraits computes the resolve and reject types of a Promise
// from the return type of a resolve or reject callback.
//
// Usage example:
// using Traits = PromiseCallbackTraits<int>;
//
// Traits::
// ResolveType is int
// RejectType is NoReject
// could_resolve is true
// could_reject is false
template <typename T>
struct PromiseCallbackTraits {
using ResolveType = T;
using RejectType = NoReject;
static constexpr bool could_resolve = true;
static constexpr bool could_reject = false;
};
template <typename T>
struct PromiseCallbackTraits<Resolved<T>> {
using ResolveType = T;
using RejectType = NoReject;
static constexpr bool could_resolve = true;
static constexpr bool could_reject = false;
};
template <typename T>
struct PromiseCallbackTraits<Rejected<T>> {
using ResolveType = NoResolve;
using RejectType = T;
static constexpr bool could_resolve = false;
static constexpr bool could_reject = true;
};
template <typename Reject>
struct PromiseCallbackTraits<Promise<NoResolve, Reject>> {
using ResolveType = NoResolve;
using RejectType = Reject;
static constexpr bool could_resolve = false;
static constexpr bool could_reject = true;
};
template <typename Resolve>
struct PromiseCallbackTraits<Promise<Resolve, NoReject>> {
using ResolveType = Resolve;
using RejectType = NoReject;
static constexpr bool could_resolve = true;
static constexpr bool could_reject = false;
};
template <typename Resolve, typename Reject>
struct PromiseCallbackTraits<Promise<Resolve, Reject>> {
using ResolveType = Resolve;
using RejectType = Reject;
static constexpr bool could_resolve = true;
static constexpr bool could_reject = true;
};
template <typename Reject>
struct PromiseCallbackTraits<PromiseResult<NoResolve, Reject>> {
using ResolveType = NoResolve;
using RejectType = Reject;
static constexpr bool could_resolve = false;
static constexpr bool could_reject = true;
};
template <typename Resolve>
struct PromiseCallbackTraits<PromiseResult<Resolve, NoReject>> {
using ResolveType = Resolve;
using RejectType = NoReject;
static constexpr bool could_resolve = true;
static constexpr bool could_reject = false;
};
template <typename Resolve, typename Reject>
struct PromiseCallbackTraits<PromiseResult<Resolve, Reject>> {
using ResolveType = Resolve;
using RejectType = Reject;
static constexpr bool could_resolve = true;
static constexpr bool could_reject = true;
};
template <typename T>
struct IsScopedRefPtr {
static constexpr bool value = false;
};
template <typename T>
struct IsScopedRefPtr<scoped_refptr<T>> {
static constexpr bool value = true;
};
// UseMoveSemantics determines whether move semantics should be used to
// pass |T| as a function parameter.
//
// Usage example:
//
// UseMoveSemantics<std::unique_ptr<int>>::value; // is true
// UseMoveSemantics<int>::value; // is false
// UseMoveSemantics<scoped_refptr<Dummy>>::value; // is false
//
// Will give false positives for some copyable types, but that should be
// harmless.
template <typename T,
bool use_move = !std::is_reference<T>::value &&
!std::is_pointer<T>::value &&
!std::is_fundamental<std::decay_t<T>>::value &&
!IsScopedRefPtr<T>::value>
struct UseMoveSemantics : public std::integral_constant<bool, use_move> {
static_assert(!std::is_rvalue_reference<T>::value,
"Promise<T&&> not supported");
static constexpr AbstractPromise::Executor::ArgumentPassingType
argument_passing_type =
use_move ? AbstractPromise::Executor::ArgumentPassingType::kMove
: AbstractPromise::Executor::ArgumentPassingType::kNormal;
};
// CallbackTraits extracts properties relevant to Promises from a callback.
//
// Usage example:
//
// using Traits = CallbackTraits<
// base::OnceCallback<PromiseResult<int, std::string>(float)>;
//
// Traits::
// ResolveType is int
// RejectType is std::string
// ArgType is float
// ReturnType is PromiseResult<int, std::string>
// SignatureType is PromiseResult<int, std::string>(float)
// argument_passing_type is kNormal
template <typename T>
struct CallbackTraits;
template <typename T>
struct CallbackTraits<base::OnceCallback<T()>> {
using ResolveType = typename internal::PromiseCallbackTraits<T>::ResolveType;
using RejectType = typename internal::PromiseCallbackTraits<T>::RejectType;
using ArgType = void;
using ReturnType = T;
using SignatureType = T();
static constexpr AbstractPromise::Executor::ArgumentPassingType
argument_passing_type =
AbstractPromise::Executor::ArgumentPassingType::kNormal;
};
template <typename T>
struct CallbackTraits<base::RepeatingCallback<T()>> {
using ResolveType = typename internal::PromiseCallbackTraits<T>::ResolveType;
using RejectType = typename internal::PromiseCallbackTraits<T>::RejectType;
using ArgType = void;
using ReturnType = T;
using SignatureType = T();
static constexpr AbstractPromise::Executor::ArgumentPassingType
argument_passing_type =
AbstractPromise::Executor::ArgumentPassingType::kNormal;
};
template <typename T, typename Arg>
struct CallbackTraits<base::OnceCallback<T(Arg)>> {
using ResolveType = typename internal::PromiseCallbackTraits<T>::ResolveType;
using RejectType = typename internal::PromiseCallbackTraits<T>::RejectType;
using ArgType = Arg;
using ReturnType = T;
using SignatureType = T(Arg);
static constexpr AbstractPromise::Executor::ArgumentPassingType
argument_passing_type = UseMoveSemantics<Arg>::argument_passing_type;
};
template <typename T, typename Arg>
struct CallbackTraits<base::RepeatingCallback<T(Arg)>> {
using ResolveType = typename internal::PromiseCallbackTraits<T>::ResolveType;
using RejectType = typename internal::PromiseCallbackTraits<T>::RejectType;
using ArgType = Arg;
using ReturnType = T;
using SignatureType = T(Arg);
static constexpr AbstractPromise::Executor::ArgumentPassingType
argument_passing_type = UseMoveSemantics<Arg>::argument_passing_type;
};
// Helper for combining the resolve types of two promises.
template <typename A, typename B>
struct ResolveCombinerHelper {
using Type = A;
static constexpr bool valid = false;
};
template <typename A>
struct ResolveCombinerHelper<A, A> {
using Type = A;
static constexpr bool valid = true;
};
template <typename B>
struct ResolveCombinerHelper<NoResolve, B> {
using Type = B;
static constexpr bool valid = true;
};
template <typename A>
struct ResolveCombinerHelper<A, NoResolve> {
using Type = A;
static constexpr bool valid = true;
};
template <>
struct ResolveCombinerHelper<NoResolve, NoResolve> {
using Type = NoResolve;
static constexpr bool valid = true;
};
// Helper for combining the reject types of two promises.
template <typename A, typename B>
struct RejectCombinerHelper {
using Type = A;
static constexpr bool valid = false;
};
template <typename A>
struct RejectCombinerHelper<A, A> {
using Type = A;
static constexpr bool valid = true;
};
template <typename B>
struct RejectCombinerHelper<NoReject, B> {
using Type = B;
static constexpr bool valid = true;
};
template <typename A>
struct RejectCombinerHelper<A, NoReject> {
using Type = A;
static constexpr bool valid = true;
};
template <>
struct RejectCombinerHelper<NoReject, NoReject> {
using Type = NoReject;
static constexpr bool valid = true;
};
// Helper that computes and validates the return type for combining promises.
// Essentially the promise types have to match unless there's NoResolve or
// or NoReject in which case they can be combined.
template <typename ThenReturnResolveT,
typename ThenReturnRejectT,
typename CatchReturnResolveT,
typename CatchReturnRejectT>
struct PromiseCombiner {
using ResolveHelper =
ResolveCombinerHelper<ThenReturnResolveT, CatchReturnResolveT>;
using RejectHelper =
RejectCombinerHelper<ThenReturnRejectT, CatchReturnRejectT>;
using ResolveType = typename ResolveHelper::Type;
using RejectType = typename RejectHelper::Type;
static constexpr bool valid = ResolveHelper::valid && RejectHelper::valid;
};
// TODO(alexclarke): Specialize |CallbackTraits| for callbacks with more than
// one argument to support Promises::All.
template <typename RejectStorage>
struct EmplaceInnerHelper {
template <typename Resolve, typename Reject>
static void Emplace(AbstractPromise* promise,
PromiseResult<Resolve, Reject> result) {
promise->emplace(std::move(result.value()));
}
};
// TODO(alexclarke): Specialize |EmplaceInnerHelper| where RejectStorage is
// base::Variant to support Promises::All.
template <typename ResolveStorage, typename RejectStorage>
struct EmplaceHelper {
template <typename Resolve, typename Reject>
static void Emplace(AbstractPromise* promise,
PromiseResult<Resolve, Reject>&& result) {
static_assert(std::is_same<typename ResolveStorage::Type, Resolve>::value ||
std::is_same<NoResolve, Resolve>::value,
"Resolve should match ResolveStorage");
static_assert(std::is_same<typename RejectStorage::Type, Reject>::value ||
std::is_same<NoReject, Reject>::value,
"Reject should match RejectStorage");
EmplaceInnerHelper<RejectStorage>::Emplace(promise, std::move(result));
}
template <typename Resolve, typename Reject>
static void Emplace(AbstractPromise* promise,
Promise<Resolve, Reject>&& result) {
static_assert(std::is_same<typename ResolveStorage::Type, Resolve>::value ||
std::is_same<NoResolve, Resolve>::value,
"Resolve should match ResolveStorage");
static_assert(std::is_same<typename RejectStorage::Type, Reject>::value ||
std::is_same<NoReject, Reject>::value,
"Reject should match RejectStorage");
promise->emplace(std::move(result.abstract_promise_));
}
template <typename Result>
static void Emplace(AbstractPromise* promise, Result&& result) {
static_assert(std::is_same<typename ResolveStorage::Type, Result>::value,
"Result should match ResolveStorage");
promise->emplace(Resolved<Result>{std::forward<Result>(result)});
}
template <typename Resolve>
static void Emplace(AbstractPromise* promise, Resolved<Resolve>&& resolved) {
static_assert(std::is_same<typename ResolveStorage::Type, Resolve>::value,
"Resolve should match ResolveStorage");
promise->emplace(std::move(resolved));
}
template <typename Reject>
static void Emplace(AbstractPromise* promise, Rejected<Reject>&& rejected) {
static_assert(std::is_same<typename RejectStorage::Type, Reject>::value,
"Reject should match RejectStorage");
promise->emplace(std::move(rejected));
}
};
// Helper that decides whether or not to std::move arguments for a callback
// based on the type the callback specifies (i.e. we don't need to move if the
// callback requests a reference).
template <typename CbArg, typename ArgStorageType>
class ArgMoveSemanticsHelper {
public:
static CbArg Get(AbstractPromise* arg) {
return GetImpl(arg, UseMoveSemantics<CbArg>());
}
private:
static CbArg GetImpl(AbstractPromise* arg, std::true_type should_move) {
return arg->TakeInnerValue<ArgStorageType>();
}
static CbArg GetImpl(AbstractPromise* arg, std::false_type should_move) {
return unique_any_cast<ArgStorageType>(&arg->value())->value;
}
};
// Helper for running a promise callback and storing the result if any.
//
// Callback = signature of the callback to execute,
// ArgStorageType = type of the callback parameter (pr void if none)
// ResolveStorage = type to use for resolve, usually Resolved<T>.
// RejectStorage = type to use for reject, usually Rejected<T>.
// TODO(alexclarke): Add support for Rejected<Variant<...>>.
template <typename Callback,
typename ArgStorageType,
typename ResolveStorage,
typename RejectStorage>
struct RunHelper;
// Run helper for callbacks with a single argument.
template <typename CbResult,
typename CbArg,
typename ArgStorageType,
typename ResolveStorage,
typename RejectStorage>
struct RunHelper<OnceCallback<CbResult(CbArg)>,
ArgStorageType,
ResolveStorage,
RejectStorage> {
using Callback = OnceCallback<CbResult(CbArg)>;
static void Run(Callback executor,
AbstractPromise* arg,
AbstractPromise* result) {
EmplaceHelper<ResolveStorage, RejectStorage>::Emplace(
result, std::move(executor).Run(
ArgMoveSemanticsHelper<CbArg, ArgStorageType>::Get(arg)));
}
};
// Run helper for callbacks with a single argument and void return value.
template <typename CbArg,
typename ArgStorageType,
typename ResolveStorage,
typename RejectStorage>
struct RunHelper<OnceCallback<void(CbArg)>,
ArgStorageType,
ResolveStorage,
RejectStorage> {
using Callback = OnceCallback<void(CbArg)>;
static void Run(Callback executor,
AbstractPromise* arg,
AbstractPromise* result) {
static_assert(std::is_void<typename ResolveStorage::Type>::value, "");
std::move(executor).Run(
ArgMoveSemanticsHelper<CbArg, ArgStorageType>::Get(arg));
result->emplace(Resolved<void>());
}
};
// Run helper for callbacks with no arguments.
template <typename CbResult,
typename ArgStorageType,
typename ResolveStorage,
typename RejectStorage>
struct RunHelper<OnceCallback<CbResult()>,
ArgStorageType,
ResolveStorage,
RejectStorage> {
using Callback = OnceCallback<CbResult()>;
static void Run(Callback executor,
AbstractPromise* arg,
AbstractPromise* result) {
EmplaceHelper<ResolveStorage, RejectStorage>::Emplace(
result, std::move(executor).Run());
}
};
// Run helper for callbacks with no arguments and void return type.
template <typename ArgStorageType,
typename ResolveStorage,
typename RejectStorage>
struct RunHelper<OnceCallback<void()>,
ArgStorageType,
ResolveStorage,
RejectStorage> {
static void Run(OnceCallback<void()> executor,
AbstractPromise* arg,
AbstractPromise* result) {
static_assert(std::is_void<typename ResolveStorage::Type>::value, "");
std::move(executor).Run();
result->emplace(Resolved<void>());
}
};
// TODO(alexclarke): Specialize RunHelper for callbacks unpacked from a tuple.
// Used by ManualPromiseResolver<> to generate callbacks.
template <typename T, typename... Args>
class PromiseCallbackHelper {
public:
using Callback = base::OnceCallback<void(Args&&...)>;
using RepeatingCallback = base::RepeatingCallback<void(Args&&...)>;
static Callback GetResolveCallback(scoped_refptr<AbstractPromise>& promise) {
return base::BindOnce(
[](scoped_refptr<AbstractPromise> promise, Args&&... args) {
promise->emplace(Resolved<T>{std::forward<Args>(args)...});
promise->OnResolved();
},
promise);
}
static RepeatingCallback GetRepeatingResolveCallback(
scoped_refptr<AbstractPromise>& promise) {
return base::BindRepeating(
[](scoped_refptr<AbstractPromise> promise, Args&&... args) {
promise->emplace(Resolved<T>{std::forward<Args>(args)...});
promise->OnResolved();
},
promise);
}
static Callback GetRejectCallback(scoped_refptr<AbstractPromise>& promise) {
return base::BindOnce(
[](scoped_refptr<AbstractPromise> promise, Args&&... args) {
promise->emplace(Rejected<T>{std::forward<Args>(args)...});
promise->OnRejected();
},
promise);
}
static RepeatingCallback GetRepeatingRejectCallback(
scoped_refptr<AbstractPromise>& promise) {
return base::BindRepeating(
[](scoped_refptr<AbstractPromise> promise, Args&&... args) {
promise->emplace(Rejected<T>{std::forward<Args>(args)...});
promise->OnRejected();
},
promise);
}
};
// Validates that the argument type |CallbackArgType| of a resolve or
// reject callback is compatible with the resolve or reject type
// |PromiseType| of this Promise.
template <typename PromiseType, typename CallbackArgType>
struct IsValidPromiseArg {
static constexpr bool value =
std::is_same<PromiseType, std::decay_t<CallbackArgType>>::value;
};
template <typename PromiseType, typename CallbackArgType>
struct IsValidPromiseArg<PromiseType&, CallbackArgType> {
static constexpr bool value =
std::is_same<PromiseType&, CallbackArgType>::value;
};
} // namespace internal
} // namespace base
#endif // BASE_TASK_PROMISE_HELPERS_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/task/promise/helpers.h"
#include "base/bind.h"
#include "base/task/promise/promise.h"
#include "base/test/bind_test_util.h"
#include "base/test/do_nothing_promise.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace internal {
TEST(UseMoveSemantics, GeneralTypes) {
static_assert(!UseMoveSemantics<int>::value,
"Integral types don't need to be moved");
static_assert(UseMoveSemantics<std::unique_ptr<int>>::value,
"Move only types should be moved");
static_assert(
!UseMoveSemantics<scoped_refptr<AbstractPromise>>::value,
"To support multiple callbacks scoped_refptr doesn't need to be moved.");
}
TEST(CallbackTraits, CallbackReferenceTypes) {
static_assert(
std::is_same<int&,
CallbackTraits<Callback<int&(int&)>>::ResolveType>::value,
"");
static_assert(
std::is_same<int&, CallbackTraits<Callback<int&(int&)>>::ArgType>::value,
"");
}
TEST(CallbackTraits, RepeatingCallbackReferenceTypes) {
static_assert(
std::is_same<
int&,
CallbackTraits<RepeatingCallback<int&(int&)>>::ResolveType>::value,
"");
static_assert(
std::is_same<
int&, CallbackTraits<RepeatingCallback<int&(int&)>>::ArgType>::value,
"");
}
TEST(PromiseCombiner, InvalidCombos) {
static_assert(!PromiseCombiner<Resolved<int>, Rejected<float>, Resolved<int>,
Rejected<bool>>::valid,
"Invalid, reject types differ");
static_assert(!PromiseCombiner<Resolved<int>, Rejected<float>, Resolved<void>,
Rejected<float>>::valid,
"Invalid, resolve types differ");
}
TEST(PromiseCombiner, TypesMatch) {
using Result = PromiseCombiner<Resolved<int>, Rejected<float>, Resolved<int>,
Rejected<float>>;
static_assert(Result::valid, "Types are the same, should match");
static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
"Resolve type should be int");
static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
"Resolve type should be float");
}
TEST(PromiseCombiner, NoResolve) {
using Result = PromiseCombiner<NoResolve, Rejected<float>, Resolved<int>,
Rejected<float>>;
static_assert(Result::valid, "Valid combination");
static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
"Resolve type should be int");
static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
"Resolve type should be float");
}
TEST(PromiseCombiner, NoResolve2) {
using Result = PromiseCombiner<Resolved<int>, Rejected<float>, NoResolve,
Rejected<float>>;
static_assert(Result::valid, "Valid combination");
static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
"Resolve type should be int");
static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
"Resolve type should be float");
}
TEST(PromiseCombiner, BothNoResolve) {
using Result =
PromiseCombiner<NoResolve, Rejected<float>, NoResolve, Rejected<float>>;
static_assert(Result::valid, "Valid combination");
static_assert(std::is_same<Result::ResolveType, NoResolve>::value,
"Resolve type should be NoResolve");
static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
"Resolve type should be float");
}
TEST(PromiseCombiner, NoReject) {
using Result =
PromiseCombiner<Resolved<int>, NoReject, Resolved<int>, Rejected<float>>;
static_assert(Result::valid, "Valid combination");
static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
"Resolve type should be int");
static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
"Resolve type should be float");
}
TEST(PromiseCombiner, NoReject2) {
using Result =
PromiseCombiner<Resolved<int>, Rejected<float>, Resolved<int>, NoReject>;
static_assert(Result::valid, "Valid combination");
static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
"Resolve type should be int");
static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
"Resolve type should be float");
}
TEST(PromiseCombiner, BothNoReject) {
using Result =
PromiseCombiner<Resolved<int>, NoReject, Resolved<int>, NoReject>;
static_assert(Result::valid, "Valid combination");
static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
"Resolve type should be int");
static_assert(std::is_same<Result::RejectType, NoReject>::value,
"Resolve type should be NoReject");
}
TEST(PromiseCombiner, NoResolveAndNoReject) {
using Result =
PromiseCombiner<Resolved<int>, NoReject, NoResolve, Rejected<float>>;
static_assert(Result::valid, "Valid combination");
static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
"Resolve type should be int");
static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
"Resolve type should be float");
}
TEST(PromiseCombiner, NoResolveAndNoReject2) {
using Result =
PromiseCombiner<NoResolve, Rejected<float>, Resolved<int>, NoReject>;
static_assert(Result::valid, "Valid combination");
static_assert(std::is_same<Result::ResolveType, Resolved<int>>::value,
"Resolve type should be int");
static_assert(std::is_same<Result::RejectType, Rejected<float>>::value,
"Resolve type should be float");
}
TEST(EmplaceHelper, EmplacePromiseResult) {
scoped_refptr<AbstractPromise> resolve = DoNothingPromiseBuilder(FROM_HERE);
scoped_refptr<AbstractPromise> reject = DoNothingPromiseBuilder(FROM_HERE);
EmplaceHelper<Resolved<int>, Rejected<std::string>>::Emplace(
resolve.get(), PromiseResult<int, std::string>(123));
EmplaceHelper<Resolved<int>, Rejected<std::string>>::Emplace(
reject.get(), PromiseResult<int, std::string>("Oh no!"));
EXPECT_EQ(unique_any_cast<Resolved<int>>(resolve->value()).value, 123);
EXPECT_EQ(unique_any_cast<Rejected<std::string>>(reject->value()).value,
"Oh no!");
}
TEST(EmplaceHelper, EmplacePromise) {
scoped_refptr<AbstractPromise> resolve = DoNothingPromiseBuilder(FROM_HERE);
scoped_refptr<AbstractPromise> curried = DoNothingPromiseBuilder(FROM_HERE);
EmplaceHelper<Resolved<int>, Rejected<NoReject>>::Emplace(
resolve.get(), Promise<int>(std::move(curried)));
EXPECT_TRUE(resolve->IsResolvedWithPromise());
}
TEST(EmplaceHelper, NakedType) {
scoped_refptr<AbstractPromise> resolve = DoNothingPromiseBuilder(FROM_HERE);
EmplaceHelper<Resolved<int>, Rejected<NoReject>>::Emplace(resolve.get(), 123);
EXPECT_EQ(unique_any_cast<Resolved<int>>(resolve->value()).value, 123);
}
TEST(EmplaceHelper, ReferenceType) {
scoped_refptr<AbstractPromise> resolve = DoNothingPromiseBuilder(FROM_HERE);
int a = 12345;
EmplaceHelper<Resolved<int&>, Rejected<NoReject>>::Emplace<int&>(
resolve.get(), a);
EXPECT_EQ(unique_any_cast<Resolved<int&>>(resolve->value()).value, 12345);
}
TEST(EmplaceHelper, ResolvedInt) {
scoped_refptr<AbstractPromise> resolve = DoNothingPromiseBuilder(FROM_HERE);
EmplaceHelper<Resolved<int>, Rejected<NoReject>>::Emplace(resolve.get(),
Resolved<int>(123));
EXPECT_EQ(unique_any_cast<Resolved<int>>(resolve->value()).value, 123);
}
TEST(EmplaceHelper, RejectedString) {
scoped_refptr<AbstractPromise> resolve = DoNothingPromiseBuilder(FROM_HERE);
EmplaceHelper<Resolved<void>, Rejected<std::string>>::Emplace(
resolve.get(), Rejected<std::string>("Whoops!"));
EXPECT_EQ(unique_any_cast<Rejected<std::string>>(resolve->value()).value,
"Whoops!");
}
TEST(RunHelper, CallbackVoidArgumentIntResult) {
scoped_refptr<AbstractPromise> arg = DoNothingPromiseBuilder(FROM_HERE);
scoped_refptr<AbstractPromise> result = DoNothingPromiseBuilder(FROM_HERE);
RunHelper<OnceCallback<int()>, Resolved<void>, Resolved<int>,
Rejected<std::string>>::Run(BindOnce([]() { return 123; }),
arg.get(), result.get());
EXPECT_EQ(unique_any_cast<Resolved<int>>(result->value()).value, 123);
}
TEST(RunHelper, CallbackVoidArgumentVoidResult) {
scoped_refptr<AbstractPromise> arg = DoNothingPromiseBuilder(FROM_HERE);
scoped_refptr<AbstractPromise> result = DoNothingPromiseBuilder(FROM_HERE);
RunHelper<OnceCallback<void()>, Resolved<void>, Resolved<void>,
Rejected<std::string>>::Run(BindOnce([]() {}), arg.get(),
result.get());
EXPECT_EQ(result->value().type(), TypeId::From<Resolved<void>>());
}
TEST(RunHelper, CallbackIntArgumentIntResult) {
scoped_refptr<AbstractPromise> arg = DoNothingPromiseBuilder(FROM_HERE);
scoped_refptr<AbstractPromise> result = DoNothingPromiseBuilder(FROM_HERE);
arg->emplace(Resolved<int>(123));
RunHelper<OnceCallback<int(int)>, Resolved<int>, Resolved<int>,
Rejected<std::string>>::Run(BindOnce([](int value) {
return value + 1;
}),
arg.get(), result.get());
EXPECT_EQ(unique_any_cast<Resolved<int>>(result->value()).value, 124);
}
TEST(RunHelper, CallbackIntArgumentArgumentVoidResult) {
scoped_refptr<AbstractPromise> arg = DoNothingPromiseBuilder(FROM_HERE);
scoped_refptr<AbstractPromise> result = DoNothingPromiseBuilder(FROM_HERE);
arg->emplace(Resolved<int>(123));
int value;
RunHelper<OnceCallback<void(int)>, Resolved<int>, Resolved<void>,
Rejected<std::string>>::Run(BindLambdaForTesting([&](int arg) {
value = arg;
}),
arg.get(), result.get());
EXPECT_EQ(value, 123);
EXPECT_EQ(result->value().type(), TypeId::From<Resolved<void>>());
}
} // namespace internal
} // namespace base
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_PROMISE_PROMISE_H_
#define BASE_TASK_PROMISE_PROMISE_H_
#include "base/task/post_task.h"
#include "base/task/promise/helpers.h"
#include "base/task/promise/no_op_promise_executor.h"
#include "base/task/promise/promise_result.h"
#include "base/task/promise/then_and_catch_executor.h"
#include "base/threading/sequenced_task_runner_handle.h"
namespace base {
// Inspired by ES6 promises, Promise<> is a PostTask based callback system for
// asynchronous operations. An operation can resolve (succeed) with a value and
// optionally reject (fail) with a different result. Interested parties can be
// notified using ThenOn() and CatchOn() which schedule callbacks to run as
// appropriate on the specified task runner or task traits. If a promise is
// settled when a ThenOn() / CatchOn() / FinallyOn() statement is added, the
// callback will be posted immediately, otherwise it has to wait.
//
// Promise<> is copyable, moveable and thread safe. Under the hood
// internal::AbstractPromise is refcounted so retaining multiple Promises<> will
// prevent that part of the promise graph from being released.
template <typename ResolveType, typename RejectType = NoReject>
class Promise {
public:
Promise() : abstract_promise_(nullptr) {}
explicit Promise(
scoped_refptr<internal::AbstractPromise> abstract_promise) noexcept
: abstract_promise_(std::move(abstract_promise)) {}
// Constructs an unresolved promise for use by a ManualPromiseResolver<> and
// TaskRunner::PostPromise.
Promise(scoped_refptr<TaskRunner> task_runner,
const Location& location,
RejectPolicy reject_policy)
: abstract_promise_(MakeRefCounted<internal::AbstractPromise>(
std::move(task_runner),
location,
nullptr,
reject_policy,
internal::AbstractPromise::ConstructWith<
internal::DependentList::ConstructUnresolved,
internal::NoOpPromiseExecutor>(),
/* can_resolve */ !std::is_same<ResolveType, NoResolve>::value,
/* can_reject */ !std::is_same<RejectType, NoReject>::value)) {}
NOINLINE ~Promise() = default;
operator bool() const { return !!abstract_promise_; }
bool IsCancelledForTesting() const {
DCHECK(abstract_promise_);
return abstract_promise_->IsCanceled();
}
// A task to execute |on_reject| is posted on |task_runner| as soon as this
// promise (or an uncaught ancestor) is rejected. A Promise<> for the return
// value of |on_reject| is returned.
//
// The following callback return types have special meanings:
// 1. PromiseResult<Resolve, Reject> lets the callback resolve, reject or
// curry a Promise<Resolve, Reject>
// 2. Promise<Resolve, Reject> where the result is a curried promise.
//
// If a promise has multiple Catches they will be run in order of creation.
template <typename RejectCb>
NOINLINE auto CatchOn(scoped_refptr<TaskRunner> task_runner,
const Location& from_here,
RejectCb&& on_reject) noexcept {
DCHECK(abstract_promise_);
// Extract properties from the |on_reject| callback.
using RejectCallbackTraits = internal::CallbackTraits<RejectCb>;
using RejectCallbackArgT = typename RejectCallbackTraits::ArgType;
// Compute the resolve and reject types of the returned Promise.
using ReturnedPromiseTraits =
internal::PromiseCombiner<ResolveType,
NoReject, // We've caught the reject case.
typename RejectCallbackTraits::ResolveType,
typename RejectCallbackTraits::RejectType>;
using ReturnedPromiseResolveT = typename ReturnedPromiseTraits::ResolveType;
using ReturnedPromiseRejectT = typename ReturnedPromiseTraits::RejectType;
static_assert(!std::is_same<NoReject, RejectType>::value,
"Can't catch a NoReject promise.");
// Check we wouldn't need to return Promise<Variant<...>, ...>
static_assert(ReturnedPromiseTraits::valid,
"Ambiguous promise resolve type");
static_assert(
internal::IsValidPromiseArg<RejectType, RejectCallbackArgT>::value ||
std::is_void<RejectCallbackArgT>::value,
"|on_reject| callback must accept Promise::RejectType or void.");
return Promise<ReturnedPromiseResolveT, ReturnedPromiseRejectT>(
MakeRefCounted<internal::AbstractPromise>(
std::move(task_runner), from_here,
std::make_unique<internal::AbstractPromise::AdjacencyList>(
abstract_promise_),
RejectPolicy::kMustCatchRejection,
internal::AbstractPromise::ConstructWith<
internal::DependentList::ConstructUnresolved,
internal::ThenAndCatchExecutor<
OnceClosure, // Never called.
OnceCallback<typename RejectCallbackTraits::SignatureType>,
internal::NoCallback, RejectType,
Resolved<ReturnedPromiseResolveT>,
Rejected<ReturnedPromiseRejectT>>>(),
OnceClosure(),
static_cast<
OnceCallback<typename RejectCallbackTraits::SignatureType>>(
std::forward<RejectCb>(on_reject))));
}
template <typename RejectCb>
auto CatchOn(const TaskTraits& traits,
const Location& from_here,
RejectCb&& on_reject) noexcept {
return CatchOn(CreateTaskRunnerWithTraits(traits), from_here,
std::forward<RejectCb>(on_reject));
}
template <typename RejectCb>
auto CatchOnCurrent(const Location& from_here,
RejectCb&& on_reject) noexcept {
return CatchOn(SequencedTaskRunnerHandle::Get(), from_here,
std::forward<RejectCb>(on_reject));
}
// A task to execute |on_resolve| is posted on |task_runner| as soon as this
// promise (or an unhandled ancestor) is resolved. A Promise<> for the return
// value of |on_resolve| is returned.
//
// The following callback return types have special meanings:
// 1. PromiseResult<Resolve, Reject> lets the callback resolve, reject or
// curry a Promise<Resolve, Reject>
// 2. Promise<Resolve, Reject> where the result is a curried promise.
//
// If a promise has multiple Thens they will be run in order of creation.
template <typename ResolveCb>
NOINLINE auto ThenOn(scoped_refptr<TaskRunner> task_runner,
const Location& from_here,
ResolveCb&& on_resolve) noexcept {
DCHECK(abstract_promise_);
// Extract properties from the |on_resolve| callback.
using ResolveCallbackTraits =
internal::CallbackTraits<std::decay_t<ResolveCb>>;
using ResolveCallbackArgT = typename ResolveCallbackTraits::ArgType;
// Compute the resolve and reject types of the returned Promise.
using ReturnedPromiseTraits =
internal::PromiseCombiner<NoResolve, // We've caught the resolve case.
RejectType,
typename ResolveCallbackTraits::ResolveType,
typename ResolveCallbackTraits::RejectType>;
using ReturnedPromiseResolveT = typename ReturnedPromiseTraits::ResolveType;
using ReturnedPromiseRejectT = typename ReturnedPromiseTraits::RejectType;
// Check we wouldn't need to return Promise<..., Variant<...>>
static_assert(ReturnedPromiseTraits::valid,
"Ambiguous promise reject type");
static_assert(
internal::IsValidPromiseArg<ResolveType, ResolveCallbackArgT>::value ||
std::is_void<ResolveCallbackArgT>::value,
"|on_resolve| callback must accept Promise::ResolveType or void.");
return Promise<ReturnedPromiseResolveT, ReturnedPromiseRejectT>(
MakeRefCounted<internal::AbstractPromise>(
std::move(task_runner), from_here,
std::make_unique<internal::AbstractPromise::AdjacencyList>(
abstract_promise_),
RejectPolicy::kMustCatchRejection,
internal::AbstractPromise::ConstructWith<
internal::DependentList::ConstructUnresolved,
internal::ThenAndCatchExecutor<
OnceCallback<typename ResolveCallbackTraits::SignatureType>,
OnceClosure, ResolveType, internal::NoCallback,
Resolved<ReturnedPromiseResolveT>,
Rejected<ReturnedPromiseRejectT>>>(),
std::forward<ResolveCb>(on_resolve), OnceClosure()));
}
template <typename ResolveCb>
auto ThenOn(const TaskTraits& traits,
const Location& from_here,
ResolveCb&& on_resolve) noexcept {
return ThenOn(CreateTaskRunnerWithTraits(traits), from_here,
std::forward<ResolveCb>(on_resolve));
}
template <typename ResolveCb>
auto ThenOnCurrent(const Location& from_here,
ResolveCb&& on_resolve) noexcept {
return ThenOn(SequencedTaskRunnerHandle::Get(), from_here,
std::forward<ResolveCb>(on_resolve));
}
// A task to execute |on_reject| is posted on |task_runner| as soon as this
// promise (or an uncaught ancestor) is rejected. Likewise a task to execute
// |on_resolve| is posted on |task_runner| as soon as this promise (or an
// unhandled ancestor) is resolved. A Promise<> for the return value of
// |on_resolve| or |on_reject| is returned.
//
// The following callback return types have special meanings:
// 1. PromiseResult<Resolve, Reject> lets the callback resolve, reject or
// curry a Promise<Resolve, Reject>
// 2. Promise<Resolve, Reject> where the result is a curried promise.
//
// If a promise has multiple Catches/ Thens, they will be run in order of
// creation.
//
// Note if either |on_resolve| or |on_reject| are canceled (due to weak
// pointer invalidation), then the other must be canceled at the same time as
// well. This restriction only applies to this form of ThenOn/ThenOnCurrent.
template <typename ResolveCb, typename RejectCb>
NOINLINE auto ThenOn(scoped_refptr<TaskRunner> task_runner,
const Location& from_here,
ResolveCb&& on_resolve,
RejectCb&& on_reject) noexcept {
DCHECK(abstract_promise_);
// Extract properties from the |on_resolve| and |on_reject| callbacks.
using ResolveCallbackTraits = internal::CallbackTraits<ResolveCb>;
using RejectCallbackTraits = internal::CallbackTraits<RejectCb>;
using ResolveCallbackArgT = typename ResolveCallbackTraits::ArgType;
using RejectCallbackArgT = typename RejectCallbackTraits::ArgType;
// Compute the resolve and reject types of the returned Promise.
using ReturnedPromiseTraits =
internal::PromiseCombiner<typename ResolveCallbackTraits::ResolveType,
typename ResolveCallbackTraits::RejectType,
typename RejectCallbackTraits::ResolveType,
typename RejectCallbackTraits::RejectType>;
using ReturnedPromiseResolveT = typename ReturnedPromiseTraits::ResolveType;
using ReturnedPromiseRejectT = typename ReturnedPromiseTraits::RejectType;
static_assert(!std::is_same<NoReject, RejectType>::value,
"Can't catch a NoReject promise.");
static_assert(ReturnedPromiseTraits::valid,
"|on_resolve| callback and |on_resolve| callback must return "
"compatible types.");
static_assert(
internal::IsValidPromiseArg<ResolveType, ResolveCallbackArgT>::value ||
std::is_void<ResolveCallbackArgT>::value,
"|on_resolve| callback must accept Promise::ResolveType or void.");
static_assert(
internal::IsValidPromiseArg<RejectType, RejectCallbackArgT>::value ||
std::is_void<RejectCallbackArgT>::value,
"|on_reject| callback must accept Promise::RejectType or void.");
return Promise<ReturnedPromiseResolveT, ReturnedPromiseRejectT>(
MakeRefCounted<internal::AbstractPromise>(
std::move(task_runner), from_here,
std::make_unique<internal::AbstractPromise::AdjacencyList>(
abstract_promise_),
RejectPolicy::kMustCatchRejection,
internal::AbstractPromise::ConstructWith<
internal::DependentList::ConstructUnresolved,
internal::ThenAndCatchExecutor<
OnceCallback<typename ResolveCallbackTraits::SignatureType>,
OnceCallback<typename RejectCallbackTraits::SignatureType>,
ResolveType, RejectType, Resolved<ReturnedPromiseResolveT>,
Rejected<ReturnedPromiseRejectT>>>(),
static_cast<
OnceCallback<typename ResolveCallbackTraits::SignatureType>>(
std::forward<ResolveCb>(on_resolve)),
static_cast<
OnceCallback<typename RejectCallbackTraits::SignatureType>>(
std::forward<RejectCb>(on_reject))));
}
template <typename ResolveCb, typename RejectCb>
auto ThenOn(const TaskTraits& traits,
const Location& from_here,
ResolveCb&& on_resolve,
RejectCb&& on_reject) noexcept {
return ThenOn(CreateTaskRunnerWithTraits(traits), from_here,
std::forward<ResolveCb>(on_resolve),
std::forward<RejectCb>(on_reject));
}
template <typename ResolveCb, typename RejectCb>
auto ThenOnCurrent(const Location& from_here,
ResolveCb&& on_resolve,
RejectCb&& on_reject) noexcept {
return ThenOn(SequencedTaskRunnerHandle::Get(), from_here,
std::forward<ResolveCb>(on_resolve),
std::forward<RejectCb>(on_reject));
}
template <typename... Args>
NOINLINE static Promise<ResolveType, RejectType> CreateResolved(
const Location& from_here,
Args&&... args) noexcept {
scoped_refptr<internal::AbstractPromise> promise(
MakeRefCounted<internal::AbstractPromise>(
nullptr, from_here, nullptr, RejectPolicy::kMustCatchRejection,
internal::AbstractPromise::ConstructWith<
internal::DependentList::ConstructResolved,
internal::NoOpPromiseExecutor>(),
/* can_resolve */ true,
/* can_reject */ false));
promise->emplace(Resolved<ResolveType>{std::forward<Args>(args)...});
return Promise<ResolveType, RejectType>(std::move(promise));
}
template <typename... Args>
NOINLINE static Promise<ResolveType, RejectType> CreateRejected(
const Location& from_here,
Args&&... args) noexcept {
scoped_refptr<internal::AbstractPromise> promise(
MakeRefCounted<internal::AbstractPromise>(
nullptr, from_here, nullptr, RejectPolicy::kMustCatchRejection,
internal::AbstractPromise::ConstructWith<
internal::DependentList::ConstructRejected,
internal::NoOpPromiseExecutor>(),
/* can_resolve */ false,
/* can_reject */ true));
promise->emplace(Rejected<RejectType>{std::forward<Args>(args)...});
return Promise<ResolveType, RejectType>(std::move(promise));
}
using ResolveT = ResolveType;
using RejectT = RejectType;
private:
template <typename A, typename B>
friend class Promise;
template <typename A, typename B>
friend class PromiseResult;
template <typename RejectStorage, typename ResultStorage>
friend struct internal::EmplaceHelper;
template <typename A, typename B>
friend class ManualPromiseResolver;
scoped_refptr<internal::AbstractPromise> abstract_promise_;
};
// Used for manually resolving and rejecting a Promise. This is for
// compatibility with old code and will eventually be removed.
template <typename ResolveType, typename RejectType = NoReject>
class ManualPromiseResolver {
public:
using ResolveHelper = std::conditional_t<
std::is_void<ResolveType>::value,
internal::PromiseCallbackHelper<void>,
internal::PromiseCallbackHelper<ResolveType, ResolveType>>;
using RejectHelper = std::conditional_t<
std::is_void<RejectType>::value,
internal::PromiseCallbackHelper<void>,
internal::PromiseCallbackHelper<RejectType, RejectType>>;
ManualPromiseResolver(
const Location& from_here,
RejectPolicy reject_policy = RejectPolicy::kMustCatchRejection)
: promise_(SequencedTaskRunnerHandle::Get(), from_here, reject_policy) {}
template <typename... Args>
void Resolve(Args&&... arg) noexcept {
DCHECK(!promise_.abstract_promise_->IsResolved());
DCHECK(!promise_.abstract_promise_->IsRejected());
static_assert(!std::is_same<NoResolve, ResolveType>::value,
"Can't resolve a NoResolve promise.");
promise_.abstract_promise_->emplace(
Resolved<ResolveType>{std::forward<Args>(arg)...});
promise_.abstract_promise_->OnResolved();
}
template <typename... Args>
void Reject(Args&&... arg) noexcept {
DCHECK(!promise_.abstract_promise_->IsResolved());
DCHECK(!promise_.abstract_promise_->IsRejected());
static_assert(!std::is_same<NoReject, RejectType>::value,
"Can't reject a NoReject promise.");
promise_.abstract_promise_->emplace(
Rejected<RejectType>{std::forward<Args>(arg)...});
promise_.abstract_promise_->OnRejected();
}
typename ResolveHelper::Callback GetResolveCallback() {
return ResolveHelper::GetResolveCallback(promise_.abstract_promise_);
}
typename ResolveHelper::RepeatingCallback GetRepeatingResolveCallback() {
return ResolveHelper::GetRepeatingResolveCallback(
promise_.abstract_promise_);
}
typename RejectHelper::Callback GetRejectCallback() {
static_assert(!std::is_same<NoReject, RejectType>::value,
"Can't reject a NoReject promise.");
return RejectHelper::GetRejectCallback(promise_.abstract_promise_);
}
typename RejectHelper::RepeatingCallback GetRepeatingRejectCallback() {
static_assert(!std::is_same<NoReject, RejectType>::value,
"Can't reject a NoReject promise.");
return RejectHelper::GetRepeatingRejectCallback(promise_.abstract_promise_);
}
Promise<ResolveType, RejectType>& promise() { return promise_; }
private:
Promise<ResolveType, RejectType> promise_;
};
} // namespace base
#endif // BASE_TASK_PROMISE_PROMISE_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_PROMISE_PROMISE_RESULT_H_
#define BASE_TASK_PROMISE_PROMISE_RESULT_H_
#include <type_traits>
#include "base/containers/unique_any.h"
namespace base {
// An optional return type from a promise callback, which allows the callback to
// decide whether or not it should reject. E.g.
//
// enum class Error { kReason };
//
// PromiseResult<int, Error> MyFn() {
// int result;
// ...
// if (error)
// return Error::kReason;
// return result;
// }
//
// If ResolveType and RejectType are distinct PromiseResult's constructor will
// accept them. E.g.
//
// PromiseResult<int, std::string> pr(123); // Resolve
// PromiseResult<int, std::string> pr("whoops"); // Reject
//
// If ResolveType and RejectType are the same you need to use Resolved<> and
// Rejected<> to disambiguate. E.g.
//
// PromiseResult<int, int> pr(Resolved{123}); // Resolve
// PromiseResult<int, int> pr(Rejected{123}); // Reject
//
// PromiseResult<ResolveType, RejectType> has a constructor that accepts
// Promise<ResolveType, RejectType>.
template <typename ResolveType, typename RejectType = NoReject>
class PromiseResult {
public:
template <typename ResolveT = ResolveType,
typename RejectT = RejectType,
class Enable = std::enable_if<std::is_void<ResolveT>::value ^
std::is_void<RejectT>::value>>
PromiseResult() : PromiseResult(typename Analyze<void>::TagType()) {}
template <typename T>
PromiseResult(T&& t)
: PromiseResult(typename Analyze<std::decay_t<T>>::TagType(),
std::forward<T>(t)) {}
PromiseResult(PromiseResult&& other) noexcept = default;
PromiseResult(const PromiseResult&) = delete;
PromiseResult& operator=(const PromiseResult&) = delete;
unique_any& value() { return value_; }
private:
struct IsWrapped {};
struct IsResolved {};
struct IsRejected {};
struct IsPromise {};
// Helper that assigns one of the above tags for |T|.
template <typename T>
struct Analyze {
using DecayedT = std::decay_t<T>;
static constexpr bool is_resolve =
std::is_convertible<DecayedT, std::decay_t<ResolveType>>::value;
static constexpr bool is_reject =
std::is_convertible<DecayedT, std::decay_t<RejectType>>::value;
static_assert(!is_reject || !std::is_same<DecayedT, NoReject>::value,
"A NoReject promise can't reject");
static_assert(!std::is_same<std::decay_t<ResolveType>,
std::decay_t<RejectType>>::value,
"Ambiguous because ResolveType and RejectType are the same");
static_assert(is_resolve || is_reject,
"Argument matches neither resolve nor reject type.");
static_assert(
is_resolve != is_reject && (is_resolve || is_reject),
"Ambiguous because argument matches both ResolveType and RejectType");
using TagType = std::conditional_t<is_resolve, IsResolved, IsRejected>;
};
template <typename T>
struct Analyze<Resolved<T>> {
using TagType = IsWrapped;
static_assert(std::is_same<ResolveType, T>::value,
"T in Resolved<T> is not ResolveType");
};
template <typename T>
struct Analyze<Rejected<T>> {
static_assert(std::is_same<RejectType, T>::value,
"T in Rejected<T> is not RejectType");
static_assert(!std::is_same<RejectType, NoReject>::value,
"A NoReject promise can't reject");
using TagType = IsWrapped;
};
template <typename ResolveT, typename RejectT>
struct Analyze<Promise<ResolveT, RejectT>> {
using TagType = IsPromise;
static_assert(std::is_same<ResolveT, ResolveType>::value,
"Promise resolve types don't match");
static_assert(std::is_same<RejectT, RejectType>::value,
"Promise reject types don't match");
};
template <typename... Args>
PromiseResult(IsResolved, Args&&... args)
: value_(in_place_type_t<Resolved<ResolveType>>(),
std::forward<Args>(args)...) {}
template <typename... Args>
PromiseResult(IsRejected, Args&&... args)
: value_(in_place_type_t<Rejected<RejectType>>(),
std::forward<Args>(args)...) {}
PromiseResult(IsPromise, const Promise<ResolveType, RejectType>& promise)
: value_(promise.abstract_promise_) {}
template <typename T>
PromiseResult(IsWrapped, T&& t) : value_(std::forward<T>(t)) {}
unique_any value_;
};
} // namespace base
#endif // BASE_TASK_PROMISE_PROMISE_RESULT_H_
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/task/promise/promise.h"
#include "base/test/bind_test_util.h"
#include "base/test/do_nothing_promise.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/threading/thread.h"
#include "base/values.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::ElementsAre;
namespace base {
namespace {
void RecordOrder(std::vector<int>* run_order, int order) {
run_order->push_back(order);
}
class ObjectToDelete : public RefCounted<ObjectToDelete> {
public:
// |delete_flag| is set to true when this object is deleted
ObjectToDelete(bool* delete_flag) : delete_flag_(delete_flag) {
EXPECT_FALSE(*delete_flag_);
}
private:
friend class RefCounted<ObjectToDelete>;
~ObjectToDelete() { *delete_flag_ = true; }
bool* const delete_flag_;
DISALLOW_COPY_AND_ASSIGN(ObjectToDelete);
};
class MockObject {
public:
MockObject() = default;
void Task(scoped_refptr<ObjectToDelete>) {}
void Reply(scoped_refptr<ObjectToDelete>) {}
private:
DISALLOW_COPY_AND_ASSIGN(MockObject);
};
struct DummyError {};
} // namespace
class PromiseTest : public testing::Test {
public:
test::ScopedTaskEnvironment scoped_task_environment_;
};
TEST(PromiseMemoryLeakTest, TargetTaskRunnerClearsTasks) {
scoped_refptr<TestMockTimeTaskRunner> post_runner =
MakeRefCounted<TestMockTimeTaskRunner>();
scoped_refptr<TestMockTimeTaskRunner> reply_runner =
MakeRefCounted<TestMockTimeTaskRunner>(
TestMockTimeTaskRunner::Type::kBoundToThread);
MockObject mock_object;
bool delete_task_flag = false;
bool delete_reply_flag = false;
Promise<int>::CreateResolved(FROM_HERE, 42)
.ThenOn(post_runner, FROM_HERE,
BindOnce(&MockObject::Task, Unretained(&mock_object),
MakeRefCounted<ObjectToDelete>(&delete_task_flag)))
.ThenOnCurrent(
FROM_HERE,
BindOnce(&MockObject::Reply, Unretained(&mock_object),
MakeRefCounted<ObjectToDelete>(&delete_reply_flag)));
post_runner->ClearPendingTasks();
post_runner = nullptr;
reply_runner = nullptr;
EXPECT_TRUE(delete_task_flag);
EXPECT_TRUE(delete_reply_flag);
}
TEST_F(PromiseTest, GetResolveCallbackThen) {
ManualPromiseResolver<int> p(FROM_HERE);
p.GetResolveCallback().Run(123);
RunLoop run_loop;
p.promise().ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
EXPECT_EQ(123, result);
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(PromiseTest, GetRejectCallbackCatch) {
ManualPromiseResolver<int, std::string> p(FROM_HERE);
RunLoop run_loop;
p.promise().ThenOnCurrent(
FROM_HERE, BindLambdaForTesting([&](int result) {
run_loop.Quit();
FAIL() << "We shouldn't get here, the promise was rejected!";
}),
BindLambdaForTesting([&](const std::string& err) {
run_loop.Quit();
EXPECT_EQ("Oh no!", err);
}));
p.GetRejectCallback().Run(std::string("Oh no!"));
run_loop.Run();
}
TEST_F(PromiseTest, GetRepeatingResolveCallbackThen) {
ManualPromiseResolver<int> p(FROM_HERE);
p.GetRepeatingResolveCallback().Run(123);
RunLoop run_loop;
p.promise().ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
EXPECT_EQ(123, result);
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(PromiseTest, GetRepeatingRejectCallbackCatch) {
ManualPromiseResolver<int, std::string> p(FROM_HERE);
RunLoop run_loop;
p.promise().ThenOnCurrent(
FROM_HERE, BindLambdaForTesting([&](int result) {
run_loop.Quit();
FAIL() << "We shouldn't get here, the promise was rejected!";
}),
BindLambdaForTesting([&](const std::string& err) {
run_loop.Quit();
EXPECT_EQ("Oh no!", err);
}));
p.GetRepeatingRejectCallback().Run(std::string("Oh no!"));
run_loop.Run();
}
TEST_F(PromiseTest, CreateResolvedThen) {
RunLoop run_loop;
Promise<int>::CreateResolved(FROM_HERE, 123)
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
EXPECT_EQ(123, result);
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(PromiseTest, CatchOnCurrentReturnTypes) {
ManualPromiseResolver<int, void> p1(FROM_HERE);
// Check CatchOnCurrent returns the expected return types for various
// return types.
Promise<int> r1 =
p1.promise().CatchOnCurrent(FROM_HERE, BindOnce([]() { return 123; }));
Promise<int> r2 = p1.promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() { return Resolved<int>(123); }));
Promise<int, int> r3 = p1.promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() { return Rejected<int>(123); }));
Promise<int, void> r4 = p1.promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() { return PromiseResult<int, void>(123.0); }));
Promise<int> r5 = p1.promise().CatchOnCurrent(
FROM_HERE,
BindOnce([]() { return PromiseResult<int, NoReject>(123.0); }));
Promise<int, int> r6 = p1.promise().CatchOnCurrent(
FROM_HERE,
BindOnce([]() { return PromiseResult<NoResolve, int>(123.0); }));
Promise<int, void> r7 = p1.promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() { return Promise<int, void>(); }));
Promise<int> r8 = p1.promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() { return Promise<int, NoReject>(); }));
Promise<int, int> r9 = p1.promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() { return Promise<NoResolve, int>(); }));
ManualPromiseResolver<NoResolve, void> p2(FROM_HERE);
Promise<int> r10 =
p2.promise().CatchOnCurrent(FROM_HERE, BindOnce([]() { return 123; }));
Promise<int> r11 = p2.promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() { return Resolved<int>(123); }));
Promise<NoResolve, int> r12 = p2.promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() { return Rejected<int>(123); }));
}
TEST_F(PromiseTest, ThenOnCurrentReturnTypes) {
ManualPromiseResolver<std::string, void> p1(FROM_HERE);
// Check ThenOnCurrent returns the expected return types for various
// return types.
Promise<int, void> r1 =
p1.promise().ThenOnCurrent(FROM_HERE, BindOnce([]() { return 123; }));
Promise<int, void> r2 = p1.promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Resolved<int>(123); }));
Promise<NoResolve, void> r3 = p1.promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Rejected<void>(); }));
Promise<int, void> r4 = p1.promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return PromiseResult<int, void>(123.0); }));
Promise<int, void> r5 = p1.promise().ThenOnCurrent(
FROM_HERE,
BindOnce([]() { return PromiseResult<int, NoReject>(123.0); }));
Promise<NoResolve, void> r6 = p1.promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return PromiseResult<NoResolve, void>(); }));
Promise<int, void> r7 = p1.promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Promise<int, void>(); }));
Promise<int, void> r8 = p1.promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Promise<int, NoReject>(); }));
Promise<NoResolve, void> r9 = p1.promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Promise<NoResolve, void>(); }));
ManualPromiseResolver<std::string> p2(FROM_HERE);
Promise<int> r10 =
p2.promise().ThenOnCurrent(FROM_HERE, BindOnce([]() { return 123; }));
Promise<int> r11 = p2.promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Resolved<int>(123); }));
Promise<NoResolve, int> r12 = p2.promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Rejected<int>(123); }));
}
TEST_F(PromiseTest, ThenAndCatchOnCurrentReturnTypes) {
struct A {};
struct B {};
struct C {};
struct D {};
Promise<B, NoReject> p1 =
ManualPromiseResolver<A, NoReject>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Resolved<B>(); }));
Promise<NoResolve, B> p2 =
ManualPromiseResolver<A, NoReject>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Rejected<B>(); }));
Promise<B, C> p3 =
ManualPromiseResolver<A, NoReject>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() -> PromiseResult<B, C> { return B{}; }));
Promise<B, NoReject> p4 =
ManualPromiseResolver<NoResolve, A>(FROM_HERE).promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() { return Resolved<B>(); }));
Promise<NoResolve, B> p5 =
ManualPromiseResolver<NoResolve, A>(FROM_HERE).promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() { return Rejected<B>(); }));
Promise<B, C> p6 =
ManualPromiseResolver<NoResolve, A>(FROM_HERE).promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() -> PromiseResult<B, C> { return B{}; }));
Promise<B, C> p7 =
ManualPromiseResolver<A, C>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Resolved<B>(); }));
Promise<NoResolve, C> p8 =
ManualPromiseResolver<A, C>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Rejected<C>(); }));
Promise<B, C> p9 =
ManualPromiseResolver<A, C>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() -> PromiseResult<B, C> { return B{}; }));
Promise<A, NoReject> p10 =
ManualPromiseResolver<A, C>(FROM_HERE).promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() { return Resolved<A>(); }));
Promise<A, B> p11 =
ManualPromiseResolver<A, C>(FROM_HERE).promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() { return Rejected<B>(); }));
Promise<A, B> p12 =
ManualPromiseResolver<A, C>(FROM_HERE).promise().CatchOnCurrent(
FROM_HERE, BindOnce([]() -> PromiseResult<A, B> { return B{}; }));
Promise<C, NoReject> p13 =
ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Resolved<C>(); }),
BindOnce([]() { return Resolved<C>(); }));
Promise<NoResolve, D> p14 =
ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Rejected<D>(); }),
BindOnce([]() { return Rejected<D>(); }));
Promise<C, D> p15 =
ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Resolved<C>(); }),
BindOnce([]() { return Rejected<D>(); }));
Promise<C, D> p16 =
ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Rejected<D>(); }),
BindOnce([]() { return Resolved<C>(); }));
Promise<C, D> p17 =
ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() -> PromiseResult<C, D> { return C{}; }),
BindOnce([]() { return Resolved<C>(); }));
Promise<C, D> p18 =
ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() -> PromiseResult<C, D> { return C{}; }),
BindOnce([]() { return Rejected<D>(); }));
Promise<C, D> p19 =
ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Resolved<C>(); }),
BindOnce([]() -> PromiseResult<C, D> { return C{}; }));
Promise<C, D> p20 =
ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() { return Rejected<D>(); }),
BindOnce([]() -> PromiseResult<C, D> { return C{}; }));
Promise<C, D> p21 =
ManualPromiseResolver<A, B>(FROM_HERE).promise().ThenOnCurrent(
FROM_HERE, BindOnce([]() -> PromiseResult<C, D> { return C{}; }),
BindOnce([]() -> PromiseResult<C, D> { return C{}; }));
}
TEST_F(PromiseTest, RejectAndReReject) {
ManualPromiseResolver<int, std::string> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.CatchOnCurrent(
FROM_HERE,
BindOnce([](const std::string& err) -> PromiseResult<int, int> {
EXPECT_EQ("Oh no!", err);
// Re-Reject with -1 this time.
return Rejected<int>(-1);
}))
.CatchOnCurrent(FROM_HERE, BindLambdaForTesting([&](int err) {
EXPECT_EQ(-1, err);
run_loop.Quit();
return -1;
}));
p.GetRejectCallback().Run("Oh no!");
run_loop.Run();
}
TEST_F(PromiseTest, RejectAndReRejectThenCatch) {
ManualPromiseResolver<int, std::string> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.CatchOnCurrent(FROM_HERE, BindLambdaForTesting([](std::string) {
return Rejected<int>(-1);
}))
.CatchOnCurrent(FROM_HERE, BindLambdaForTesting(
[&](int) { return Resolved<int>(1000); }))
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int value) {
EXPECT_EQ(1000, value);
return Rejected<DummyError>();
}))
.CatchOnCurrent(FROM_HERE, BindLambdaForTesting(
[&](DummyError) { run_loop.Quit(); }));
p.GetRejectCallback().Run("Oh no!");
run_loop.Run();
}
TEST_F(PromiseTest, ThenWhichAlwayResolves) {
ManualPromiseResolver<void> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindOnce([]() -> Resolved<int> {
// Resolve
return 123;
}))
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int value) {
EXPECT_EQ(123, value);
run_loop.Quit();
}));
p.GetResolveCallback().Run();
run_loop.Run();
}
TEST_F(PromiseTest, ThenWhichAlwayRejects) {
ManualPromiseResolver<void, int> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindOnce([]() -> Rejected<int> {
// Reject
return -1;
}))
.CatchOnCurrent(FROM_HERE, BindLambdaForTesting([&](int err) {
EXPECT_EQ(-1, err);
run_loop.Quit();
}));
p.GetResolveCallback().Run();
run_loop.Run();
}
TEST_F(PromiseTest, ThenWhichAlwayRejectsTypeTwo) {
ManualPromiseResolver<void> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindOnce([]() -> Rejected<int> {
// Reject
return -1;
}))
.CatchOnCurrent(FROM_HERE, BindLambdaForTesting([&](int err) {
EXPECT_EQ(-1, err);
run_loop.Quit();
}));
p.GetResolveCallback().Run();
run_loop.Run();
}
TEST_F(PromiseTest, ThenWhichAlwayRejectsTypeThree) {
ManualPromiseResolver<int> p(FROM_HERE);
base::RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
return Rejected<std::string>(std::string("reject"));
}))
.CatchOnCurrent(FROM_HERE, BindLambdaForTesting([&](std::string result) {
run_loop.Quit();
}));
p.GetResolveCallback().Run(123);
run_loop.Run();
}
TEST_F(PromiseTest, ReferenceType) {
int a = 123;
int b = 456;
ManualPromiseResolver<int&> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int& value) -> int& {
EXPECT_EQ(123, value);
return b;
}))
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int& value) {
EXPECT_EQ(456, value);
run_loop.Quit();
}));
p.GetResolveCallback().Run(a);
run_loop.Run();
}
TEST_F(PromiseTest, PromiseResultVoid) {
ManualPromiseResolver<void> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting(
[&]() { return PromiseResult<void>(); }))
.ThenOnCurrent(FROM_HERE,
BindLambdaForTesting([&]() { run_loop.Quit(); }));
p.Resolve();
run_loop.Run();
}
TEST_F(PromiseTest, RefcountedType) {
scoped_refptr<internal::AbstractPromise> a =
DoNothingPromiseBuilder(FROM_HERE);
scoped_refptr<internal::AbstractPromise> b =
DoNothingPromiseBuilder(FROM_HERE);
ManualPromiseResolver<scoped_refptr<internal::AbstractPromise>> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE,
BindLambdaForTesting(
[&](scoped_refptr<internal::AbstractPromise> value) {
EXPECT_EQ(a, value);
return b;
}))
.ThenOnCurrent(FROM_HERE,
BindLambdaForTesting(
[&](scoped_refptr<internal::AbstractPromise> value) {
EXPECT_EQ(b, value);
run_loop.Quit();
}));
p.Resolve(a);
run_loop.Run();
}
TEST_F(PromiseTest, ResolveThenVoidFunction) {
ManualPromiseResolver<int> p(FROM_HERE);
p.Resolve(123);
// You don't have to use the resolve (or reject) arguments from the
// previous promise.
RunLoop run_loop;
p.promise().ThenOnCurrent(FROM_HERE,
BindLambdaForTesting([&]() { run_loop.Quit(); }));
run_loop.Run();
}
TEST_F(PromiseTest, ResolveAfterThen) {
ManualPromiseResolver<int> p(FROM_HERE);
RunLoop run_loop;
p.promise().ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
EXPECT_EQ(123, result);
run_loop.Quit();
}));
p.Resolve(123);
run_loop.Run();
}
TEST_F(PromiseTest, RejectOutsidePromiseAfterThen) {
ManualPromiseResolver<int, void> p(FROM_HERE);
RunLoop run_loop;
p.promise().ThenOnCurrent(
FROM_HERE, BindLambdaForTesting([&](int result) {
run_loop.Quit();
FAIL() << "We shouldn't get here, the promise was rejected!";
}),
run_loop.QuitClosure());
p.Reject();
run_loop.Run();
}
TEST_F(PromiseTest, ThenChainMoveOnlyType) {
ManualPromiseResolver<std::unique_ptr<int>> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindOnce([](std::unique_ptr<int> result) {
return result;
}))
.ThenOnCurrent(FROM_HERE, BindOnce([](std::unique_ptr<int> result) {
return result;
}))
.ThenOnCurrent(FROM_HERE,
BindLambdaForTesting([&](std::unique_ptr<int> result) {
EXPECT_THAT(123, *result);
run_loop.Quit();
}));
p.Resolve(std::make_unique<int>(123));
run_loop.Run();
}
TEST_F(PromiseTest, MultipleMovesNotAllowed) {
ManualPromiseResolver<std::unique_ptr<int>> p(FROM_HERE);
// The executor argument will be called with move semantics.
p.promise().ThenOnCurrent(FROM_HERE,
BindOnce([](std::unique_ptr<int> result) {}));
// It's an error to do that twice.
EXPECT_DCHECK_DEATH({
p.promise().ThenOnCurrent(FROM_HERE,
BindOnce([](std::unique_ptr<int> result) {}));
});
}
TEST_F(PromiseTest, ThenChain) {
ManualPromiseResolver<std::vector<size_t>> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindOnce([](std::vector<size_t> result) {
result.push_back(1);
return result;
}))
.ThenOnCurrent(FROM_HERE, BindOnce([](std::vector<size_t> result) {
result.push_back(2);
return result;
}))
.ThenOnCurrent(FROM_HERE, BindOnce([](std::vector<size_t> result) {
result.push_back(3);
return result;
}))
.ThenOnCurrent(FROM_HERE,
BindLambdaForTesting([&](std::vector<size_t> result) {
EXPECT_THAT(result, ElementsAre(0u, 1u, 2u, 3u));
run_loop.Quit();
}));
p.Resolve(std::vector<size_t>{0});
run_loop.Run();
}
TEST_F(PromiseTest, RejectionInThenChainDefaultVoid) {
ManualPromiseResolver<std::vector<size_t>> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindOnce([](std::vector<size_t> result) {
result.push_back(result.size());
return result;
}))
.ThenOnCurrent(FROM_HERE, BindOnce([](std::vector<size_t> result) {
result.push_back(result.size());
return result;
}))
.ThenOnCurrent(FROM_HERE,
BindOnce([](std::vector<size_t> result)
-> PromiseResult<std::vector<size_t>, void> {
return Rejected<void>();
}))
.ThenOnCurrent(
FROM_HERE, BindLambdaForTesting([&](std::vector<size_t> result) {
FAIL() << "We shouldn't get here, the promise was rejected!";
}),
BindLambdaForTesting([&]() { run_loop.Quit(); }));
p.Resolve(std::vector<size_t>{0});
run_loop.Run();
}
TEST_F(PromiseTest, RejectPropagation) {
ManualPromiseResolver<int, bool> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result + 1; }))
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result + 1; }))
.ThenOnCurrent(
FROM_HERE,
BindOnce([](int result) -> PromiseResult<int, std::string> {
return std::string("Fail shouldn't get here");
}),
BindOnce([](bool value) -> PromiseResult<int, std::string> {
EXPECT_FALSE(value);
return std::string("Oh no!");
}))
.ThenOnCurrent(
FROM_HERE, BindLambdaForTesting([&](int result) {
FAIL() << "We shouldn't get here, the promise was rejected!";
run_loop.Quit();
}),
BindLambdaForTesting([&](const std::string& err) {
EXPECT_EQ("Oh no!", err);
run_loop.Quit();
}));
p.Reject(false);
run_loop.Run();
}
TEST_F(PromiseTest, RejectPropagationThensAfterRejectSkipped) {
ManualPromiseResolver<int, bool> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result + 1; }))
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result + 1; }))
.ThenOnCurrent(
FROM_HERE,
BindOnce([](int result) -> PromiseResult<int, std::string> {
return std::string("Fail shouldn't get here");
}),
BindOnce([](bool value) -> PromiseResult<int, std::string> {
EXPECT_FALSE(value);
return std::string("Oh no!"); // Reject
}))
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) {
CHECK(false) << "Shouldn't get here";
return result + 1;
}))
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) {
CHECK(false) << "Shouldn't get here";
return result + 1;
}))
.ThenOnCurrent(
FROM_HERE, BindLambdaForTesting([&](int result) {
FAIL() << "We shouldn't get here, the promise was rejected!";
run_loop.Quit();
}),
BindLambdaForTesting([&](const std::string& err) {
EXPECT_EQ("Oh no!", err);
run_loop.Quit();
}));
p.Reject(false);
run_loop.Run();
}
TEST_F(PromiseTest, ThenOnWithHetrogenousButCompatibleReturnTypes) {
ManualPromiseResolver<void, int> p(FROM_HERE);
// Make sure ThenOnCurrent returns the expected type.
Promise<int, std::string> p2 = p.promise().ThenOnCurrent(
FROM_HERE,
BindOnce([]() -> PromiseResult<int, std::string> { return 123; }),
BindOnce([](int err) -> Resolved<int> { return 123; }));
}
TEST_F(PromiseTest, ThenOnWithHetrogenousButCompatibleReturnTypes2) {
ManualPromiseResolver<void, int> p(FROM_HERE);
// Make sure ThenOnCurrent returns the expected type.
Promise<int, std::string> p2 = p.promise().ThenOnCurrent(
FROM_HERE,
BindOnce([]() -> PromiseResult<int, std::string> { return 123; }),
BindOnce([](int err) -> Rejected<std::string> { return "123"; }));
}
TEST_F(PromiseTest, ThenOnWithHetrogenousButCompatibleReturnTypes3) {
ManualPromiseResolver<int, std::string> p(FROM_HERE);
// Make sure ThenOnCurrent returns the expected type.
Promise<void, bool> p2 = p.promise().ThenOnCurrent(
FROM_HERE, BindOnce([](int value) -> PromiseResult<void, bool> {
if (value % 2) {
return Resolved<void>();
} else {
return true;
}
}),
BindOnce([](const std::string& err) -> Rejected<bool> { return false; }));
}
TEST_F(PromiseTest, ThenOnAfterNoResolvePromiseResult) {
ManualPromiseResolver<std::unique_ptr<int>, int> p1(FROM_HERE);
RunLoop run_loop;
p1.promise()
.CatchOnCurrent(FROM_HERE, BindLambdaForTesting(
[&](int) -> PromiseResult<NoResolve, int> {
return Rejected<int>();
}))
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](std::unique_ptr<int>) {
run_loop.Quit();
return std::make_unique<int>(42);
}),
BindLambdaForTesting([&](int err) {
CHECK(false) << "Shouldn't get here";
return std::make_unique<int>(42);
}));
p1.GetResolveCallback().Run(std::make_unique<int>(42));
run_loop.Run();
}
TEST_F(PromiseTest, CatchCreatesNoRejectPromise) {
ManualPromiseResolver<int> p(FROM_HERE);
// Make sure CatchOnCurrent returns the expected type.
Promise<int> p2 =
p.promise()
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int) {
return Rejected<std::string>();
}))
.CatchOnCurrent(FROM_HERE, BindLambdaForTesting([&](std::string) {
return Resolved<int>();
}));
}
TEST_F(PromiseTest, ResolveSkipsCatches) {
ManualPromiseResolver<int, void> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result + 1; }))
.CatchOnCurrent(FROM_HERE, BindOnce([]() -> PromiseResult<int, void> {
CHECK(false) << "Shouldn't get here";
return -1;
}))
.CatchOnCurrent(FROM_HERE, BindOnce([]() -> PromiseResult<int, void> {
CHECK(false) << "Shouldn't get here";
return -1;
}))
.CatchOnCurrent(FROM_HERE, BindOnce([]() -> PromiseResult<int, void> {
CHECK(false) << "Shouldn't get here";
return -1;
}))
.ThenOnCurrent(
FROM_HERE, BindLambdaForTesting([&](int result) {
EXPECT_EQ(2, result);
run_loop.Quit();
}),
BindLambdaForTesting([&]() {
FAIL() << "We shouldn't get here, the promise was resolved!";
run_loop.Quit();
}));
p.Resolve(1);
run_loop.Run();
}
TEST_F(PromiseTest, ThenChainVariousReturnTypes) {
ManualPromiseResolver<void> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindOnce([]() { return 5; }))
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) {
EXPECT_EQ(5, result);
return std::string("Hello");
}))
.ThenOnCurrent(FROM_HERE, BindOnce([](std::string result) {
EXPECT_EQ("Hello", result);
return true;
}))
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](bool result) {
EXPECT_TRUE(result);
run_loop.Quit();
}));
p.GetResolveCallback().Run();
run_loop.Run();
}
TEST_F(PromiseTest, CurriedVoidPromise) {
Promise<void> p = Promise<void>::CreateResolved(FROM_HERE);
ManualPromiseResolver<void> promise_resolver(FROM_HERE);
RunLoop run_loop;
p.ThenOnCurrent(FROM_HERE,
BindOnce(
[](ManualPromiseResolver<void>* promise_resolver) {
return promise_resolver->promise();
},
&promise_resolver))
.ThenOnCurrent(FROM_HERE, run_loop.QuitClosure());
RunLoop().RunUntilIdle();
promise_resolver.Resolve();
run_loop.Run();
}
TEST_F(PromiseTest, CurriedIntPromise) {
Promise<int> p = Promise<int>::CreateResolved(FROM_HERE, 1000);
ManualPromiseResolver<int> promise_resolver(FROM_HERE);
RunLoop run_loop;
p.ThenOnCurrent(
FROM_HERE,
BindOnce(
[](ManualPromiseResolver<int>* promise_resolver, int result) {
EXPECT_EQ(1000, result);
return promise_resolver->promise();
},
&promise_resolver))
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
EXPECT_EQ(123, result);
run_loop.Quit();
}));
RunLoop().RunUntilIdle();
promise_resolver.Resolve(123);
run_loop.Run();
}
TEST_F(PromiseTest, PromiseResultReturningAPromise) {
Promise<int> p = Promise<int>::CreateResolved(FROM_HERE, 1000);
ManualPromiseResolver<int> promise_resolver(FROM_HERE);
RunLoop run_loop;
p.ThenOnCurrent(FROM_HERE,
BindLambdaForTesting([&](int result) -> PromiseResult<int> {
EXPECT_EQ(1000, result);
return promise_resolver.promise();
}))
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
EXPECT_EQ(123, result);
run_loop.Quit();
}));
RunLoop().RunUntilIdle();
promise_resolver.Resolve(123);
run_loop.Run();
}
TEST_F(PromiseTest, ResolveToDisambiguateThenReturnValue) {
ManualPromiseResolver<int> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE,
BindOnce([](int i) -> PromiseResult<Value, Value> {
if ((i % 2) == 1)
return Resolved<Value>("Success it was odd.");
return Rejected<Value>("Failure it was even.");
}))
.ThenOnCurrent(
FROM_HERE, BindLambdaForTesting([&](Value result) {
EXPECT_EQ("Success it was odd.", result.GetString());
run_loop.Quit();
}),
BindLambdaForTesting([&](Value err) {
run_loop.Quit();
FAIL() << "We shouldn't get here, the promise was resolved!";
}));
p.Resolve(1);
run_loop.Run();
}
TEST_F(PromiseTest, RejectedToDisambiguateThenReturnValue) {
ManualPromiseResolver<int, int> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindOnce([]() -> PromiseResult<int, int> {
return Rejected<int>(123);
}))
.ThenOnCurrent(
FROM_HERE, BindLambdaForTesting([&](int result) {
run_loop.Quit();
FAIL() << "We shouldn't get here, the promise was rejected!";
}),
BindLambdaForTesting([&](int err) {
run_loop.Quit();
EXPECT_EQ(123, err);
}));
p.Resolve();
run_loop.Run();
}
TEST_F(PromiseTest, NestedPromises) {
ManualPromiseResolver<int> p(FROM_HERE);
p.Resolve(100);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) {
ManualPromiseResolver<int> p2(FROM_HERE);
p2.Resolve(200);
return p2.promise().ThenOnCurrent(
FROM_HERE, BindOnce([](int result) {
ManualPromiseResolver<int> p3(FROM_HERE);
p3.Resolve(300);
return p3.promise().ThenOnCurrent(
FROM_HERE,
BindOnce([](int result) { return result; }));
}));
}))
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
EXPECT_EQ(300, result);
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(PromiseTest, Catch) {
ManualPromiseResolver<int, std::string> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result; }))
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result; }))
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) { return result; }))
.CatchOnCurrent(FROM_HERE,
BindLambdaForTesting([&](const std::string& err) {
EXPECT_EQ("Whoops!", err);
run_loop.Quit();
return -1;
}));
p.Reject("Whoops!");
run_loop.Run();
}
TEST_F(PromiseTest, BranchedThenChainExecutionOrder) {
std::vector<int> run_order;
ManualPromiseResolver<void> promise_a(FROM_HERE);
Promise<void> promise_b =
promise_a.promise()
.ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 0))
.ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 1));
Promise<void> promise_c =
promise_a.promise()
.ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 2))
.ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 3));
Promise<void> promise_d =
promise_a.promise()
.ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 4))
.ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 5));
promise_a.Resolve();
RunLoop().RunUntilIdle();
EXPECT_THAT(run_order, ElementsAre(0, 2, 4, 1, 3, 5));
}
TEST_F(PromiseTest, BranchedThenChainWithCatchExecutionOrder) {
std::vector<int> run_order;
ManualPromiseResolver<void, void> promise_a(FROM_HERE);
Promise<void> promise_b =
promise_a.promise()
.ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 0))
.ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 1))
.CatchOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 2));
Promise<void> promise_c =
promise_a.promise()
.ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 3))
.ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 4))
.CatchOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 5));
Promise<void> promise_d =
promise_a.promise()
.ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 6))
.ThenOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 7))
.CatchOnCurrent(FROM_HERE, BindOnce(&RecordOrder, &run_order, 8));
promise_a.Reject();
RunLoop().RunUntilIdle();
EXPECT_THAT(run_order, ElementsAre(2, 5, 8));
}
TEST_F(PromiseTest, CatchRejectInThenChain) {
ManualPromiseResolver<int> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.ThenOnCurrent(
FROM_HERE,
BindOnce([](int result) -> PromiseResult<int, std::string> {
return std::string("Whoops!");
}))
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) {
CHECK(false) << "Shouldn't get here";
return result;
}))
.ThenOnCurrent(FROM_HERE, BindOnce([](int result) {
CHECK(false) << "Shouldn't get here";
return result;
}))
.CatchOnCurrent(FROM_HERE,
BindLambdaForTesting([&](const std::string& err) {
EXPECT_EQ("Whoops!", err);
run_loop.Quit();
return -1;
}));
p.Resolve(123);
run_loop.Run();
}
TEST_F(PromiseTest, CatchThenVoid) {
ManualPromiseResolver<int, void> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.CatchOnCurrent(FROM_HERE, BindOnce([]() { return 123; }))
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
EXPECT_EQ(123, result);
run_loop.Quit();
}));
p.Reject();
run_loop.Run();
}
TEST_F(PromiseTest, CatchThenInt) {
ManualPromiseResolver<int, int> p(FROM_HERE);
RunLoop run_loop;
p.promise()
.CatchOnCurrent(FROM_HERE, BindOnce([](int err) { return err + 1; }))
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting([&](int result) {
EXPECT_EQ(124, result);
run_loop.Quit();
}));
p.Reject(123);
run_loop.Run();
}
namespace {
struct Cancelable {
Cancelable() : weak_ptr_factory(this) {}
void LogTask(std::vector<std::string>* log, std::string value) {
log->push_back(value);
}
void NopTask() {}
WeakPtrFactory<Cancelable> weak_ptr_factory;
};
} // namespace
TEST_F(PromiseTest, CancelViaWeakPtr) {
std::vector<std::string> log;
ManualPromiseResolver<void, std::string> mpr(FROM_HERE,
RejectPolicy::kCatchNotRequired);
Promise<void, std::string> p1 = mpr.promise();
{
Cancelable cancelable;
Promise<void, std::string> p2 = p1.ThenOnCurrent(
FROM_HERE,
BindOnce(&Cancelable::LogTask, cancelable.weak_ptr_factory.GetWeakPtr(),
&log, "Then #1"));
p2.ThenOnCurrent(FROM_HERE, BindLambdaForTesting(
[&]() -> PromiseResult<void, std::string> {
log.push_back("Then #2 (reject)");
return std::string("Whoops!");
}))
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting(
[&]() { log.push_back("Then #3"); }))
.ThenOnCurrent(FROM_HERE, BindLambdaForTesting(
[&]() { log.push_back("Then #4"); }))
.CatchOnCurrent(FROM_HERE,
BindLambdaForTesting([&](const std::string& err) {
log.push_back("Caught " + err);
}));
p2.ThenOnCurrent(FROM_HERE,
BindLambdaForTesting([&]() { log.push_back("Then #5"); }));
p2.ThenOnCurrent(FROM_HERE,
BindLambdaForTesting([&]() { log.push_back("Then #6"); }));
}
mpr.Resolve();
RunLoop().RunUntilIdle();
EXPECT_TRUE(log.empty());
}
TEST_F(PromiseTest, CatchNotRequired) {
ManualPromiseResolver<bool, int> p(FROM_HERE,
RejectPolicy::kCatchNotRequired);
RunLoop run_loop;
p.promise().ThenOnCurrent(FROM_HERE, run_loop.QuitClosure());
// Note this doesn't DCHECK even though we haven't specified a Catch.
p.Resolve();
run_loop.Run();
}
TEST_F(PromiseTest, MoveOnlyTypeMultipleThensNotAllowed) {
#if DCHECK_IS_ON()
Promise<std::unique_ptr<int>> p =
Promise<std::unique_ptr<int>>::CreateResolved(FROM_HERE,
std::make_unique<int>(123));
p.ThenOnCurrent(FROM_HERE,
BindOnce([](std::unique_ptr<int> i) { EXPECT_EQ(123, *i); }));
EXPECT_DCHECK_DEATH({
p.ThenOnCurrent(FROM_HERE, BindOnce([](std::unique_ptr<int> i) {
EXPECT_EQ(123, *i);
}));
});
#endif
}
TEST_F(PromiseTest, MoveOnlyTypeMultipleCatchesNotAllowed) {
#if DCHECK_IS_ON()
auto p = Promise<void, std::unique_ptr<int>>::CreateRejected(
FROM_HERE, std::make_unique<int>(123));
p.CatchOnCurrent(
FROM_HERE, BindOnce([](std::unique_ptr<int> i) { EXPECT_EQ(123, *i); }));
EXPECT_DCHECK_DEATH({
p.CatchOnCurrent(FROM_HERE, BindOnce([](std::unique_ptr<int> i) {
EXPECT_EQ(123, *i);
}));
});
#endif
}
TEST_F(PromiseTest, PostTaskUnhandledRejection) {
#if DCHECK_IS_ON()
Promise<void, int> p =
Promise<void, int>::CreateRejected(FROM_HERE).ThenOnCurrent(
FROM_HERE, BindOnce([]() {}));
RunLoop().RunUntilIdle();
Promise<void, int> null_promise;
EXPECT_DCHECK_DEATH({ p = null_promise; });
// EXPECT_DCHECK_DEATH uses fork under the hood so we still have to tidy up.
p.CatchOnCurrent(FROM_HERE, BindOnce([]() {}));
#endif
}
TEST_F(PromiseTest, ManualPromiseResolverPotentialUnhandledRejection) {
#if DCHECK_IS_ON()
ManualPromiseResolver<void, void> promise_resolver(FROM_HERE);
// |promise_resolver| could reject but there's no catch.
Promise<void, void> p =
promise_resolver.promise().ThenOnCurrent(FROM_HERE, BindOnce([]() {}));
promise_resolver.Resolve();
RunLoop().RunUntilIdle();
Promise<void, void> null_promise;
EXPECT_DCHECK_DEATH({ p = null_promise; });
// EXPECT_DCHECK_DEATH uses fork under the hood so we still have to tidy up.
p.CatchOnCurrent(FROM_HERE, BindOnce([]() {}));
#endif
}
TEST_F(PromiseTest, ManualPromiseResolverResolveCalledTwice) {
#if DCHECK_IS_ON()
ManualPromiseResolver<void> promise_resolver(FROM_HERE);
promise_resolver.Resolve();
EXPECT_DCHECK_DEATH({ promise_resolver.Resolve(); });
#endif
}
TEST_F(PromiseTest, ManualPromiseResolverRejectCalledTwice) {
#if DCHECK_IS_ON()
ManualPromiseResolver<void, void> promise_resolver(
FROM_HERE, RejectPolicy::kCatchNotRequired);
promise_resolver.Reject();
EXPECT_DCHECK_DEATH({ promise_resolver.Reject(); });
#endif
}
TEST_F(PromiseTest, ManualPromiseResolverResolveCalledAfterReject) {
#if DCHECK_IS_ON()
ManualPromiseResolver<void, void> promise_resolver(
FROM_HERE, RejectPolicy::kCatchNotRequired);
promise_resolver.Reject();
EXPECT_DCHECK_DEATH({ promise_resolver.Resolve(); });
#endif
}
TEST_F(PromiseTest, ManualPromiseResolverRepeatingResolveCallbackCalledTwice) {
#if DCHECK_IS_ON()
ManualPromiseResolver<void, void> promise_resolver(
FROM_HERE, RejectPolicy::kCatchNotRequired);
RepeatingCallback<void(void)> resolve =
promise_resolver.GetRepeatingResolveCallback();
resolve.Run();
EXPECT_DCHECK_DEATH({ resolve.Run(); });
#endif
}
TEST_F(PromiseTest, ManualPromiseResolverRepeatingRejectCallbackCalledTwice) {
#if DCHECK_IS_ON()
ManualPromiseResolver<void, void> promise_resolver(
FROM_HERE, RejectPolicy::kCatchNotRequired);
RepeatingCallback<void(void)> resolve =
promise_resolver.GetRepeatingRejectCallback();
resolve.Run();
EXPECT_DCHECK_DEATH({ resolve.Run(); });
#endif
}
class MultiThreadedPromiseTest : public PromiseTest {
public:
void SetUp() override {
thread_a_.reset(new Thread("MultiThreadPromiseTest_Thread_A"));
thread_b_.reset(new Thread("MultiThreadPromiseTest_Thread_B"));
thread_c_.reset(new Thread("MultiThreadPromiseTest_Thread_C"));
thread_a_->Start();
thread_b_->Start();
thread_c_->Start();
}
void TearDown() override {
thread_a_->Stop();
thread_b_->Stop();
thread_c_->Stop();
}
std::unique_ptr<Thread> thread_a_;
std::unique_ptr<Thread> thread_b_;
std::unique_ptr<Thread> thread_c_;
};
TEST_F(MultiThreadedPromiseTest, SimpleThreadHopping) {
ManualPromiseResolver<void> promise_resolver(FROM_HERE);
RunLoop run_loop;
promise_resolver.promise()
.ThenOn(
thread_a_->task_runner(), FROM_HERE, BindLambdaForTesting([&]() {
EXPECT_TRUE(thread_a_->task_runner()->RunsTasksInCurrentSequence());
}))
.ThenOn(
thread_b_->task_runner(), FROM_HERE, BindLambdaForTesting([&]() {
EXPECT_TRUE(thread_b_->task_runner()->RunsTasksInCurrentSequence());
}))
.ThenOn(
thread_c_->task_runner(), FROM_HERE, BindLambdaForTesting([&]() {
EXPECT_TRUE(thread_c_->task_runner()->RunsTasksInCurrentSequence());
}))
.ThenOnCurrent(
FROM_HERE, BindLambdaForTesting([&]() {
EXPECT_FALSE(
thread_a_->task_runner()->RunsTasksInCurrentSequence());
EXPECT_FALSE(
thread_b_->task_runner()->RunsTasksInCurrentSequence());
EXPECT_FALSE(
thread_c_->task_runner()->RunsTasksInCurrentSequence());
run_loop.Quit();
}));
promise_resolver.Resolve();
run_loop.Run();
}
TEST_F(MultiThreadedPromiseTest, CrossThreadThens) {
ManualPromiseResolver<void> promise_resolver(FROM_HERE);
std::atomic<int> count(0);
auto increment_task =
BindLambdaForTesting([&]() { std::atomic_fetch_add(&count, 1); });
auto resolve_task =
BindLambdaForTesting([&]() { promise_resolver.Resolve(); });
RunLoop run_loop;
thread_a_->task_runner()->PostTask(
FROM_HERE, BindLambdaForTesting([&]() {
// Post 500 thens.
for (int i = 0; i < 500; i++) {
promise_resolver.promise().ThenOn(thread_c_->task_runner(), FROM_HERE,
increment_task);
}
// Post a task onto the main thread to resolve |promise_resolver|.
// This should run at an undefined time yet all the thens should run.
thread_b_->task_runner()->PostTask(FROM_HERE, resolve_task);
// Post another 500 thens.
for (int i = 0; i < 500; i++) {
promise_resolver.promise().ThenOn(thread_c_->task_runner(), FROM_HERE,
increment_task);
}
promise_resolver.promise().ThenOn(thread_c_->task_runner(), FROM_HERE,
run_loop.QuitClosure());
}));
run_loop.Run();
EXPECT_EQ(std::atomic_load(&count), 1000);
}
TEST_F(PromiseTest, ThreadPoolThenChain) {
ManualPromiseResolver<std::vector<size_t>> p(FROM_HERE);
auto main_sequence = SequencedTaskRunnerHandle::Get();
RunLoop run_loop;
p.promise()
.ThenOn({TaskPriority::USER_BLOCKING}, FROM_HERE,
BindLambdaForTesting([&](std::vector<size_t> result) {
EXPECT_FALSE(main_sequence->RunsTasksInCurrentSequence());
result.push_back(1);
return result;
}))
.ThenOn({TaskPriority::USER_BLOCKING}, FROM_HERE,
BindLambdaForTesting([&](std::vector<size_t> result) {
EXPECT_FALSE(main_sequence->RunsTasksInCurrentSequence());
result.push_back(2);
return result;
}))
.ThenOn({TaskPriority::USER_BLOCKING}, FROM_HERE,
BindLambdaForTesting([&](std::vector<size_t> result) {
EXPECT_FALSE(main_sequence->RunsTasksInCurrentSequence());
result.push_back(3);
return result;
}))
.ThenOnCurrent(FROM_HERE,
BindLambdaForTesting([&](std::vector<size_t> result) {
EXPECT_TRUE(main_sequence->RunsTasksInCurrentSequence());
EXPECT_THAT(result, ElementsAre(0u, 1u, 2u, 3u));
run_loop.Quit();
}));
p.Resolve(std::vector<size_t>{0});
run_loop.Run();
}
} // namespace base
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// This is a "No Compile Test" suite.
// http://dev.chromium.org/developers/testing/no-compile-tests
#include "base/task/promise/promise.h"
#include "base/task/promise/promise_result.h"
namespace base {
#if defined(NCTEST_METHOD_CANT_CATCH_NOREJECT_PROMISE) // [r"fatal error: static_assert failed .*\"Can't catch a NoReject promise\."]
void WontCompile() {
Promise<int> p;
p.CatchOnCurrent(FROM_HERE, BindOnce([]() {}));
}
#elif defined(NCTEST_METHOD_CANT_CATCH_NOREJECT_PROMISE_TYPE_TWO) // [r"fatal error: static_assert failed .*\"Can't catch a NoReject promise\."]
void WontCompile() {
Promise<int> p;
p.ThenOnCurrent(FROM_HERE, BindOnce([](int) {}), BindOnce([]() {}));
}
#elif defined(NCTEST_METHOD_RESOLVE_CALLBACK_TYPE_MISSMATCH) // [r"fatal error: static_assert failed .*\"|on_resolve| callback must accept Promise::ResolveType or void\."]
void WontCompile() {
Promise<int, void> p;
p.ThenOnCurrent(FROM_HERE, BindOnce([](bool) { }));
}
#elif defined(NCTEST_METHOD_REJECT_CALLBACK_TYPE_MISSMATCH) // [r"fatal error: static_assert failed .*\"|on_reject| callback must accept Promise::ResolveType or void\."]
void WontCompile() {
Promise<int, void> p;
p.CatchOnCurrent(FROM_HERE, BindOnce([](bool) { }));
}
#elif defined(NCTEST_METHOD_REJECT_CALLBACK_TYPE_MISSMATCH_TYPE_TWO) // [r"fatal error: static_assert failed .*\"|on_reject| callback must accept Promise::ResolveType or void\."]
void WontCompile() {
Promise<int, void> p;
p.ThenOnCurrent(FROM_HERE, BindOnce([](int) { }), BindOnce([](bool) { }));
}
#elif defined(NCTEST_METHOD_INCOMPATIBLE_RETURN_TYPES) // [r"fatal error: static_assert failed .*\"|on_resolve| callback and |on_resolve| callback must return compatible types\."]
void WontCompile() {
Promise<void> p;
p.ThenOnCurrent(
FROM_HERE,
BindOnce([]() -> PromiseResult<int, std::string> { return 123; }),
BindOnce([](int err) -> Rejected<bool> { return "123"; }));
}
#elif defined(NCTEST_METHOD_INCOMPATIBLE_RETURN_TYPES2) // [r"fatal error: static_assert failed .*\"|on_resolve| callback and |on_resolve| callback must return compatible types\."]
void WontCompile() {
Promise<void> p;
p.ThenOnCurrent(
FROM_HERE,
BindOnce([]() -> PromiseResult<int, std::string> { return 123; }),
BindOnce([](int err) -> Resolved<std::string> { return "123"; }));
}
#elif defined(NCTEST_METHOD_INCOMPATIBLE_RETURN_TYPES3) // [r"fatal error: static_assert failed .*\"|on_resolve| callback and |on_resolve| callback must return compatible types\."]
void WontCompile() {
Promise<int, void> p;
p.ThenOnCurrent(FROM_HERE, BindOnce([](int) { return true; }),
BindOnce([](int) { return 123.0; }));
}
#elif defined(NCTEST_METHOD_AMBIGUOUS_CONSTRUCTOR) // [r"fatal error: static_assert failed .*\"Ambiguous because ResolveType and RejectType are the same"]
void WontCompile() {
PromiseResult<void, void> pr;
}
#elif defined(NCTEST_METHOD_AMBIGUOUS_CONSTRUCTOR2) // [r"fatal error: static_assert failed .*\"Ambiguous because ResolveType and RejectType are the same"]
void WontCompile() {
PromiseResult<int, int> pr(123);
}
#elif defined(NCTEST_METHOD_REJECTED_NOREJECT) // [r"fatal error: static_assert failed .*\"Can't have Rejected<NoReject>"]
void WontCompile() {
Rejected<NoReject>();
}
#elif defined(NCTEST_METHOD_ARGUMENT_DOESNT_MATCH) // [r"fatal error: static_assert failed .*\"Argument matches neither resolve nor reject type\."]
void WontCompile() {
PromiseResult<int, float> pr("invalid");
}
#elif defined(NCTEST_METHOD_ARGUMENT_DOESNT_MATCH2) // [r"fatal error: static_assert failed .*\"Promise resolve types don't match"]
void WontCompile() {
Promise<void> p;
PromiseResult<int, float> pr(p);
}
#elif defined(NCTEST_METHOD_UNRELATED_RESOLVE) // [r"fatal error: static_assert failed .*\"T in Resolved<T> is not ResolveType"]
void WontCompile() {
struct Unrelated{};
PromiseResult<int, void> pr(Resolved<Unrelated>{});
}
#elif defined(NCTEST_METHOD_UNRELATED_REJECT) // [r"fatal error: static_assert failed .*\"T in Rejected<T> is not RejectType"]
void WontCompile() {
struct Unrelated{};
PromiseResult<int, void> pr(Rejected<Unrelated>{});
}
#elif defined(NCTEST_METHOD_AMBIGUOUS_REJECT_TYPE) // [r"fatal error: static_assert failed .*\"Ambiguous promise reject type"]
void WontCompile() {
Promise<int, void> p1;
// If supported |p2| would have type Promise<NoReject, variant<void, bool>>.
auto p2 = p1.ThenOnCurrent(FROM_HERE, BindOnce([]() { return Rejected<bool>(true); }));
}
#elif defined(NCTEST_METHOD_AMBIGUOUS_RESOLVE_TYPE) // [r"fatal error: static_assert failed .*\"Ambiguous promise resolve type"]
void WontCompile() {
Promise<int, void> p1;
// If supported |p2| would have type Promise<variant<int, bool>, NoReject>.
auto p2 = p1.CatchOnCurrent(FROM_HERE, BindOnce([](int) { return Resolved<bool>(true); }));
}
#endif
} // namespace base
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/task/promise/then_and_catch_executor.h"
namespace base {
namespace internal {
BaseThenAndCatchExecutor::BaseThenAndCatchExecutor(
internal::CallbackBase&& resolve_executor,
internal::CallbackBase&& reject_executor)
: resolve_callback_(std::move(resolve_executor)),
reject_callback_(std::move(reject_executor)) {}
BaseThenAndCatchExecutor::~BaseThenAndCatchExecutor() = default;
bool BaseThenAndCatchExecutor::IsCancelled() const {
if (!resolve_callback_.is_null()) {
// If there is both a resolve and a reject executor they must be canceled
// at the same time.
DCHECK(reject_callback_.is_null() ||
reject_callback_.IsCancelled() == resolve_callback_.IsCancelled());
return resolve_callback_.IsCancelled();
}
return reject_callback_.IsCancelled();
}
AbstractPromise::Executor::PrerequisitePolicy
BaseThenAndCatchExecutor::GetPrerequisitePolicy() {
return PrerequisitePolicy::kAll;
}
void BaseThenAndCatchExecutor::Execute(AbstractPromise* promise) {
AbstractPromise* prerequisite =
promise->GetOnlyPrerequisite()->FindNonCurriedAncestor();
if (prerequisite->IsResolved()) {
if (ProcessNullCallback(resolve_callback_, prerequisite, promise)) {
promise->OnResolved();
return;
}
ExecuteThen(prerequisite, promise);
} else {
DCHECK(prerequisite->IsRejected());
if (ProcessNullCallback(reject_callback_, prerequisite, promise)) {
promise->OnResolved();
return;
}
ExecuteCatch(prerequisite, promise);
}
}
// static
bool BaseThenAndCatchExecutor::ProcessNullCallback(const CallbackBase& callback,
AbstractPromise* arg,
AbstractPromise* result) {
if (callback.is_null()) {
// A curried promise is used to forward the result through null callbacks.
result->emplace(scoped_refptr<AbstractPromise>(arg));
DCHECK(result->IsResolvedWithPromise());
return true;
}
return false;
}
} // namespace internal
} // namespace base
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TASK_PROMISE_THEN_AND_CATCH_EXECUTOR_H_
#define BASE_TASK_PROMISE_THEN_AND_CATCH_EXECUTOR_H_
#include <type_traits>
#include "base/callback.h"
#include "base/task/promise/abstract_promise.h"
#include "base/task/promise/helpers.h"
namespace base {
namespace internal {
// Exists to reduce template bloat.
class BASE_EXPORT BaseThenAndCatchExecutor : public AbstractPromise::Executor {
public:
BaseThenAndCatchExecutor(CallbackBase&& resolve_callback,
CallbackBase&& reject_callback);
~BaseThenAndCatchExecutor() override;
// AbstractPromise::Executor:
bool IsCancelled() const override;
PrerequisitePolicy GetPrerequisitePolicy() override;
void Execute(AbstractPromise* promise) override;
protected:
virtual void ExecuteThen(AbstractPromise* prerequisite,
AbstractPromise* promise) = 0;
virtual void ExecuteCatch(AbstractPromise* prerequisite,
AbstractPromise* promise) = 0;
// If |executor| is null then the value of |arg| is moved or copied into
// |result| and true is returned. Otherwise false is returned.
static bool ProcessNullCallback(const CallbackBase& executor,
AbstractPromise* arg,
AbstractPromise* result);
CallbackBase resolve_callback_;
CallbackBase reject_callback_;
};
// Tag signals no callback which is used to eliminate dead code.
struct NoCallback {};
struct CouldResolveOrReject {};
struct CanOnlyResolve {};
struct CanOnlyReject {};
template <bool can_resolve, bool can_reject>
struct CheckResultHelper;
template <>
struct CheckResultHelper<true, false> {
using TagType = CanOnlyResolve;
};
template <>
struct CheckResultHelper<true, true> {
using TagType = CouldResolveOrReject;
};
template <>
struct CheckResultHelper<false, true> {
using TagType = CanOnlyReject;
};
template <typename ResolveOnceCallback,
typename RejectOnceCallback,
typename ArgResolve,
typename ArgReject,
typename ResolveStorage,
typename RejectStorage>
class ThenAndCatchExecutor : public BaseThenAndCatchExecutor {
public:
using ResolveReturnT =
typename CallbackTraits<ResolveOnceCallback>::ReturnType;
using RejectReturnT = typename CallbackTraits<RejectOnceCallback>::ReturnType;
using PrerequisiteCouldResolve =
std::integral_constant<bool,
!std::is_same<ArgResolve, NoCallback>::value>;
using PrerequisiteCouldReject =
std::integral_constant<bool, !std::is_same<ArgReject, NoCallback>::value>;
ThenAndCatchExecutor(ResolveOnceCallback&& resolve_callback,
RejectOnceCallback&& reject_callback)
: BaseThenAndCatchExecutor(std::move(resolve_callback),
std::move(reject_callback)) {
static_assert(sizeof(CallbackBase) == sizeof(ResolveOnceCallback),
"We assume it's possible to cast from CallbackBase to "
"ResolveOnceCallback");
static_assert(sizeof(CallbackBase) == sizeof(RejectOnceCallback),
"We assume it's possible to cast from CallbackBase to "
"RejectOnceCallback");
}
~ThenAndCatchExecutor() override = default;
void ExecuteThen(AbstractPromise* prerequisite,
AbstractPromise* promise) override {
ExecuteThenInternal(prerequisite, promise, PrerequisiteCouldResolve());
}
void ExecuteCatch(AbstractPromise* prerequisite,
AbstractPromise* promise) override {
ExecuteCatchInternal(prerequisite, promise, PrerequisiteCouldReject());
}
#if DCHECK_IS_ON()
ArgumentPassingType ResolveArgumentPassingType() const override {
return resolve_callback_.is_null()
? AbstractPromise::Executor::ArgumentPassingType::kNoCallback
: CallbackTraits<ResolveOnceCallback>::argument_passing_type;
}
ArgumentPassingType RejectArgumentPassingType() const override {
return reject_callback_.is_null()
? AbstractPromise::Executor::ArgumentPassingType::kNoCallback
: CallbackTraits<RejectOnceCallback>::argument_passing_type;
}
bool CanResolve() const override {
return (!resolve_callback_.is_null() &&
PromiseCallbackTraits<ResolveReturnT>::could_resolve) ||
(!reject_callback_.is_null() &&
PromiseCallbackTraits<RejectReturnT>::could_resolve);
}
bool CanReject() const override {
return (!resolve_callback_.is_null() &&
PromiseCallbackTraits<ResolveReturnT>::could_reject) ||
(!reject_callback_.is_null() &&
PromiseCallbackTraits<RejectReturnT>::could_reject);
}
#endif
private:
void ExecuteThenInternal(AbstractPromise* prerequisite,
AbstractPromise* promise,
std::true_type can_resolve) {
ResolveOnceCallback* resolve_callback =
static_cast<ResolveOnceCallback*>(&resolve_callback_);
RunHelper<ResolveOnceCallback, Resolved<ArgResolve>, ResolveStorage,
RejectStorage>::Run(std::move(*resolve_callback), prerequisite,
promise);
using CheckResultTagType = typename CheckResultHelper<
PromiseCallbackTraits<ResolveReturnT>::could_resolve,
PromiseCallbackTraits<ResolveReturnT>::could_reject>::TagType;
CheckResultType(promise, CheckResultTagType());
}
void ExecuteThenInternal(AbstractPromise* prerequisite,
AbstractPromise* promise,
std::false_type can_resolve) {
// |prerequisite| can't resolve so don't generate dead code.
}
void ExecuteCatchInternal(AbstractPromise* prerequisite,
AbstractPromise* promise,
std::true_type can_reject) {
RejectOnceCallback* reject_callback =
static_cast<RejectOnceCallback*>(&reject_callback_);
RunHelper<RejectOnceCallback, Rejected<ArgReject>, ResolveStorage,
RejectStorage>::Run(std::move(*reject_callback), prerequisite,
promise);
using CheckResultTagType = typename CheckResultHelper<
PromiseCallbackTraits<RejectReturnT>::could_resolve,
PromiseCallbackTraits<RejectReturnT>::could_reject>::TagType;
CheckResultType(promise, CheckResultTagType());
}
void ExecuteCatchInternal(AbstractPromise* prerequisite,
AbstractPromise* promise,
std::false_type can_reject) {
// |prerequisite| can't reject so don't generate dead code.
}
void CheckResultType(AbstractPromise* promise, CouldResolveOrReject) {
if (promise->IsResolvedWithPromise() ||
promise->value().type() == TypeId::From<ResolveStorage>()) {
promise->OnResolved();
} else {
DCHECK_EQ(promise->value().type(), TypeId::From<RejectStorage>())
<< " See " << promise->from_here().ToString();
promise->OnRejected();
}
}
void CheckResultType(AbstractPromise* promise, CanOnlyResolve) {
promise->OnResolved();
}
void CheckResultType(AbstractPromise* promise, CanOnlyReject) {
promise->OnRejected();
}
};
} // namespace internal
} // namespace base
#endif // BASE_TASK_PROMISE_THEN_AND_CATCH_EXECUTOR_H_
...@@ -40,8 +40,40 @@ bool PostTaskAndReplyTaskRunner::PostTask(const Location& from_here, ...@@ -40,8 +40,40 @@ bool PostTaskAndReplyTaskRunner::PostTask(const Location& from_here,
return destination_->PostTask(from_here, std::move(task)); return destination_->PostTask(from_here, std::move(task));
} }
// TODO(alexclarke): Remove this when TaskRunner::PostPromiseInternal becomes
// pure virtual.
class PromiseHolder {
public:
explicit PromiseHolder(scoped_refptr<internal::AbstractPromise> promise)
: promise_(std::move(promise)) {}
~PromiseHolder() {
// Detect if the promise was not executed and if so cancel to ensure memory
// is released.
if (promise_)
promise_->OnCanceled();
}
PromiseHolder(PromiseHolder&& other) : promise_(std::move(other.promise_)) {}
scoped_refptr<internal::AbstractPromise> Unwrap() const {
return std::move(promise_);
}
private:
mutable scoped_refptr<internal::AbstractPromise> promise_;
};
} // namespace } // namespace
template <>
struct BindUnwrapTraits<PromiseHolder> {
static scoped_refptr<internal::AbstractPromise> Unwrap(
const PromiseHolder& o) {
return o.Unwrap();
}
};
bool TaskRunner::PostTask(const Location& from_here, OnceClosure task) { bool TaskRunner::PostTask(const Location& from_here, OnceClosure task) {
return PostDelayedTask(from_here, std::move(task), base::TimeDelta()); return PostDelayedTask(from_here, std::move(task), base::TimeDelta());
} }
...@@ -58,7 +90,8 @@ bool TaskRunner::PostPromiseInternal( ...@@ -58,7 +90,8 @@ bool TaskRunner::PostPromiseInternal(
base::TimeDelta delay) { base::TimeDelta delay) {
return PostDelayedTask( return PostDelayedTask(
promise->from_here(), promise->from_here(),
BindOnce(&internal::AbstractPromise::Execute, std::move(promise)), delay); BindOnce(&internal::AbstractPromise::Execute, PromiseHolder(promise)),
delay);
} }
TaskRunner::TaskRunner() = default; TaskRunner::TaskRunner() = default;
......
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