1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #if !defined(MozPromise_h_)
10 # include <type_traits>
13 # include "mozilla/Logging.h"
14 # include "mozilla/Maybe.h"
15 # include "mozilla/Monitor.h"
16 # include "mozilla/Mutex.h"
17 # include "mozilla/RefPtr.h"
18 # include "mozilla/UniquePtr.h"
19 # include "mozilla/Variant.h"
20 # include "nsIDirectTaskDispatcher.h"
21 # include "nsISerialEventTarget.h"
22 # include "nsTArray.h"
23 # include "nsThreadUtils.h"
25 # ifdef MOZ_WIDGET_ANDROID
26 # include "mozilla/jni/GeckoResultUtils.h"
29 # if MOZ_DIAGNOSTIC_ASSERT_ENABLED
30 # define PROMISE_DEBUG
34 # define PROMISE_ASSERT MOZ_RELEASE_ASSERT
36 # define PROMISE_ASSERT(...) \
42 # include "nsPrintfCString.h"
51 extern LazyLogModule gMozPromiseLog
;
53 # define PROMISE_LOG(x, ...) \
54 MOZ_LOG(gMozPromiseLog, mozilla::LogLevel::Debug, (x, ##__VA_ARGS__))
58 struct MethodTraitsHelper
: MethodTraitsHelper
<decltype(&F::operator())> {};
59 template <typename ThisType
, typename Ret
, typename
... ArgTypes
>
60 struct MethodTraitsHelper
<Ret (ThisType::*)(ArgTypes
...)> {
61 using ReturnType
= Ret
;
62 static const size_t ArgSize
= sizeof...(ArgTypes
);
64 template <typename ThisType
, typename Ret
, typename
... ArgTypes
>
65 struct MethodTraitsHelper
<Ret (ThisType::*)(ArgTypes
...) const> {
66 using ReturnType
= Ret
;
67 static const size_t ArgSize
= sizeof...(ArgTypes
);
69 template <typename ThisType
, typename Ret
, typename
... ArgTypes
>
70 struct MethodTraitsHelper
<Ret (ThisType::*)(ArgTypes
...) volatile> {
71 using ReturnType
= Ret
;
72 static const size_t ArgSize
= sizeof...(ArgTypes
);
74 template <typename ThisType
, typename Ret
, typename
... ArgTypes
>
75 struct MethodTraitsHelper
<Ret (ThisType::*)(ArgTypes
...) const volatile> {
76 using ReturnType
= Ret
;
77 static const size_t ArgSize
= sizeof...(ArgTypes
);
80 struct MethodTrait
: MethodTraitsHelper
<std::remove_reference_t
<T
>> {};
84 template <typename MethodType
>
86 std::integral_constant
<bool, detail::MethodTrait
<MethodType
>::ArgSize
!= 0>;
88 template <typename MethodType
, typename TargetType
>
90 std::is_convertible
<typename
detail::MethodTrait
<MethodType
>::ReturnType
,
93 template <typename ResolveValueT
, typename RejectValueT
, bool IsExclusive
>
96 template <typename Return
>
97 struct IsMozPromise
: std::false_type
{};
99 template <typename ResolveValueT
, typename RejectValueT
, bool IsExclusive
>
100 struct IsMozPromise
<MozPromise
<ResolveValueT
, RejectValueT
, IsExclusive
>>
104 * A promise manages an asynchronous request that may or may not be able to be
105 * fulfilled immediately. When an API returns a promise, the consumer may attach
106 * callbacks to be invoked (asynchronously, on a specified thread) when the
107 * request is either completed (resolved) or cannot be completed (rejected).
108 * Whereas JS promise callbacks are dispatched from Microtask checkpoints,
109 * MozPromises resolution/rejection make a normal round-trip through the event
110 * loop, which simplifies their ordering semantics relative to other native
113 * MozPromises attempt to mirror the spirit of JS Promises to the extent that
114 * is possible (and desirable) in C++. While the intent is that MozPromises
115 * feel familiar to programmers who are accustomed to their JS-implemented
116 * cousin, we don't shy away from imposing restrictions and adding features that
117 * make sense for the use cases we encounter.
119 * A MozPromise is ThreadSafe, and may be ->Then()ed on any thread. The Then()
120 * call accepts resolve and reject callbacks, and returns a magic object which
121 * will be implicitly converted to a MozPromise::Request or a MozPromise object
122 * depending on how the return value is used. The magic object serves several
123 * purposes for the consumer.
125 * (1) When converting to a MozPromise::Request, it allows the caller to
126 * cancel the delivery of the resolve/reject value if it has not already
127 * occurred, via Disconnect() (this must be done on the target thread to
130 * (2) When converting to a MozPromise (which is called a completion promise),
131 * it allows promise chaining so ->Then() can be called again to attach
132 * more resolve and reject callbacks. If the resolve/reject callback
133 * returns a new MozPromise, that promise is chained to the completion
134 * promise, such that its resolve/reject value will be forwarded along
135 * when it arrives. If the resolve/reject callback returns void, the
136 * completion promise is resolved/rejected with the same value that was
137 * passed to the callback.
139 * The MozPromise APIs skirt traditional XPCOM convention by returning nsRefPtrs
140 * (rather than already_AddRefed) from various methods. This is done to allow
141 * elegant chaining of calls without cluttering up the code with intermediate
142 * variables, and without introducing separate API variants for callers that
143 * want a return value (from, say, ->Then()) from those that don't.
145 * When IsExclusive is true, the MozPromise does a release-mode assertion that
146 * there is at most one call to either Then(...) or ChainTo(...).
149 class MozPromiseRefcountable
{
151 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozPromiseRefcountable
)
153 virtual ~MozPromiseRefcountable() = default;
156 class MozPromiseBase
: public MozPromiseRefcountable
{
158 virtual void AssertIsDead() = 0;
161 template <typename T
>
162 class MozPromiseHolder
;
163 template <typename T
>
164 class MozPromiseRequestHolder
;
165 template <typename ResolveValueT
, typename RejectValueT
, bool IsExclusive
>
166 class MozPromise
: public MozPromiseBase
{
167 static const uint32_t sMagic
= 0xcecace11;
169 // Return a |T&&| to enable move when IsExclusive is true or
170 // a |const T&| to enforce copy otherwise.
171 template <typename T
,
172 typename R
= std::conditional_t
<IsExclusive
, T
&&, const T
&>>
173 static R
MaybeMove(T
& aX
) {
174 return static_cast<R
>(aX
);
178 typedef ResolveValueT ResolveValueType
;
179 typedef RejectValueT RejectValueType
;
180 class ResolveOrRejectValue
{
182 template <typename ResolveValueType_
>
183 void SetResolve(ResolveValueType_
&& aResolveValue
) {
184 MOZ_ASSERT(IsNothing());
185 mValue
= Storage(VariantIndex
<ResolveIndex
>{},
186 std::forward
<ResolveValueType_
>(aResolveValue
));
189 template <typename RejectValueType_
>
190 void SetReject(RejectValueType_
&& aRejectValue
) {
191 MOZ_ASSERT(IsNothing());
192 mValue
= Storage(VariantIndex
<RejectIndex
>{},
193 std::forward
<RejectValueType_
>(aRejectValue
));
196 template <typename ResolveValueType_
>
197 static ResolveOrRejectValue
MakeResolve(ResolveValueType_
&& aResolveValue
) {
198 ResolveOrRejectValue val
;
199 val
.SetResolve(std::forward
<ResolveValueType_
>(aResolveValue
));
203 template <typename RejectValueType_
>
204 static ResolveOrRejectValue
MakeReject(RejectValueType_
&& aRejectValue
) {
205 ResolveOrRejectValue val
;
206 val
.SetReject(std::forward
<RejectValueType_
>(aRejectValue
));
210 bool IsResolve() const { return mValue
.template is
<ResolveIndex
>(); }
211 bool IsReject() const { return mValue
.template is
<RejectIndex
>(); }
212 bool IsNothing() const { return mValue
.template is
<NothingIndex
>(); }
214 const ResolveValueType
& ResolveValue() const {
215 return mValue
.template as
<ResolveIndex
>();
217 ResolveValueType
& ResolveValue() {
218 return mValue
.template as
<ResolveIndex
>();
220 const RejectValueType
& RejectValue() const {
221 return mValue
.template as
<RejectIndex
>();
223 RejectValueType
& RejectValue() { return mValue
.template as
<RejectIndex
>(); }
226 enum { NothingIndex
, ResolveIndex
, RejectIndex
};
227 using Storage
= Variant
<Nothing
, ResolveValueType
, RejectValueType
>;
228 Storage mValue
= Storage(VariantIndex
<NothingIndex
>{});
232 // MozPromise is the public type, and never constructed directly. Construct
233 // a MozPromise::Private, defined below.
234 MozPromise(const char* aCreationSite
, bool aIsCompletionPromise
)
235 : mCreationSite(aCreationSite
),
236 mMutex("MozPromise Mutex"),
238 mIsCompletionPromise(aIsCompletionPromise
)
239 # ifdef PROMISE_DEBUG
244 PROMISE_LOG("%s creating MozPromise (%p)", mCreationSite
, this);
248 // MozPromise::Private allows us to separate the public interface (upon which
249 // consumers of the promise may invoke methods like Then()) from the private
250 // interface (upon which the creator of the promise may invoke Resolve() or
251 // Reject()). APIs should create and store a MozPromise::Private (usually
252 // via a MozPromiseHolder), and return a MozPromise to consumers.
254 // NB: We can include the definition of this class inline once B2G ICS is
258 template <typename ResolveValueType_
>
259 [[nodiscard
]] static RefPtr
<MozPromise
> CreateAndResolve(
260 ResolveValueType_
&& aResolveValue
, const char* aResolveSite
) {
261 static_assert(std::is_convertible_v
<ResolveValueType_
, ResolveValueT
>,
262 "Resolve() argument must be implicitly convertible to "
263 "MozPromise's ResolveValueT");
264 RefPtr
<typename
MozPromise::Private
> p
=
265 new MozPromise::Private(aResolveSite
);
266 p
->Resolve(std::forward
<ResolveValueType_
>(aResolveValue
), aResolveSite
);
270 template <typename RejectValueType_
>
271 [[nodiscard
]] static RefPtr
<MozPromise
> CreateAndReject(
272 RejectValueType_
&& aRejectValue
, const char* aRejectSite
) {
273 static_assert(std::is_convertible_v
<RejectValueType_
, RejectValueT
>,
274 "Reject() argument must be implicitly convertible to "
275 "MozPromise's RejectValueT");
276 RefPtr
<typename
MozPromise::Private
> p
=
277 new MozPromise::Private(aRejectSite
);
278 p
->Reject(std::forward
<RejectValueType_
>(aRejectValue
), aRejectSite
);
282 template <typename ResolveOrRejectValueType_
>
283 [[nodiscard
]] static RefPtr
<MozPromise
> CreateAndResolveOrReject(
284 ResolveOrRejectValueType_
&& aValue
, const char* aSite
) {
285 RefPtr
<typename
MozPromise::Private
> p
= new MozPromise::Private(aSite
);
286 p
->ResolveOrReject(std::forward
<ResolveOrRejectValueType_
>(aValue
), aSite
);
290 typedef MozPromise
<CopyableTArray
<ResolveValueType
>, RejectValueType
,
294 typedef MozPromise
<CopyableTArray
<ResolveOrRejectValue
>, bool, IsExclusive
>
295 AllSettledPromiseType
;
298 class AllPromiseHolder
: public MozPromiseRefcountable
{
300 explicit AllPromiseHolder(size_t aDependentPromises
)
301 : mPromise(new typename
AllPromiseType::Private(__func__
)),
302 mOutstandingPromises(aDependentPromises
) {
303 MOZ_ASSERT(aDependentPromises
> 0);
304 mResolveValues
.SetLength(aDependentPromises
);
307 template <typename ResolveValueType_
>
308 void Resolve(size_t aIndex
, ResolveValueType_
&& aResolveValue
) {
314 mResolveValues
[aIndex
].emplace(
315 std::forward
<ResolveValueType_
>(aResolveValue
));
316 if (--mOutstandingPromises
== 0) {
317 nsTArray
<ResolveValueType
> resolveValues
;
318 resolveValues
.SetCapacity(mResolveValues
.Length());
319 for (auto&& resolveValue
: mResolveValues
) {
320 resolveValues
.AppendElement(std::move(resolveValue
.ref()));
323 mPromise
->Resolve(std::move(resolveValues
), __func__
);
325 mResolveValues
.Clear();
329 template <typename RejectValueType_
>
330 void Reject(RejectValueType_
&& aRejectValue
) {
336 mPromise
->Reject(std::forward
<RejectValueType_
>(aRejectValue
), __func__
);
338 mResolveValues
.Clear();
341 AllPromiseType
* Promise() { return mPromise
; }
344 nsTArray
<Maybe
<ResolveValueType
>> mResolveValues
;
345 RefPtr
<typename
AllPromiseType::Private
> mPromise
;
346 size_t mOutstandingPromises
;
349 // Trying to pass ResolveOrRejectValue by value fails static analysis checks,
350 // so we need to use either a const& or an rvalue reference, depending on
351 // whether IsExclusive is true or not.
352 typedef std::conditional_t
<IsExclusive
, ResolveOrRejectValue
&&,
353 const ResolveOrRejectValue
&>
354 ResolveOrRejectValueParam
;
356 typedef std::conditional_t
<IsExclusive
, ResolveValueType
&&,
357 const ResolveValueType
&>
358 ResolveValueTypeParam
;
360 typedef std::conditional_t
<IsExclusive
, RejectValueType
&&,
361 const RejectValueType
&>
362 RejectValueTypeParam
;
364 class AllSettledPromiseHolder
: public MozPromiseRefcountable
{
366 explicit AllSettledPromiseHolder(size_t aDependentPromises
)
367 : mPromise(new typename
AllSettledPromiseType::Private(__func__
)),
368 mOutstandingPromises(aDependentPromises
) {
369 MOZ_ASSERT(aDependentPromises
> 0);
370 mValues
.SetLength(aDependentPromises
);
373 void Settle(size_t aIndex
, ResolveOrRejectValueParam aValue
) {
379 mValues
[aIndex
].emplace(MaybeMove(aValue
));
380 if (--mOutstandingPromises
== 0) {
381 nsTArray
<ResolveOrRejectValue
> values
;
382 values
.SetCapacity(mValues
.Length());
383 for (auto&& value
: mValues
) {
384 values
.AppendElement(std::move(value
.ref()));
387 mPromise
->Resolve(std::move(values
), __func__
);
393 AllSettledPromiseType
* Promise() { return mPromise
; }
396 nsTArray
<Maybe
<ResolveOrRejectValue
>> mValues
;
397 RefPtr
<typename
AllSettledPromiseType::Private
> mPromise
;
398 size_t mOutstandingPromises
;
402 [[nodiscard
]] static RefPtr
<AllPromiseType
> All(
403 nsISerialEventTarget
* aProcessingTarget
,
404 nsTArray
<RefPtr
<MozPromise
>>& aPromises
) {
405 if (aPromises
.Length() == 0) {
406 return AllPromiseType::CreateAndResolve(
407 CopyableTArray
<ResolveValueType
>(), __func__
);
410 RefPtr
<AllPromiseHolder
> holder
= new AllPromiseHolder(aPromises
.Length());
411 RefPtr
<AllPromiseType
> promise
= holder
->Promise();
412 for (size_t i
= 0; i
< aPromises
.Length(); ++i
) {
414 aProcessingTarget
, __func__
,
415 [holder
, i
](ResolveValueTypeParam aResolveValue
) -> void {
416 holder
->Resolve(i
, MaybeMove(aResolveValue
));
418 [holder
](RejectValueTypeParam aRejectValue
) -> void {
419 holder
->Reject(MaybeMove(aRejectValue
));
425 [[nodiscard
]] static RefPtr
<AllSettledPromiseType
> AllSettled(
426 nsISerialEventTarget
* aProcessingTarget
,
427 nsTArray
<RefPtr
<MozPromise
>>& aPromises
) {
428 if (aPromises
.Length() == 0) {
429 return AllSettledPromiseType::CreateAndResolve(
430 CopyableTArray
<ResolveOrRejectValue
>(), __func__
);
433 RefPtr
<AllSettledPromiseHolder
> holder
=
434 new AllSettledPromiseHolder(aPromises
.Length());
435 RefPtr
<AllSettledPromiseType
> promise
= holder
->Promise();
436 for (size_t i
= 0; i
< aPromises
.Length(); ++i
) {
437 aPromises
[i
]->Then(aProcessingTarget
, __func__
,
438 [holder
, i
](ResolveOrRejectValueParam aValue
) -> void {
439 holder
->Settle(i
, MaybeMove(aValue
));
445 class Request
: public MozPromiseRefcountable
{
447 virtual void Disconnect() = 0;
450 Request() : mComplete(false), mDisconnected(false) {}
451 virtual ~Request() = default;
459 * A ThenValue tracks a single consumer waiting on the promise. When a
460 * consumer invokes promise->Then(...), a ThenValue is created. Once the
461 * Promise is resolved or rejected, a {Resolve,Reject}Runnable is dispatched,
462 * which invokes the resolve/reject method and then deletes the ThenValue.
464 class ThenValueBase
: public Request
{
465 friend class MozPromise
;
466 static const uint32_t sMagic
= 0xfadece11;
469 class ResolveOrRejectRunnable
: public CancelableRunnable
{
471 ResolveOrRejectRunnable(ThenValueBase
* aThenValue
, MozPromise
* aPromise
)
472 : CancelableRunnable(
473 "MozPromise::ThenValueBase::ResolveOrRejectRunnable"),
474 mThenValue(aThenValue
),
476 MOZ_DIAGNOSTIC_ASSERT(!mPromise
->IsPending());
479 ~ResolveOrRejectRunnable() {
481 mThenValue
->AssertIsDead();
485 NS_IMETHOD
Run() override
{
486 PROMISE_LOG("ResolveOrRejectRunnable::Run() [this=%p]", this);
487 mThenValue
->DoResolveOrReject(mPromise
->Value());
488 mThenValue
= nullptr;
493 nsresult
Cancel() override
{ return Run(); }
496 RefPtr
<ThenValueBase
> mThenValue
;
497 RefPtr
<MozPromise
> mPromise
;
500 ThenValueBase(nsISerialEventTarget
* aResponseTarget
, const char* aCallSite
)
501 : mResponseTarget(aResponseTarget
), mCallSite(aCallSite
) {
502 MOZ_ASSERT(aResponseTarget
);
505 # ifdef PROMISE_DEBUG
512 void AssertIsDead() {
513 PROMISE_ASSERT(mMagic1
== sMagic
&& mMagic2
== sMagic
);
514 // We want to assert that this ThenValues is dead - that is to say, that
515 // there are no consumers waiting for the result. In the case of a normal
516 // ThenValue, we check that it has been disconnected, which is the way
517 // that the consumer signals that it no longer wishes to hear about the
518 // result. If this ThenValue has a completion promise (which is mutually
519 // exclusive with being disconnectable), we recursively assert that every
520 // ThenValue associated with the completion promise is dead.
521 if (MozPromiseBase
* p
= CompletionPromise()) {
524 MOZ_DIAGNOSTIC_ASSERT(Request::mDisconnected
);
528 void Dispatch(MozPromise
* aPromise
) {
529 PROMISE_ASSERT(mMagic1
== sMagic
&& mMagic2
== sMagic
);
530 aPromise
->mMutex
.AssertCurrentThreadOwns();
531 MOZ_ASSERT(!aPromise
->IsPending());
533 nsCOMPtr
<nsIRunnable
> r
= new ResolveOrRejectRunnable(this, aPromise
);
535 "%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p] "
537 aPromise
->mValue
.IsResolve() ? "Resolving" : "Rejecting", mCallSite
,
538 r
.get(), aPromise
, this,
539 aPromise
->mUseSynchronousTaskDispatch
? "synchronous"
540 : aPromise
->mUseDirectTaskDispatch
? "directtask"
543 if (aPromise
->mUseSynchronousTaskDispatch
&&
544 mResponseTarget
->IsOnCurrentThread()) {
545 PROMISE_LOG("ThenValue::Dispatch running task synchronously [this=%p]",
551 if (aPromise
->mUseDirectTaskDispatch
&&
552 mResponseTarget
->IsOnCurrentThread()) {
554 "ThenValue::Dispatch dispatch task via direct task queue [this=%p]",
556 nsCOMPtr
<nsIDirectTaskDispatcher
> dispatcher
=
557 do_QueryInterface(mResponseTarget
);
559 dispatcher
->DispatchDirectTask(r
.forget());
564 "Direct Task dispatching not available for thread \"%s\"",
565 PR_GetThreadName(PR_GetCurrentThread()))
567 MOZ_DIAGNOSTIC_ASSERT(
569 "mResponseTarget must implement nsIDirectTaskDispatcher for direct "
573 // Promise consumers are allowed to disconnect the Request object and
574 // then shut down the thread or task queue that the promise result would
575 // be dispatched on. So we unfortunately can't assert that promise
576 // dispatch succeeds. :-(
577 mResponseTarget
->Dispatch(r
.forget());
580 void Disconnect() override
{
581 MOZ_DIAGNOSTIC_ASSERT(mResponseTarget
->IsOnCurrentThread());
582 MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete
);
583 Request::mDisconnected
= true;
585 // We could support rejecting the completion promise on disconnection, but
586 // then we'd need to have some sort of default reject value. The use cases
587 // of disconnection and completion promise chaining seem pretty
588 // orthogonal, so let's use assert against it.
589 MOZ_DIAGNOSTIC_ASSERT(!CompletionPromise());
593 virtual MozPromiseBase
* CompletionPromise() const = 0;
594 virtual void DoResolveOrRejectInternal(ResolveOrRejectValue
& aValue
) = 0;
596 void DoResolveOrReject(ResolveOrRejectValue
& aValue
) {
597 PROMISE_ASSERT(mMagic1
== sMagic
&& mMagic2
== sMagic
);
598 MOZ_DIAGNOSTIC_ASSERT(mResponseTarget
->IsOnCurrentThread());
599 Request::mComplete
= true;
600 if (Request::mDisconnected
) {
602 "ThenValue::DoResolveOrReject disconnected - bailing out [this=%p]",
607 // Invoke the resolve or reject method.
608 DoResolveOrRejectInternal(aValue
);
611 nsCOMPtr
<nsISerialEventTarget
>
612 mResponseTarget
; // May be released on any thread.
613 # ifdef PROMISE_DEBUG
614 uint32_t mMagic1
= sMagic
;
616 const char* mCallSite
;
617 # ifdef PROMISE_DEBUG
618 uint32_t mMagic2
= sMagic
;
623 * We create two overloads for invoking Resolve/Reject Methods so as to
624 * make the resolve/reject value argument "optional".
626 template <typename ThisType
, typename MethodType
, typename ValueType
>
627 static std::enable_if_t
<TakesArgument
<MethodType
>::value
,
628 typename
detail::MethodTrait
<MethodType
>::ReturnType
>
629 InvokeMethod(ThisType
* aThisVal
, MethodType aMethod
, ValueType
&& aValue
) {
630 return (aThisVal
->*aMethod
)(std::forward
<ValueType
>(aValue
));
633 template <typename ThisType
, typename MethodType
, typename ValueType
>
634 static std::enable_if_t
<!TakesArgument
<MethodType
>::value
,
635 typename
detail::MethodTrait
<MethodType
>::ReturnType
>
636 InvokeMethod(ThisType
* aThisVal
, MethodType aMethod
, ValueType
&& aValue
) {
637 return (aThisVal
->*aMethod
)();
640 // Called when promise chaining is supported.
641 template <bool SupportChaining
, typename ThisType
, typename MethodType
,
642 typename ValueType
, typename CompletionPromiseType
>
643 static std::enable_if_t
<SupportChaining
, void> InvokeCallbackMethod(
644 ThisType
* aThisVal
, MethodType aMethod
, ValueType
&& aValue
,
645 CompletionPromiseType
&& aCompletionPromise
) {
646 auto p
= InvokeMethod(aThisVal
, aMethod
, std::forward
<ValueType
>(aValue
));
647 if (aCompletionPromise
) {
648 p
->ChainTo(aCompletionPromise
.forget(), "<chained completion promise>");
652 // Called when promise chaining is not supported.
653 template <bool SupportChaining
, typename ThisType
, typename MethodType
,
654 typename ValueType
, typename CompletionPromiseType
>
655 static std::enable_if_t
<!SupportChaining
, void> InvokeCallbackMethod(
656 ThisType
* aThisVal
, MethodType aMethod
, ValueType
&& aValue
,
657 CompletionPromiseType
&& aCompletionPromise
) {
658 MOZ_DIAGNOSTIC_ASSERT(
660 "Can't do promise chaining for a non-promise-returning method.");
661 InvokeMethod(aThisVal
, aMethod
, std::forward
<ValueType
>(aValue
));
667 template <typename
...>
670 template <typename ThisType
, typename ResolveMethodType
,
671 typename RejectMethodType
>
672 class ThenValue
<ThisType
*, ResolveMethodType
, RejectMethodType
>
673 : public ThenValueBase
{
674 friend class ThenCommand
<ThenValue
>;
676 using R1
= typename RemoveSmartPointer
<
677 typename
detail::MethodTrait
<ResolveMethodType
>::ReturnType
>::Type
;
678 using R2
= typename RemoveSmartPointer
<
679 typename
detail::MethodTrait
<RejectMethodType
>::ReturnType
>::Type
;
680 using SupportChaining
=
681 std::integral_constant
<bool, IsMozPromise
<R1
>::value
&&
682 std::is_same_v
<R1
, R2
>>;
684 // Fall back to MozPromise when promise chaining is not supported to make
687 std::conditional_t
<SupportChaining::value
, R1
, MozPromise
>;
690 ThenValue(nsISerialEventTarget
* aResponseTarget
, ThisType
* aThisVal
,
691 ResolveMethodType aResolveMethod
, RejectMethodType aRejectMethod
,
692 const char* aCallSite
)
693 : ThenValueBase(aResponseTarget
, aCallSite
),
695 mResolveMethod(aResolveMethod
),
696 mRejectMethod(aRejectMethod
) {}
698 void Disconnect() override
{
699 ThenValueBase::Disconnect();
701 // If a Request has been disconnected, we don't guarantee that the
702 // resolve/reject runnable will be dispatched. Null out our refcounted
703 // this-value now so that it's released predictably on the dispatch
709 MozPromiseBase
* CompletionPromise() const override
{
710 return mCompletionPromise
;
713 void DoResolveOrRejectInternal(ResolveOrRejectValue
& aValue
) override
{
714 if (aValue
.IsResolve()) {
715 InvokeCallbackMethod
<SupportChaining::value
>(
716 mThisVal
.get(), mResolveMethod
, MaybeMove(aValue
.ResolveValue()),
717 std::move(mCompletionPromise
));
719 InvokeCallbackMethod
<SupportChaining::value
>(
720 mThisVal
.get(), mRejectMethod
, MaybeMove(aValue
.RejectValue()),
721 std::move(mCompletionPromise
));
724 // Null out mThisVal after invoking the callback so that any references
725 // are released predictably on the dispatch thread. Otherwise, it would be
726 // released on whatever thread last drops its reference to the ThenValue,
727 // which may or may not be ok.
733 mThisVal
; // Only accessed and refcounted on dispatch thread.
734 ResolveMethodType mResolveMethod
;
735 RejectMethodType mRejectMethod
;
736 RefPtr
<typename
PromiseType::Private
> mCompletionPromise
;
739 template <typename ThisType
, typename ResolveRejectMethodType
>
740 class ThenValue
<ThisType
*, ResolveRejectMethodType
> : public ThenValueBase
{
741 friend class ThenCommand
<ThenValue
>;
743 using R1
= typename RemoveSmartPointer
<typename
detail::MethodTrait
<
744 ResolveRejectMethodType
>::ReturnType
>::Type
;
745 using SupportChaining
=
746 std::integral_constant
<bool, IsMozPromise
<R1
>::value
>;
748 // Fall back to MozPromise when promise chaining is not supported to make
751 std::conditional_t
<SupportChaining::value
, R1
, MozPromise
>;
754 ThenValue(nsISerialEventTarget
* aResponseTarget
, ThisType
* aThisVal
,
755 ResolveRejectMethodType aResolveRejectMethod
,
756 const char* aCallSite
)
757 : ThenValueBase(aResponseTarget
, aCallSite
),
759 mResolveRejectMethod(aResolveRejectMethod
) {}
761 void Disconnect() override
{
762 ThenValueBase::Disconnect();
764 // If a Request has been disconnected, we don't guarantee that the
765 // resolve/reject runnable will be dispatched. Null out our refcounted
766 // this-value now so that it's released predictably on the dispatch
772 MozPromiseBase
* CompletionPromise() const override
{
773 return mCompletionPromise
;
776 void DoResolveOrRejectInternal(ResolveOrRejectValue
& aValue
) override
{
777 InvokeCallbackMethod
<SupportChaining::value
>(
778 mThisVal
.get(), mResolveRejectMethod
, MaybeMove(aValue
),
779 std::move(mCompletionPromise
));
781 // Null out mThisVal after invoking the callback so that any references
782 // are released predictably on the dispatch thread. Otherwise, it would be
783 // released on whatever thread last drops its reference to the ThenValue,
784 // which may or may not be ok.
790 mThisVal
; // Only accessed and refcounted on dispatch thread.
791 ResolveRejectMethodType mResolveRejectMethod
;
792 RefPtr
<typename
PromiseType::Private
> mCompletionPromise
;
795 // NB: We could use std::function here instead of a template if it were
797 template <typename ResolveFunction
, typename RejectFunction
>
798 class ThenValue
<ResolveFunction
, RejectFunction
> : public ThenValueBase
{
799 friend class ThenCommand
<ThenValue
>;
801 using R1
= typename RemoveSmartPointer
<
802 typename
detail::MethodTrait
<ResolveFunction
>::ReturnType
>::Type
;
803 using R2
= typename RemoveSmartPointer
<
804 typename
detail::MethodTrait
<RejectFunction
>::ReturnType
>::Type
;
805 using SupportChaining
=
806 std::integral_constant
<bool, IsMozPromise
<R1
>::value
&&
807 std::is_same_v
<R1
, R2
>>;
809 // Fall back to MozPromise when promise chaining is not supported to make
812 std::conditional_t
<SupportChaining::value
, R1
, MozPromise
>;
815 ThenValue(nsISerialEventTarget
* aResponseTarget
,
816 ResolveFunction
&& aResolveFunction
,
817 RejectFunction
&& aRejectFunction
, const char* aCallSite
)
818 : ThenValueBase(aResponseTarget
, aCallSite
) {
819 mResolveFunction
.emplace(std::move(aResolveFunction
));
820 mRejectFunction
.emplace(std::move(aRejectFunction
));
823 void Disconnect() override
{
824 ThenValueBase::Disconnect();
826 // If a Request has been disconnected, we don't guarantee that the
827 // resolve/reject runnable will be dispatched. Destroy our callbacks
828 // now so that any references in closures are released predictable on
829 // the dispatch thread.
830 mResolveFunction
.reset();
831 mRejectFunction
.reset();
835 MozPromiseBase
* CompletionPromise() const override
{
836 return mCompletionPromise
;
839 void DoResolveOrRejectInternal(ResolveOrRejectValue
& aValue
) override
{
840 // Note: The usage of InvokeCallbackMethod here requires that
841 // ResolveFunction/RejectFunction are capture-lambdas (i.e. anonymous
842 // classes with ::operator()), since it allows us to share code more
843 // easily. We could fix this if need be, though it's quite easy to work
844 // around by just capturing something.
845 if (aValue
.IsResolve()) {
846 InvokeCallbackMethod
<SupportChaining::value
>(
847 mResolveFunction
.ptr(), &ResolveFunction::operator(),
848 MaybeMove(aValue
.ResolveValue()), std::move(mCompletionPromise
));
850 InvokeCallbackMethod
<SupportChaining::value
>(
851 mRejectFunction
.ptr(), &RejectFunction::operator(),
852 MaybeMove(aValue
.RejectValue()), std::move(mCompletionPromise
));
855 // Destroy callbacks after invocation so that any references in closures
856 // are released predictably on the dispatch thread. Otherwise, they would
857 // be released on whatever thread last drops its reference to the
858 // ThenValue, which may or may not be ok.
859 mResolveFunction
.reset();
860 mRejectFunction
.reset();
864 Maybe
<ResolveFunction
>
865 mResolveFunction
; // Only accessed and deleted on dispatch thread.
866 Maybe
<RejectFunction
>
867 mRejectFunction
; // Only accessed and deleted on dispatch thread.
868 RefPtr
<typename
PromiseType::Private
> mCompletionPromise
;
871 template <typename ResolveRejectFunction
>
872 class ThenValue
<ResolveRejectFunction
> : public ThenValueBase
{
873 friend class ThenCommand
<ThenValue
>;
875 using R1
= typename RemoveSmartPointer
<
876 typename
detail::MethodTrait
<ResolveRejectFunction
>::ReturnType
>::Type
;
877 using SupportChaining
=
878 std::integral_constant
<bool, IsMozPromise
<R1
>::value
>;
880 // Fall back to MozPromise when promise chaining is not supported to make
883 std::conditional_t
<SupportChaining::value
, R1
, MozPromise
>;
886 ThenValue(nsISerialEventTarget
* aResponseTarget
,
887 ResolveRejectFunction
&& aResolveRejectFunction
,
888 const char* aCallSite
)
889 : ThenValueBase(aResponseTarget
, aCallSite
) {
890 mResolveRejectFunction
.emplace(std::move(aResolveRejectFunction
));
893 void Disconnect() override
{
894 ThenValueBase::Disconnect();
896 // If a Request has been disconnected, we don't guarantee that the
897 // resolve/reject runnable will be dispatched. Destroy our callbacks
898 // now so that any references in closures are released predictable on
899 // the dispatch thread.
900 mResolveRejectFunction
.reset();
904 MozPromiseBase
* CompletionPromise() const override
{
905 return mCompletionPromise
;
908 void DoResolveOrRejectInternal(ResolveOrRejectValue
& aValue
) override
{
909 // Note: The usage of InvokeCallbackMethod here requires that
910 // ResolveRejectFunction is capture-lambdas (i.e. anonymous
911 // classes with ::operator()), since it allows us to share code more
912 // easily. We could fix this if need be, though it's quite easy to work
913 // around by just capturing something.
914 InvokeCallbackMethod
<SupportChaining::value
>(
915 mResolveRejectFunction
.ptr(), &ResolveRejectFunction::operator(),
916 MaybeMove(aValue
), std::move(mCompletionPromise
));
918 // Destroy callbacks after invocation so that any references in closures
919 // are released predictably on the dispatch thread. Otherwise, they would
920 // be released on whatever thread last drops its reference to the
921 // ThenValue, which may or may not be ok.
922 mResolveRejectFunction
.reset();
926 Maybe
<ResolveRejectFunction
>
927 mResolveRejectFunction
; // Only accessed and deleted on dispatch
929 RefPtr
<typename
PromiseType::Private
> mCompletionPromise
;
933 void ThenInternal(already_AddRefed
<ThenValueBase
> aThenValue
,
934 const char* aCallSite
) {
935 PROMISE_ASSERT(mMagic1
== sMagic
&& mMagic2
== sMagic
&&
936 mMagic3
== sMagic
&& mMagic4
== &mMutex
);
937 RefPtr
<ThenValueBase
> thenValue
= aThenValue
;
938 MutexAutoLock
lock(mMutex
);
939 MOZ_DIAGNOSTIC_ASSERT(
940 !IsExclusive
|| !mHaveRequest
,
941 "Using an exclusive promise in a non-exclusive fashion");
943 PROMISE_LOG("%s invoking Then() [this=%p, aThenValue=%p, isPending=%d]",
944 aCallSite
, this, thenValue
.get(), (int)IsPending());
946 thenValue
->Dispatch(this);
948 mThenValues
.AppendElement(thenValue
.forget());
954 * A command object to store all information needed to make a request to
955 * the promise. This allows us to delay the request until further use is
956 * known (whether it is ->Then() again for more promise chaining or ->Track()
957 * to terminate chaining and issue the request).
959 * This allows a unified syntax for promise chaining and disconnection
960 * and feels more like its JS counterpart.
962 template <typename ThenValueType
>
964 // Allow Promise1::ThenCommand to access the private constructor,
965 // Promise2::ThenCommand(ThenCommand&&).
966 template <typename
, typename
, bool>
967 friend class MozPromise
;
969 using PromiseType
= typename
ThenValueType::PromiseType
;
970 using Private
= typename
PromiseType::Private
;
972 ThenCommand(const char* aCallSite
,
973 already_AddRefed
<ThenValueType
> aThenValue
,
974 MozPromise
* aReceiver
)
975 : mCallSite(aCallSite
), mThenValue(aThenValue
), mReceiver(aReceiver
) {}
977 ThenCommand(ThenCommand
&& aOther
) = default;
981 // Issue the request now if the return value of Then() is not used.
983 mReceiver
->ThenInternal(mThenValue
.forget(), mCallSite
);
987 // Allow RefPtr<MozPromise> p = somePromise->Then();
988 // p->Then(thread1, ...);
989 // p->Then(thread2, ...);
990 operator RefPtr
<PromiseType
>() {
992 ThenValueType::SupportChaining::value
,
993 "The resolve/reject callback needs to return a RefPtr<MozPromise> "
994 "in order to do promise chaining.");
996 // mCompletionPromise must be created before ThenInternal() to avoid race.
998 new Private("<completion promise>", true /* aIsCompletionPromise */);
999 mThenValue
->mCompletionPromise
= p
;
1000 // Note ThenInternal() might nullify mCompletionPromise before return.
1001 // So we need to return p instead of mCompletionPromise.
1002 mReceiver
->ThenInternal(mThenValue
.forget(), mCallSite
);
1006 template <typename
... Ts
>
1007 auto Then(Ts
&&... aArgs
) -> decltype(std::declval
<PromiseType
>().Then(
1008 std::forward
<Ts
>(aArgs
)...)) {
1009 return static_cast<RefPtr
<PromiseType
>>(*this)->Then(
1010 std::forward
<Ts
>(aArgs
)...);
1013 void Track(MozPromiseRequestHolder
<MozPromise
>& aRequestHolder
) {
1014 aRequestHolder
.Track(do_AddRef(mThenValue
));
1015 mReceiver
->ThenInternal(mThenValue
.forget(), mCallSite
);
1018 // Allow calling ->Then() again for more promise chaining or ->Track() to
1019 // end chaining and track the request for future disconnection.
1020 ThenCommand
* operator->() { return this; }
1023 const char* mCallSite
;
1024 RefPtr
<ThenValueType
> mThenValue
;
1025 RefPtr
<MozPromise
> mReceiver
;
1029 template <typename ThisType
, typename
... Methods
,
1030 typename ThenValueType
= ThenValue
<ThisType
*, Methods
...>,
1031 typename ReturnType
= ThenCommand
<ThenValueType
>>
1032 ReturnType
Then(nsISerialEventTarget
* aResponseTarget
, const char* aCallSite
,
1033 ThisType
* aThisVal
, Methods
... aMethods
) {
1034 RefPtr
<ThenValueType
> thenValue
=
1035 new ThenValueType(aResponseTarget
, aThisVal
, aMethods
..., aCallSite
);
1036 return ReturnType(aCallSite
, thenValue
.forget(), this);
1039 template <typename
... Functions
,
1040 typename ThenValueType
= ThenValue
<Functions
...>,
1041 typename ReturnType
= ThenCommand
<ThenValueType
>>
1042 ReturnType
Then(nsISerialEventTarget
* aResponseTarget
, const char* aCallSite
,
1043 Functions
&&... aFunctions
) {
1044 RefPtr
<ThenValueType
> thenValue
=
1045 new ThenValueType(aResponseTarget
, std::move(aFunctions
)..., aCallSite
);
1046 return ReturnType(aCallSite
, thenValue
.forget(), this);
1049 void ChainTo(already_AddRefed
<Private
> aChainedPromise
,
1050 const char* aCallSite
) {
1051 MutexAutoLock
lock(mMutex
);
1052 MOZ_DIAGNOSTIC_ASSERT(
1053 !IsExclusive
|| !mHaveRequest
,
1054 "Using an exclusive promise in a non-exclusive fashion");
1055 mHaveRequest
= true;
1056 RefPtr
<Private
> chainedPromise
= aChainedPromise
;
1058 "%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]",
1059 aCallSite
, this, chainedPromise
.get(), (int)IsPending());
1061 // We want to use the same type of dispatching method with the chained
1064 // We need to ensure that the UseSynchronousTaskDispatch branch isn't taken
1065 // at compilation time to ensure we're not triggering the static_assert in
1066 // UseSynchronousTaskDispatch method. if constexpr (IsExclusive) ensures
1068 if (mUseDirectTaskDispatch
) {
1069 chainedPromise
->UseDirectTaskDispatch(aCallSite
);
1070 } else if constexpr (IsExclusive
) {
1071 if (mUseSynchronousTaskDispatch
) {
1072 chainedPromise
->UseSynchronousTaskDispatch(aCallSite
);
1077 ForwardTo(chainedPromise
);
1079 mChainedPromises
.AppendElement(chainedPromise
);
1083 # ifdef MOZ_WIDGET_ANDROID
1084 // Creates a C++ MozPromise from its Java counterpart, GeckoResult.
1085 [[nodiscard
]] static RefPtr
<MozPromise
> FromGeckoResult(
1086 java::GeckoResult::Param aGeckoResult
) {
1087 using jni::GeckoResultCallback
;
1088 RefPtr
<Private
> p
= new Private("GeckoResult Glue", false);
1089 auto resolve
= GeckoResultCallback::CreateAndAttach
<ResolveValueType
>(
1090 [p
](ResolveValueType
&& aArg
) {
1091 p
->Resolve(MaybeMove(aArg
), __func__
);
1093 auto reject
= GeckoResultCallback::CreateAndAttach
<RejectValueType
>(
1094 [p
](RejectValueType
&& aArg
) { p
->Reject(MaybeMove(aArg
), __func__
); });
1095 aGeckoResult
->NativeThen(resolve
, reject
);
1100 // Creates a C++ MozPromise from its JS counterpart, dom::Promise.
1101 // FromDomPromise currently only supports primitive types (int8/16/32, float,
1102 // double) And the reject value type must be a nsresult.
1103 // To use, please include MozPromiseInlines.h
1104 static RefPtr
<MozPromise
> FromDomPromise(dom::Promise
* aDOMPromise
);
1106 // Note we expose the function AssertIsDead() instead of IsDead() since
1107 // checking IsDead() is a data race in the situation where the request is not
1108 // dead. Therefore we enforce the form |Assert(IsDead())| by exposing
1109 // AssertIsDead() only.
1110 void AssertIsDead() override
{
1111 PROMISE_ASSERT(mMagic1
== sMagic
&& mMagic2
== sMagic
&&
1112 mMagic3
== sMagic
&& mMagic4
== &mMutex
);
1113 MutexAutoLock
lock(mMutex
);
1114 for (auto&& then
: mThenValues
) {
1115 then
->AssertIsDead();
1117 for (auto&& chained
: mChainedPromises
) {
1118 chained
->AssertIsDead();
1122 bool IsResolved() const { return mValue
.IsResolve(); }
1125 bool IsPending() const { return mValue
.IsNothing(); }
1127 ResolveOrRejectValue
& Value() {
1128 // This method should only be called once the value has stabilized. As
1129 // such, we don't need to acquire the lock here.
1130 MOZ_DIAGNOSTIC_ASSERT(!IsPending());
1134 void DispatchAll() {
1135 mMutex
.AssertCurrentThreadOwns();
1136 for (auto&& thenValue
: mThenValues
) {
1137 thenValue
->Dispatch(this);
1139 mThenValues
.Clear();
1141 for (auto&& chainedPromise
: mChainedPromises
) {
1142 ForwardTo(chainedPromise
);
1144 mChainedPromises
.Clear();
1147 void ForwardTo(Private
* aOther
) {
1148 MOZ_ASSERT(!IsPending());
1149 if (mValue
.IsResolve()) {
1150 aOther
->Resolve(MaybeMove(mValue
.ResolveValue()), "<chained promise>");
1152 aOther
->Reject(MaybeMove(mValue
.RejectValue()), "<chained promise>");
1156 virtual ~MozPromise() {
1157 PROMISE_LOG("MozPromise::~MozPromise [this=%p]", this);
1159 // We can't guarantee a completion promise will always be revolved or
1160 // rejected since ResolveOrRejectRunnable might not run when dispatch fails.
1161 if (!mIsCompletionPromise
) {
1162 MOZ_ASSERT(!IsPending());
1163 MOZ_ASSERT(mThenValues
.IsEmpty());
1164 MOZ_ASSERT(mChainedPromises
.IsEmpty());
1166 # ifdef PROMISE_DEBUG
1174 const char* mCreationSite
; // For logging
1176 ResolveOrRejectValue mValue
;
1177 bool mUseSynchronousTaskDispatch
= false;
1178 bool mUseDirectTaskDispatch
= false;
1179 # ifdef PROMISE_DEBUG
1180 uint32_t mMagic1
= sMagic
;
1182 // Try shows we never have more than 3 elements when IsExclusive is false.
1183 // So '3' is a good value to avoid heap allocation in most cases.
1184 AutoTArray
<RefPtr
<ThenValueBase
>, IsExclusive
? 1 : 3> mThenValues
;
1185 # ifdef PROMISE_DEBUG
1186 uint32_t mMagic2
= sMagic
;
1188 nsTArray
<RefPtr
<Private
>> mChainedPromises
;
1189 # ifdef PROMISE_DEBUG
1190 uint32_t mMagic3
= sMagic
;
1193 const bool mIsCompletionPromise
;
1194 # ifdef PROMISE_DEBUG
1199 template <typename ResolveValueT
, typename RejectValueT
, bool IsExclusive
>
1200 class MozPromise
<ResolveValueT
, RejectValueT
, IsExclusive
>::Private
1201 : public MozPromise
<ResolveValueT
, RejectValueT
, IsExclusive
> {
1203 explicit Private(const char* aCreationSite
, bool aIsCompletionPromise
= false)
1204 : MozPromise(aCreationSite
, aIsCompletionPromise
) {}
1206 template <typename ResolveValueT_
>
1207 void Resolve(ResolveValueT_
&& aResolveValue
, const char* aResolveSite
) {
1208 PROMISE_ASSERT(mMagic1
== sMagic
&& mMagic2
== sMagic
&&
1209 mMagic3
== sMagic
&& mMagic4
== &mMutex
);
1210 MutexAutoLock
lock(mMutex
);
1211 PROMISE_LOG("%s resolving MozPromise (%p created at %s)", aResolveSite
,
1212 this, mCreationSite
);
1215 "%s ignored already resolved or rejected MozPromise (%p created at "
1217 aResolveSite
, this, mCreationSite
);
1220 mValue
.SetResolve(std::forward
<ResolveValueT_
>(aResolveValue
));
1224 template <typename RejectValueT_
>
1225 void Reject(RejectValueT_
&& aRejectValue
, const char* aRejectSite
) {
1226 PROMISE_ASSERT(mMagic1
== sMagic
&& mMagic2
== sMagic
&&
1227 mMagic3
== sMagic
&& mMagic4
== &mMutex
);
1228 MutexAutoLock
lock(mMutex
);
1229 PROMISE_LOG("%s rejecting MozPromise (%p created at %s)", aRejectSite
, this,
1233 "%s ignored already resolved or rejected MozPromise (%p created at "
1235 aRejectSite
, this, mCreationSite
);
1238 mValue
.SetReject(std::forward
<RejectValueT_
>(aRejectValue
));
1242 template <typename ResolveOrRejectValue_
>
1243 void ResolveOrReject(ResolveOrRejectValue_
&& aValue
, const char* aSite
) {
1244 PROMISE_ASSERT(mMagic1
== sMagic
&& mMagic2
== sMagic
&&
1245 mMagic3
== sMagic
&& mMagic4
== &mMutex
);
1246 MutexAutoLock
lock(mMutex
);
1247 PROMISE_LOG("%s resolveOrRejecting MozPromise (%p created at %s)", aSite
,
1248 this, mCreationSite
);
1251 "%s ignored already resolved or rejected MozPromise (%p created at "
1253 aSite
, this, mCreationSite
);
1256 mValue
= std::forward
<ResolveOrRejectValue_
>(aValue
);
1260 // If the caller and target are both on the same thread, run the the resolve
1261 // or reject callback synchronously. Otherwise, the task will be dispatched
1262 // via the target Dispatch method.
1263 void UseSynchronousTaskDispatch(const char* aSite
) {
1266 "Synchronous dispatch can only be used with exclusive promises");
1267 PROMISE_ASSERT(mMagic1
== sMagic
&& mMagic2
== sMagic
&&
1268 mMagic3
== sMagic
&& mMagic4
== &mMutex
);
1269 MutexAutoLock
lock(mMutex
);
1270 PROMISE_LOG("%s UseSynchronousTaskDispatch MozPromise (%p created at %s)",
1271 aSite
, this, mCreationSite
);
1272 MOZ_ASSERT(IsPending(),
1273 "A Promise must not have been already resolved or rejected to "
1274 "set dispatch state");
1275 mUseSynchronousTaskDispatch
= true;
1278 // If the caller and target are both on the same thread, run the
1279 // resolve/reject callback off the direct task queue instead. This avoids a
1280 // full trip to the back of the event queue for each additional asynchronous
1281 // step when using MozPromise, and is similar (but not identical to) the
1282 // microtask semantics of JS promises.
1283 void UseDirectTaskDispatch(const char* aSite
) {
1284 PROMISE_ASSERT(mMagic1
== sMagic
&& mMagic2
== sMagic
&&
1285 mMagic3
== sMagic
&& mMagic4
== &mMutex
);
1286 MutexAutoLock
lock(mMutex
);
1287 PROMISE_LOG("%s UseDirectTaskDispatch MozPromise (%p created at %s)", aSite
,
1288 this, mCreationSite
);
1289 MOZ_ASSERT(IsPending(),
1290 "A Promise must not have been already resolved or rejected to "
1291 "set dispatch state");
1292 MOZ_ASSERT(!mUseSynchronousTaskDispatch
,
1293 "Promise already set for synchronous dispatch");
1294 mUseDirectTaskDispatch
= true;
1298 // A generic promise type that does the trick for simple use cases.
1299 typedef MozPromise
<bool, nsresult
, /* IsExclusive = */ true> GenericPromise
;
1301 // A generic, non-exclusive promise type that does the trick for simple use
1303 typedef MozPromise
<bool, nsresult
, /* IsExclusive = */ false>
1304 GenericNonExclusivePromise
;
1307 * Class to encapsulate a promise for a particular role. Use this as the member
1308 * variable for a class whose method returns a promise.
1310 template <typename PromiseType
, typename ImplType
>
1311 class MozPromiseHolderBase
{
1313 MozPromiseHolderBase() = default;
1315 MozPromiseHolderBase(MozPromiseHolderBase
&& aOther
) = default;
1316 MozPromiseHolderBase
& operator=(MozPromiseHolderBase
&& aOther
) = default;
1318 ~MozPromiseHolderBase() { MOZ_ASSERT(!mPromise
); }
1320 already_AddRefed
<PromiseType
> Ensure(const char* aMethodName
) {
1321 static_cast<ImplType
*>(this)->Check();
1323 mPromise
= new (typename
PromiseType::Private
)(aMethodName
);
1325 RefPtr
<PromiseType
> p
= mPromise
.get();
1329 bool IsEmpty() const {
1330 static_cast<const ImplType
*>(this)->Check();
1334 already_AddRefed
<typename
PromiseType::Private
> Steal() {
1335 static_cast<ImplType
*>(this)->Check();
1336 return mPromise
.forget();
1339 template <typename ResolveValueType_
>
1340 void Resolve(ResolveValueType_
&& aResolveValue
, const char* aMethodName
) {
1341 static_assert(std::is_convertible_v
<ResolveValueType_
,
1342 typename
PromiseType::ResolveValueType
>,
1343 "Resolve() argument must be implicitly convertible to "
1344 "MozPromise's ResolveValueT");
1346 static_cast<ImplType
*>(this)->Check();
1347 MOZ_ASSERT(mPromise
);
1348 mPromise
->Resolve(std::forward
<ResolveValueType_
>(aResolveValue
),
1353 template <typename ResolveValueType_
>
1354 void ResolveIfExists(ResolveValueType_
&& aResolveValue
,
1355 const char* aMethodName
) {
1357 Resolve(std::forward
<ResolveValueType_
>(aResolveValue
), aMethodName
);
1361 template <typename RejectValueType_
>
1362 void Reject(RejectValueType_
&& aRejectValue
, const char* aMethodName
) {
1363 static_assert(std::is_convertible_v
<RejectValueType_
,
1364 typename
PromiseType::RejectValueType
>,
1365 "Reject() argument must be implicitly convertible to "
1366 "MozPromise's RejectValueT");
1368 static_cast<ImplType
*>(this)->Check();
1369 MOZ_ASSERT(mPromise
);
1370 mPromise
->Reject(std::forward
<RejectValueType_
>(aRejectValue
), aMethodName
);
1374 template <typename RejectValueType_
>
1375 void RejectIfExists(RejectValueType_
&& aRejectValue
,
1376 const char* aMethodName
) {
1378 Reject(std::forward
<RejectValueType_
>(aRejectValue
), aMethodName
);
1382 template <typename ResolveOrRejectValueType_
>
1383 void ResolveOrReject(ResolveOrRejectValueType_
&& aValue
,
1384 const char* aMethodName
) {
1385 static_cast<ImplType
*>(this)->Check();
1386 MOZ_ASSERT(mPromise
);
1387 mPromise
->ResolveOrReject(std::forward
<ResolveOrRejectValueType_
>(aValue
),
1392 template <typename ResolveOrRejectValueType_
>
1393 void ResolveOrRejectIfExists(ResolveOrRejectValueType_
&& aValue
,
1394 const char* aMethodName
) {
1396 ResolveOrReject(std::forward
<ResolveOrRejectValueType_
>(aValue
),
1401 void UseSynchronousTaskDispatch(const char* aSite
) {
1402 MOZ_ASSERT(mPromise
);
1403 mPromise
->UseSynchronousTaskDispatch(aSite
);
1406 void UseDirectTaskDispatch(const char* aSite
) {
1407 MOZ_ASSERT(mPromise
);
1408 mPromise
->UseDirectTaskDispatch(aSite
);
1412 RefPtr
<typename
PromiseType::Private
> mPromise
;
1415 template <typename PromiseType
>
1416 class MozPromiseHolder
1417 : public MozPromiseHolderBase
<PromiseType
, MozPromiseHolder
<PromiseType
>> {
1419 using MozPromiseHolderBase
<
1420 PromiseType
, MozPromiseHolder
<PromiseType
>>::MozPromiseHolderBase
;
1421 static constexpr void Check(){};
1424 template <typename PromiseType
>
1425 class MozMonitoredPromiseHolder
1426 : public MozPromiseHolderBase
<PromiseType
,
1427 MozMonitoredPromiseHolder
<PromiseType
>> {
1429 // Provide a Monitor that should always be held when accessing this instance.
1430 explicit MozMonitoredPromiseHolder(Monitor
* const aMonitor
)
1431 : mMonitor(aMonitor
) {
1432 MOZ_ASSERT(aMonitor
);
1435 MozMonitoredPromiseHolder(MozMonitoredPromiseHolder
&& aOther
) = delete;
1436 MozMonitoredPromiseHolder
& operator=(MozMonitoredPromiseHolder
&& aOther
) =
1439 void Check() const { mMonitor
->AssertCurrentThreadOwns(); }
1442 Monitor
* const mMonitor
;
1446 * Class to encapsulate a MozPromise::Request reference. Use this as the member
1447 * variable for a class waiting on a MozPromise.
1449 template <typename PromiseType
>
1450 class MozPromiseRequestHolder
{
1452 MozPromiseRequestHolder() = default;
1453 ~MozPromiseRequestHolder() { MOZ_ASSERT(!mRequest
); }
1455 void Track(already_AddRefed
<typename
PromiseType::Request
> aRequest
) {
1456 MOZ_DIAGNOSTIC_ASSERT(!Exists());
1457 mRequest
= aRequest
;
1461 MOZ_DIAGNOSTIC_ASSERT(Exists());
1465 // Disconnects and forgets an outstanding promise. The resolve/reject methods
1466 // will never be called.
1468 MOZ_ASSERT(Exists());
1469 mRequest
->Disconnect();
1473 void DisconnectIfExists() {
1479 bool Exists() const { return !!mRequest
; }
1482 RefPtr
<typename
PromiseType::Request
> mRequest
;
1485 // Asynchronous Potentially-Cross-Thread Method Calls.
1487 // This machinery allows callers to schedule a promise-returning function
1488 // (a method and object, or a function object like a lambda) to be invoked
1489 // asynchronously on a given thread, while at the same time receiving a
1490 // promise upon which to invoke Then() immediately. InvokeAsync dispatches a
1491 // task to invoke the function on the proper thread and also chain the
1492 // resulting promise to the one that the caller received, so that resolve/
1493 // reject values are forwarded through.
1497 // Non-templated base class to allow us to use MOZ_COUNT_{C,D}TOR, which cause
1498 // assertions when used on templated types.
1499 class MethodCallBase
{
1501 MOZ_COUNTED_DEFAULT_CTOR(MethodCallBase
)
1502 MOZ_COUNTED_DTOR_VIRTUAL(MethodCallBase
)
1505 template <typename PromiseType
, typename MethodType
, typename ThisType
,
1506 typename
... Storages
>
1507 class MethodCall
: public MethodCallBase
{
1509 template <typename
... Args
>
1510 MethodCall(MethodType aMethod
, ThisType
* aThisVal
, Args
&&... aArgs
)
1513 mArgs(std::forward
<Args
>(aArgs
)...) {
1514 static_assert(sizeof...(Storages
) == sizeof...(Args
),
1515 "Storages and Args should have equal sizes");
1518 RefPtr
<PromiseType
> Invoke() { return mArgs
.apply(mThisVal
.get(), mMethod
); }
1522 RefPtr
<ThisType
> mThisVal
;
1523 RunnableMethodArguments
<Storages
...> mArgs
;
1526 template <typename PromiseType
, typename MethodType
, typename ThisType
,
1527 typename
... Storages
>
1528 class ProxyRunnable
: public CancelableRunnable
{
1531 typename
PromiseType::Private
* aProxyPromise
,
1532 MethodCall
<PromiseType
, MethodType
, ThisType
, Storages
...>* aMethodCall
)
1533 : CancelableRunnable("detail::ProxyRunnable"),
1534 mProxyPromise(aProxyPromise
),
1535 mMethodCall(aMethodCall
) {}
1537 NS_IMETHOD
Run() override
{
1538 RefPtr
<PromiseType
> p
= mMethodCall
->Invoke();
1539 mMethodCall
= nullptr;
1540 p
->ChainTo(mProxyPromise
.forget(), "<Proxy Promise>");
1544 nsresult
Cancel() override
{ return Run(); }
1547 RefPtr
<typename
PromiseType::Private
> mProxyPromise
;
1548 UniquePtr
<MethodCall
<PromiseType
, MethodType
, ThisType
, Storages
...>>
1552 template <typename
... Storages
, typename PromiseType
, typename ThisType
,
1553 typename
... ArgTypes
, typename
... ActualArgTypes
>
1554 static RefPtr
<PromiseType
> InvokeAsyncImpl(
1555 nsISerialEventTarget
* aTarget
, ThisType
* aThisVal
, const char* aCallerName
,
1556 RefPtr
<PromiseType
> (ThisType::*aMethod
)(ArgTypes
...),
1557 ActualArgTypes
&&... aArgs
) {
1558 MOZ_ASSERT(aTarget
);
1560 typedef RefPtr
<PromiseType
> (ThisType::*MethodType
)(ArgTypes
...);
1561 typedef detail::MethodCall
<PromiseType
, MethodType
, ThisType
, Storages
...>
1563 typedef detail::ProxyRunnable
<PromiseType
, MethodType
, ThisType
, Storages
...>
1566 MethodCallType
* methodCall
= new MethodCallType(
1567 aMethod
, aThisVal
, std::forward
<ActualArgTypes
>(aArgs
)...);
1568 RefPtr
<typename
PromiseType::Private
> p
=
1569 new (typename
PromiseType::Private
)(aCallerName
);
1570 RefPtr
<ProxyRunnableType
> r
= new ProxyRunnableType(p
, methodCall
);
1571 aTarget
->Dispatch(r
.forget());
1575 constexpr bool Any() { return false; }
1577 template <typename T1
>
1578 constexpr bool Any(T1 a
) {
1579 return static_cast<bool>(a
);
1582 template <typename T1
, typename
... Ts
>
1583 constexpr bool Any(T1 a
, Ts
... aOthers
) {
1584 return a
|| Any(aOthers
...);
1587 } // namespace detail
1589 // InvokeAsync with explicitly-specified storages.
1590 // See ParameterStorage in nsThreadUtils.h for help.
1591 template <typename
... Storages
, typename PromiseType
, typename ThisType
,
1592 typename
... ArgTypes
, typename
... ActualArgTypes
,
1593 std::enable_if_t
<sizeof...(Storages
) != 0, int> = 0>
1594 static RefPtr
<PromiseType
> InvokeAsync(
1595 nsISerialEventTarget
* aTarget
, ThisType
* aThisVal
, const char* aCallerName
,
1596 RefPtr
<PromiseType
> (ThisType::*aMethod
)(ArgTypes
...),
1597 ActualArgTypes
&&... aArgs
) {
1599 sizeof...(Storages
) == sizeof...(ArgTypes
),
1600 "Provided Storages and method's ArgTypes should have equal sizes");
1601 static_assert(sizeof...(Storages
) == sizeof...(ActualArgTypes
),
1602 "Provided Storages and ActualArgTypes should have equal sizes");
1603 return detail::InvokeAsyncImpl
<Storages
...>(
1604 aTarget
, aThisVal
, aCallerName
, aMethod
,
1605 std::forward
<ActualArgTypes
>(aArgs
)...);
1608 // InvokeAsync with no explicitly-specified storages, will copy arguments and
1609 // then move them out of the runnable into the target method parameters.
1610 template <typename
... Storages
, typename PromiseType
, typename ThisType
,
1611 typename
... ArgTypes
, typename
... ActualArgTypes
,
1612 std::enable_if_t
<sizeof...(Storages
) == 0, int> = 0>
1613 static RefPtr
<PromiseType
> InvokeAsync(
1614 nsISerialEventTarget
* aTarget
, ThisType
* aThisVal
, const char* aCallerName
,
1615 RefPtr
<PromiseType
> (ThisType::*aMethod
)(ArgTypes
...),
1616 ActualArgTypes
&&... aArgs
) {
1619 std::is_pointer_v
<std::remove_reference_t
<ActualArgTypes
>>...),
1620 "Cannot pass pointer types through InvokeAsync, Storages must be "
1622 static_assert(sizeof...(ArgTypes
) == sizeof...(ActualArgTypes
),
1623 "Method's ArgTypes and ActualArgTypes should have equal sizes");
1624 return detail::InvokeAsyncImpl
<
1625 StoreCopyPassByRRef
<std::decay_t
<ActualArgTypes
>>...>(
1626 aTarget
, aThisVal
, aCallerName
, aMethod
,
1627 std::forward
<ActualArgTypes
>(aArgs
)...);
1632 template <typename Function
, typename PromiseType
>
1633 class ProxyFunctionRunnable
: public CancelableRunnable
{
1634 using FunctionStorage
= std::decay_t
<Function
>;
1637 template <typename F
>
1638 ProxyFunctionRunnable(typename
PromiseType::Private
* aProxyPromise
,
1640 : CancelableRunnable("detail::ProxyFunctionRunnable"),
1641 mProxyPromise(aProxyPromise
),
1642 mFunction(new FunctionStorage(std::forward
<F
>(aFunction
))) {}
1644 NS_IMETHOD
Run() override
{
1645 RefPtr
<PromiseType
> p
= (*mFunction
)();
1646 mFunction
= nullptr;
1647 p
->ChainTo(mProxyPromise
.forget(), "<Proxy Promise>");
1651 nsresult
Cancel() override
{ return Run(); }
1654 RefPtr
<typename
PromiseType::Private
> mProxyPromise
;
1655 UniquePtr
<FunctionStorage
> mFunction
;
1658 // Note: The following struct and function are not for public consumption (yet?)
1659 // as we would prefer all calls to pass on-the-spot lambdas (or at least moved
1660 // function objects). They could be moved outside of detail if really needed.
1662 // We prefer getting function objects by non-lvalue-ref (to avoid copying them
1663 // and their captures). This struct is a tag that allows the use of objects
1664 // through lvalue-refs where necessary.
1665 struct AllowInvokeAsyncFunctionLVRef
{};
1667 // Invoke a function object (e.g., lambda or std/mozilla::function)
1668 // asynchronously; note that the object will be copied if provided by
1669 // lvalue-ref. Return a promise that the function should eventually resolve or
1671 template <typename Function
>
1672 static auto InvokeAsync(nsISerialEventTarget
* aTarget
, const char* aCallerName
,
1673 AllowInvokeAsyncFunctionLVRef
, Function
&& aFunction
)
1674 -> decltype(aFunction()) {
1676 IsRefcountedSmartPointer
<decltype(aFunction())>::value
&&
1678 typename RemoveSmartPointer
<decltype(aFunction())>::Type
>::value
,
1679 "Function object must return RefPtr<MozPromise>");
1680 MOZ_ASSERT(aTarget
);
1681 typedef typename RemoveSmartPointer
<decltype(aFunction())>::Type PromiseType
;
1682 typedef detail::ProxyFunctionRunnable
<Function
, PromiseType
>
1685 auto p
= MakeRefPtr
<typename
PromiseType::Private
>(aCallerName
);
1686 auto r
= MakeRefPtr
<ProxyRunnableType
>(p
, std::forward
<Function
>(aFunction
));
1687 aTarget
->Dispatch(r
.forget());
1691 } // namespace detail
1693 // Invoke a function object (e.g., lambda) asynchronously.
1694 // Return a promise that the function should eventually resolve or reject.
1695 template <typename Function
>
1696 static auto InvokeAsync(nsISerialEventTarget
* aTarget
, const char* aCallerName
,
1697 Function
&& aFunction
) -> decltype(aFunction()) {
1698 static_assert(!std::is_lvalue_reference_v
<Function
>,
1699 "Function object must not be passed by lvalue-ref (to avoid "
1700 "unplanned copies); Consider move()ing the object.");
1701 return detail::InvokeAsync(aTarget
, aCallerName
,
1702 detail::AllowInvokeAsyncFunctionLVRef(),
1703 std::forward
<Function
>(aFunction
));
1707 # undef PROMISE_ASSERT
1708 # undef PROMISE_DEBUG
1710 } // namespace mozilla