Bug 1321250 - Add MozPromise::ThenPromise() for easier promise chaining. r=jya
authorJW Wang <jwwang@mozilla.com>
Wed, 30 Nov 2016 18:23:12 +0800
changeset 324902 0fc28aab9031aa803f8adbe69c059063195cd6c3
parent 324901 fcabe39c665ff1e8e5a83563748449ca1acc214f
child 324903 7d0ff0aae0a0b99a26ae1236817dabba93f53e49
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersjya
bugs1321250
milestone53.0a1
Bug 1321250 - Add MozPromise::ThenPromise() for easier promise chaining. r=jya MozReview-Commit-ID: 7J60CN0HbOW
xpcom/threads/MozPromise.h
--- a/xpcom/threads/MozPromise.h
+++ b/xpcom/threads/MozPromise.h
@@ -327,24 +327,35 @@ protected:
         return NS_OK;
       }
 
     private:
       RefPtr<ThenValueBase> mThenValue;
       RefPtr<MozPromise> mPromise;
     };
 
-    explicit ThenValueBase(AbstractThread* aResponseTarget, const char* aCallSite)
-      : mResponseTarget(aResponseTarget), mCallSite(aCallSite) {}
+    ThenValueBase(AbstractThread* aResponseTarget,
+                  const char* aCallSite,
+                  bool aInitCompletionPromise = false)
+      : mResponseTarget(aResponseTarget)
+      , mCallSite(aCallSite)
+      , mInitCompletionPromise(aInitCompletionPromise)
+    {
+      if (mInitCompletionPromise) {
+        mCompletionPromise = new MozPromise::Private(
+          "<completion promise>", true /* aIsCompletionPromise */);
+      }
+    }
 
     MozPromise* CompletionPromise() override
     {
-      MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsCurrentThreadIn());
+      MOZ_DIAGNOSTIC_ASSERT(mInitCompletionPromise ||
+                            mResponseTarget->IsCurrentThreadIn());
       MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete);
-      if (!mCompletionPromise) {
+      if (!mInitCompletionPromise && !mCompletionPromise) {
         mCompletionPromise = new MozPromise::Private(
           "<completion promise>", true /* aIsCompletionPromise */);
       }
       return mCompletionPromise;
     }
 
     void AssertIsDead() override
     {
@@ -428,16 +439,20 @@ protected:
 
     // Declaring RefPtr<MozPromise::Private> here causes build failures
     // on MSVC because MozPromise::Private is only forward-declared at this
     // point. This hack can go away when we inline-declare MozPromise::Private,
     // which is blocked on the B2G ICS compiler being too old.
     RefPtr<MozPromise> mCompletionPromise;
 
     const char* mCallSite;
+
+    // True if mCompletionPromise should be initialized in the constructor
+    // to make CompletionPromise() thread-safe.
+    const bool mInitCompletionPromise;
   };
 
   /*
    * We create two overloads for invoking Resolve/Reject Methods so as to
    * make the resolve/reject value argument "optional".
    */
 
   template<typename ThisType, typename MethodType, typename ValueType>
@@ -479,18 +494,18 @@ protected:
   }
 
   template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
   class MethodThenValue : public ThenValueBase
   {
   public:
     MethodThenValue(AbstractThread* aResponseTarget, ThisType* aThisVal,
                     ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod,
-                    const char* aCallSite)
-      : ThenValueBase(aResponseTarget, aCallSite)
+                    const char* aCallSite, bool aInitCompletionPromise = false)
+      : ThenValueBase(aResponseTarget, aCallSite, aInitCompletionPromise)
       , mThisVal(aThisVal)
       , mResolveMethod(aResolveMethod)
       , mRejectMethod(aRejectMethod) {}
 
   virtual void Disconnect() override
   {
     ThenValueBase::Disconnect();
 
@@ -528,18 +543,19 @@ protected:
   // NB: We could use std::function here instead of a template if it were supported. :-(
   template<typename ResolveFunction, typename RejectFunction>
   class FunctionThenValue : public ThenValueBase
   {
   public:
     FunctionThenValue(AbstractThread* aResponseTarget,
                       ResolveFunction&& aResolveFunction,
                       RejectFunction&& aRejectFunction,
-                      const char* aCallSite)
-      : ThenValueBase(aResponseTarget, aCallSite)
+                      const char* aCallSite,
+                      bool aInitCompletionPromise = false)
+      : ThenValueBase(aResponseTarget, aCallSite, aInitCompletionPromise)
     {
       mResolveFunction.emplace(Move(aResolveFunction));
       mRejectFunction.emplace(Move(aRejectFunction));
     }
 
   virtual void Disconnect() override
   {
     ThenValueBase::Disconnect();
@@ -598,34 +614,69 @@ public:
       mThenValues.AppendElement(aThenValue);
     }
   }
 
 public:
 
   template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
   RefPtr<Request> Then(AbstractThread* aResponseThread, const char* aCallSite, ThisType* aThisVal,
-                         ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod)
+                       ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod)
   {
     RefPtr<ThenValueBase> thenValue = new MethodThenValue<ThisType, ResolveMethodType, RejectMethodType>(
                                               aResponseThread, aThisVal, aResolveMethod, aRejectMethod, aCallSite);
     ThenInternal(aResponseThread, thenValue, aCallSite);
     return thenValue.forget(); // Implicit conversion from already_AddRefed<ThenValueBase> to RefPtr<Request>.
   }
 
   template<typename ResolveFunction, typename RejectFunction>
   RefPtr<Request> Then(AbstractThread* aResponseThread, const char* aCallSite,
-                         ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction)
+                       ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction)
   {
     RefPtr<ThenValueBase> thenValue = new FunctionThenValue<ResolveFunction, RejectFunction>(aResponseThread,
                                               Move(aResolveFunction), Move(aRejectFunction), aCallSite);
     ThenInternal(aResponseThread, thenValue, aCallSite);
     return thenValue.forget(); // Implicit conversion from already_AddRefed<ThenValueBase> to RefPtr<Request>.
   }
 
+  // Equivalent to Then(target, ...)->CompletionPromise()
+  // without the restriction that CompletionPromise() must be called on the
+  // |target| thread. So ThenPromise() can be called on any thread as Then().
+  // The syntax is close to JS promise and makes promise chaining easier
+  // where you can do: p->ThenPromise()->ThenPromise()->ThenPromise();
+  //
+  // Note you would have to call Then() instead when the result needs to be held
+  // by a MozPromiseRequestHolder for future disconnection.
+  //
+  // TODO: replace Then()->CompletionPromise() with ThenPromise() and
+  // stop exposing CompletionPromise() to the client code.
+  template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
+  MOZ_MUST_USE RefPtr<MozPromise>
+  ThenPromise(AbstractThread* aResponseThread, const char* aCallSite, ThisType* aThisVal,
+              ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod)
+  {
+    using ThenType = MethodThenValue<ThisType, ResolveMethodType, RejectMethodType>;
+    RefPtr<ThenValueBase> thenValue = new ThenType(aResponseThread, aThisVal, aResolveMethod,
+      aRejectMethod, aCallSite, true /* aInitCompletionPromise */);
+    ThenInternal(aResponseThread, thenValue, aCallSite);
+    return thenValue->CompletionPromise();
+  }
+
+  template<typename ResolveFunction, typename RejectFunction>
+  MOZ_MUST_USE RefPtr<MozPromise>
+  ThenPromise(AbstractThread* aResponseThread, const char* aCallSite,
+              ResolveFunction&& aResolveFunction, RejectFunction&& aRejectFunction)
+  {
+    using ThenType = FunctionThenValue<ResolveFunction, RejectFunction>;
+    RefPtr<ThenValueBase> thenValue = new ThenType(aResponseThread, Move(aResolveFunction),
+      Move(aRejectFunction), aCallSite, true /* aInitCompletionPromise */);
+    ThenInternal(aResponseThread, thenValue, aCallSite);
+    return thenValue->CompletionPromise();
+  }
+
   void ChainTo(already_AddRefed<Private> aChainedPromise, const char* aCallSite)
   {
     MutexAutoLock lock(mMutex);
     MOZ_DIAGNOSTIC_ASSERT(!IsExclusive || !mHaveRequest);
     mHaveRequest = true;
     RefPtr<Private> chainedPromise = aChainedPromise;
     PROMISE_LOG("%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]",
                 aCallSite, this, chainedPromise.get(), (int) IsPending());