| /* |
| * Copyright (C) 2023-2025 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| // NativePromise is a C++ Promise implementation based on Mozilla's MozPromise. |
| |
| #pragma once |
| |
| #if ASSERT_ENABLED |
| #include <atomic> |
| #endif |
| #include <functional> |
| #include <type_traits> |
| #include <utility> |
| #include <wtf/Assertions.h> |
| #include <wtf/CrossThreadCopier.h> |
| #include <wtf/Expected.h> |
| #include <wtf/FastMalloc.h> |
| #include <wtf/Forward.h> |
| #include <wtf/Function.h> |
| #include <wtf/FunctionDispatcher.h> |
| #include <wtf/Lock.h> |
| #include <wtf/Locker.h> |
| #include <wtf/Logger.h> |
| #include <wtf/Ref.h> |
| #include <wtf/RefCountedAndCanMakeWeakPtr.h> |
| #include <wtf/RefPtr.h> |
| #include <wtf/ThreadSafeRefCounted.h> |
| #include <wtf/TypeTraits.h> |
| #include <wtf/Vector.h> |
| #include <wtf/WeakPtr.h> |
| #include <wtf/text/MakeString.h> |
| |
| namespace WTF { |
| |
| /* |
| * A promise manages an asynchronous request that may or may not be able to be fulfilled immediately. |
| * When an API returns a promise, the consumer may attach callbacks to be invoked (asynchronously, on a specified thread) |
| * when the request is either completed (resolved) or cannot be completed (rejected). |
| * |
| * A NativePromise object is thread safe, and may be ->then()/whenSettle()ed on any threads. |
| * The then() call accepts either a resolve and reject callback, while whenSettled() accepts a resolveOrReject one. |
| * |
| * NativePromise::then() and NativePromise::whenSettled() returns a NativePromise::ThenCommand object. This object can be either: |
| * 1- Converted back to a NativePromise which will be resolved or rejected once the resolve/reject callbacks are run. |
| * This new NativePromise can be then()ed again to chain multiple operations. |
| * 2- Be tracked using a NativePromiseRequest: this allows the caller to cancel the delivery of the resolve/reject result if it has not already occurred. |
| * (call to NativePromiseRequest::disconnect() must be done on the target thread to avoid thread safety issues). |
| * |
| * When IsExclusive is true: |
| * - The NativePromise performs run-time assertions that there is at most one call to either then(...) or chainTo(...). |
| * - Move semantics are used when passing arguments |
| * - The resolved or rejected object will be deleted on the target thread. |
| * - The ResolveValueType and RejectValueType must be moveable (e.g have a move constructor). Compilation will fail otherwise. |
| * Otherwise: |
| * - values are passed to the resolve/reject callbacks through either const references or pointers. |
| * - the resolve or reject object will be deleted on the last SerialFunctionDispatcher that got used. |
| * |
| * By default, a NativePromise will use crossThreadCopy() on the resolved or rejected object if it contains a type with the `isolatedCopy()` method |
| * or an AtomString (be it directly, or in a composited object (e.g. Vector<AtomString>). |
| * This behaviour can be overridden with either PromiseOption::WithCrossThreadCopy or PromiseOption::WithoutCrossThreadCopy |
| * |
| * A typical workflow would be as follow: |
| * If the work is to be done immediately: |
| * From the producer side: |
| * - Do the work |
| * - return a resolved or rejected promise via NativePromise::createAndResolve or NativePromise::createAndReject |
| * From the consumer side: |
| * - call the method returning a promise |
| * - then()/whenSettled() on the promise to set the actions to run once the promise has settled. |
| * |
| * If the work is to be done at a later stage: |
| * From the producer side: |
| * - Allocate a NativePromise::Producer and return it to the consumer has a Ref<NativePromise> |
| * - Do the work |
| * - Once the work has been completed, either resolve or reject the NativePromise::Producer object. |
| * From the consumer side: |
| * - call the method returning a promise |
| * - then() on the promise to set the actions to run once the promise has settled. |
| * |
| * In either case (immediate or later resolution) using a NativePromiseRequest: |
| * - track the promise |
| * - cancel the delivery of the resolve/reject result and prevent callbacks to be run. |
| * |
| * By disconnecting the NativePromiseRequest (via NativePromiseRequest::disconnect(), the then()/whenSettled() callbacks will not be run. |
| * |
| * The object given to resolve, reject or settle must have a CrossThreadCopier specialisation as needed. |
| * The type of this object may not be identical to ResolveValueType or RejectValueType as the methods allow for implicit conversion. |
| * |
| * Examples: |
| * 1. Basic usage. methodA runs on the main thread, methodB must run on a WorkQueue, and expects a std::unique<int>. |
| * methodA calls methodB for asynchronous work and will perform some work once methodB is done. |
| * |
| * static Ref<GenericPromise> methodB(std_unique<int>&& arg) |
| * { |
| * assertIsCurrent(workQueue); |
| * // Do something with arg and once done return a resolved promise. |
| * if (all_ok) |
| * return GenericPromise::createAndResolve(); |
| * else |
| * return GenericPromise::createAndReject(-1); |
| * } |
| * |
| * static void methodA() |
| * { |
| * assertIsMainThread(); |
| * auto arg = std::make_unique<int>(20); |
| * // invokeAsync returns a promise of same type as what the function returns, |
| * // and it will be resolved or rejected when the original promise is settled. |
| * invokeAsync(workQueue, [arg = WTF::move(arg)] () mutable { return methodB(WTF::move(arg); }) |
| * ->then(RunLoop::mainSingleton(), |
| * []() { |
| * assertIsMainThread(); |
| * // Method succeeded |
| * }, [](int) { |
| * assertIsMainThread(); |
| * // Method failed |
| * }); |
| * } |
| * |
| * 2. Using lambdas |
| * auto p = MyAsyncMethod(); // MyAsyncMethod returns a Ref<NativePromise>, and perform some work on some thread. |
| * p->then(RunLoop::mainSingleton(), [] (NativePromise::Result&& result) { |
| * assertIsMainThread(); |
| * if (result) { |
| * auto resolveValue = WTF::move(result.value()); |
| * } else { |
| * auto rejectValue = WTF::move(result.error()); |
| * } |
| * } |
| * |
| * 3. Using a NativePromiseRequest |
| * NativePromiseRequest request; |
| * |
| * GenericPromise::Producer p; |
| * // Note that if you're not interested in the result you can provide a Function<void()> |
| * p->then(RunLoop::mainSingleton(), |
| * [] { CRASH("resolve callback won't be run"); }, |
| * [] { CRASH("reject callback won't be run"); }) |
| * ->track(request); |
| * |
| * // We resolve the promise. |
| * p.resolve(); |
| * |
| * // We are no longer interested by the result of the promise. We disconnect the request holder. |
| * request.disconnect(); |
| * |
| * 4. Chaining promises of different types |
| * auto p = MyAsyncMethod(); // MyAsyncMethod returns a Ref<MyNativePromise>, and perform some work on some thread. |
| * auto p2 = p->then(RunLoop::mainSingleton(), [] (MyNativePromise::ResolveValueType val) { |
| * assertIsMainThread(); |
| * if (val) |
| * return MyOtherPromise::createAndResolve(val); |
| * return MyOtherPromise::createAndReject(val); |
| * }, [] (MyOtherPromise::RejectValueType val) { |
| * return MyOtherPromise::createAndReject(val); |
| * }) // The type returned by then() is of the last PromiseType returned in the chain. |
| * ->whenSettled(RunLoop::mainSingleton(), [] (const MyOtherPromise::Result&) -> void { |
| * // do something else |
| * }); |
| * |
| * Another Example: |
| * Consider a PhotoProducer class that can take a photo and returns an image and its mimetype. |
| * The PhotoProducer uses some system framework that takes a completion handler which will receive the photo once taken. |
| * The PhotoProducer uses its own WorkQueue to perform the work so that it won't block the thread it's called on. |
| * We want the PhotoProducer to be able to be called on any threads. |
| * |
| * // This would be the system framework. |
| * struct AVCaptureMethod { |
| * // Note that we must use Function as std::function requires the lambda to be copyable. |
| * static void captureImage(Function<void(std::vector<uint8_t>&&, std::string&&)>&& handler) |
| * { |
| * handler({ 1, 2, 3, 4, 5 }, "image/jpeg"); |
| * } |
| * }; |
| * |
| * struct PhotoSettings { }; |
| * |
| * class PhotoProducer : public ThreadSafeRefCounted<PhotoProducer> { |
| * public: |
| * using PhotoPromise = NativePromise<std::pair<Vector<uint8_t>, String>, int>; |
| * static Ref<PhotoProducer> create(const PhotoSettings& settings) { return adoptRef(*new PhotoProducer(settings)); } |
| * |
| * Ref<PhotoPromise> takePhoto() const |
| * { |
| * // This can be called on any threads. |
| * // It uses invokeAsync which returns a NativePromise that will be settled when the promise returned by the method will itself be settled. |
| * // (the invokeAsync promise is "chained" to the promise returned by `takePhotoImpl()`) |
| * return invokeAsync(m_generatePhotoQueue, [protectedThis = Ref { *this }] { |
| * assertIsCurrent(protectedThis->m_generatePhotoQueue); |
| * return protectedThis->takePhotoImpl(); |
| * }); |
| * } |
| * private: |
| * explicit PhotoProducer(const PhotoSettings& settings) |
| * : m_generatePhotoQueue(WorkQueue::create("takePhoto queue"_s)) |
| * { |
| * } |
| * |
| * Ref<PhotoPromise> takePhotoImpl() const |
| * { |
| * PhotoPromise::Producer producer; |
| * Ref<PhotoPromise> promise = producer; |
| * |
| * AVCaptureMethod::captureImage([producer = WTF::move(producer)] (std::vector<uint8_t>&& image, std::string&& mimeType) { |
| * // Note that you can resolve a NativePromise on any threads. Unlike with a CompletionHandler it is not the responsibility of the producer |
| * // to resolve the promise on a particular thread. |
| * // The consumer specifies the thread on which it wants to be called back. |
| * producer.resolve(std::make_pair<Vector<uint8_t>, String>({ image.data(), image.size() }, { mimeType.data(), static_cast<unsigned>(mimeType.size()) })); |
| * }); |
| * |
| * // Return the promise which the producer will resolve at a later stage. |
| * return promise; |
| * } |
| * Ref<WorkQueue> m_generatePhotoQueue; |
| * }; |
| * |
| * And usage would be: |
| * auto photoProducer = PhotoProducer::create(PhotoSettings { }); |
| * photoProducer->takePhoto()->whenSettled(RunLoop::mainSingleton(), [] (PhotoProducer::PhotoPromise::Result&& result) mutable { |
| * static_assert(std::is_same_v<decltype(result.value()), std::pair<Vector<uint8_t>, String>&>); |
| * if (result) |
| * EXPECT_EQ(result.value().second, "image/jpeg"_s); |
| * else |
| * EXPECT_TRUE(false); // Got an unexpected error. |
| * }); |
| * |
| * For additional examples on how to use NativePromise, refer to NativePromise.cpp API tests. |
| */ |
| |
| class NativePromiseBase : public ThreadSafeRefCounted<NativePromiseBase> { |
| public: |
| virtual void assertIsDead() = 0; |
| virtual ~NativePromiseBase() = default; |
| #if !LOG_DISABLED || !RELEASE_LOG_DISABLED |
| WTF_EXPORT_PRIVATE static WTFLogChannel& NODELETE logChannel(); |
| #endif |
| template<typename... Args> |
| static inline void log(UNUSED_VARIADIC_PARAMS const Args&... arguments) |
| { |
| #if !LOG_DISABLED || !RELEASE_LOG_DISABLED |
| auto& channel = logChannel(); |
| if (channel.state == WTFLogChannelState::Off || WTFLogLevel::Debug > channel.level) |
| return; |
| |
| Logger::log(channel, WTFLogLevel::Debug, arguments...); |
| #endif |
| } |
| }; |
| |
| #define PROMISE_LOG(...) NativePromiseBase::log(__VA_ARGS__) |
| |
| // Ideally we would use C++20 source_location, but it's currently broken in XCode see rdar://116228776 |
| #define DEFAULT_LOGSITEIDENTIFIER Logger::LogSiteIdentifier(__builtin_FUNCTION(), 0) |
| |
| class ConvertibleToNativePromise { }; |
| |
| class NativePromiseRequest final : public RefCountedAndCanMakeWeakPtr<NativePromiseRequest> { |
| WTF_DEPRECATED_MAKE_FAST_ALLOCATED(NativePromiseRequest); |
| public: |
| static Ref<NativePromiseRequest> create() |
| { |
| return adoptRef(*new NativePromiseRequest); |
| } |
| |
| ~NativePromiseRequest() |
| { |
| ASSERT(!m_callback, "complete() or disconnect() wasn't called"); |
| } |
| |
| class Callback : public ThreadSafeRefCounted<Callback> { |
| public: |
| virtual ~Callback() = default; |
| virtual void disconnect() = 0; |
| }; |
| |
| |
| void track(Ref<Callback> callback) |
| { |
| ASSERT(!m_callback); |
| m_callback = WTF::move(callback); |
| } |
| |
| bool hasCallback() const { return !!m_callback; } |
| |
| void complete() |
| { |
| ASSERT(m_callback); |
| m_callback = nullptr; |
| } |
| |
| // Disconnect and forget an outstanding promise. The resolve/reject methods will never be called. |
| void disconnect() |
| { |
| ASSERT(m_callback); |
| if (!m_callback) |
| return; |
| RefPtr callback = std::exchange(m_callback, nullptr); |
| callback->disconnect(); |
| } |
| |
| private: |
| NativePromiseRequest() = default; |
| |
| RefPtr<Callback> m_callback; |
| }; |
| |
| template<typename ResolveValueT, typename RejectValueT, unsigned options = 0> |
| class NativePromiseProducer; |
| |
| enum class PromiseDispatchMode : uint8_t { |
| Default, // ResolveRejectCallbacks will be dispatched on the target thread. |
| RunSynchronouslyOnTarget, // ResolveRejectCallbacks will be run synchronously if target thread is current. |
| }; |
| |
| enum class PromiseOption : uint8_t { |
| Default = 0, // Exclusive | WithAutomaticCrossThreadCopy |
| NonExclusive = (1 << 0), |
| WithCrossThreadCopy = (1 << 2), |
| WithoutCrossThreadCopy = (1 << 3), |
| AutoRejectProducer = (1 << 4), |
| }; |
| constexpr unsigned operator|(PromiseOption a, PromiseOption b) |
| { |
| return static_cast<unsigned>(a) | static_cast<unsigned>(b); |
| } |
| constexpr unsigned operator|(unsigned a, PromiseOption b) |
| { |
| return a | static_cast<unsigned>(b); |
| } |
| constexpr unsigned operator&(PromiseOption a, PromiseOption b) |
| { |
| return static_cast<unsigned>(a) & static_cast<unsigned>(b); |
| } |
| constexpr unsigned operator&(unsigned a, PromiseOption b) |
| { |
| return a & static_cast<unsigned>(b); |
| } |
| |
| namespace detail { |
| struct VoidPlaceholder { |
| }; |
| } // namespace detail |
| |
| template<typename ResolveValueT, typename RejectValueT, unsigned options> |
| class NativePromise final : public NativePromiseBase, public ConvertibleToNativePromise { |
| public: |
| |
| static constexpr bool IsExclusive = !(options & PromiseOption::NonExclusive); |
| static constexpr bool WithCrossThreadCopy = !!(options & PromiseOption::WithCrossThreadCopy); |
| static constexpr bool WithAutomaticCrossThreadCopy = !(options & (PromiseOption::WithCrossThreadCopy | PromiseOption::WithoutCrossThreadCopy)) && (CrossThreadCopier<ResolveValueT>::IsNeeded || CrossThreadCopier<RejectValueT>::IsNeeded); |
| static_assert(!WithAutomaticCrossThreadCopy || IsExclusive, "Using Non-Exclusive NativePromise with a ResolveValueT or RejectValueT requiring a call to isolatedCopy() must be explicitly set with WithCrossThreadCopy or WithoutCrossThreadCopy option"); |
| using ResolveValueType = std::conditional_t<WithAutomaticCrossThreadCopy || WithCrossThreadCopy, typename CrossThreadCopier<ResolveValueT>::Type, ResolveValueT>; |
| using RejectValueType = std::conditional_t<std::is_void_v<RejectValueT>, detail::VoidPlaceholder, std::conditional_t<WithAutomaticCrossThreadCopy || WithCrossThreadCopy, typename CrossThreadCopier<RejectValueT>::Type, RejectValueT>>; |
| using Result = Expected<ResolveValueType, RejectValueType>; |
| using Error = std::unexpected<RejectValueType>; |
| using ResultRunnable = Function<Result(void)>; |
| |
| // used by IsConvertibleToNativePromise to determine how to cast the result. |
| using PromiseType = NativePromise; |
| |
| // We split the functionalities from a "Producer" that can create and resolve/reject a promise and a "Consumer" |
| // that will then()/whenSettled() on such promise. |
| using Producer = NativePromiseProducer<ResolveValueT, RejectValueT, options>; |
| using AutoRejectProducer = NativePromiseProducer<ResolveValueT, RejectValueT, options | PromiseOption::AutoRejectProducer>; |
| |
| virtual ~NativePromise() |
| { |
| PROMISE_LOG("destroying ", *this); |
| assertIsDead(); |
| #if ASSERT_ENABLED |
| Locker lock { m_lock }; |
| ASSERT(!isNothing()); |
| ASSERT(m_thenCallbacks.isEmpty()); |
| ASSERT(m_chainedPromises.isEmpty()); |
| #endif |
| } |
| |
| const Logger::LogSiteIdentifier& logSiteIdentifier() const LIFETIME_BOUND { return m_logSiteIdentifier; } |
| |
| private: |
| // Return a |T&&| to enable move when IsExclusive is true or a |const T&| to enforce copy otherwise. |
| template<typename T, typename R = std::conditional_t<IsExclusive, T&&, const T&>> |
| static R maybeMove(T& aX) |
| { |
| return static_cast<R>(aX); |
| } |
| |
| public: |
| template<typename ResolveValueType_, typename = std::enable_if<!std::is_void_v<ResolveValueT>>> |
| static Ref<NativePromise> createAndResolve(ResolveValueType_&& resolveValue, const Logger::LogSiteIdentifier& resolveSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| auto p = adoptRef(*new NativePromise(resolveSite)); |
| p->resolve(std::forward<ResolveValueType_>(resolveValue), resolveSite); |
| return p; |
| } |
| |
| template<typename = std::enable_if<std::is_void_v<ResolveValueT>>> |
| static Ref<NativePromise> createAndResolve(const Logger::LogSiteIdentifier& resolveSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| auto p = adoptRef(*new NativePromise(resolveSite)); |
| p->resolve(resolveSite); |
| return p; |
| } |
| |
| template<typename RejectValueType_, typename = std::enable_if<!std::is_void_v<RejectValueT>>> |
| static Ref<NativePromise> createAndReject(RejectValueType_&& rejectValue, const Logger::LogSiteIdentifier& rejectSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| auto p = adoptRef(*new NativePromise(rejectSite)); |
| p->reject(std::forward<RejectValueType_>(rejectValue), rejectSite); |
| return p; |
| } |
| |
| template<typename = std::enable_if<std::is_void_v<RejectValueT>>> |
| static Ref<NativePromise> createAndReject(const Logger::LogSiteIdentifier& rejectSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| auto p = adoptRef(*new NativePromise(rejectSite)); |
| p->reject(rejectSite); |
| return p; |
| } |
| |
| template<typename SettleValueType> |
| static Ref<NativePromise> createAndSettle(SettleValueType&& result, const Logger::LogSiteIdentifier& site = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| auto p = adoptRef(*new NativePromise(site)); |
| p->settle(std::forward<SettleValueType>(result), site); |
| return p; |
| } |
| |
| using AllPromiseType = NativePromise<std::conditional_t<std::is_void_v<ResolveValueType>, void, Vector<ResolveValueType>>, RejectValueType, options>; |
| using AllSettledPromiseType = NativePromise<Vector<Result>, bool, options>; |
| |
| private: |
| template<typename ResolveValueT2, typename RejectValueT2, unsigned options2> |
| friend class NativePromiseProducer; |
| |
| template<typename SettleValueType> |
| inline void settleImpl(SettleValueType&& result, Locker<Lock>& lock) |
| { |
| assertIsHeld(m_lock); |
| ASSERT(isNothing()); |
| m_result.emplace(std::forward<SettleValueType>(result)); |
| dispatchAll(lock); |
| } |
| |
| template<typename ResolveValueType_, typename = std::enable_if<!std::is_void_v<ResolveValueT>>> |
| void resolve(ResolveValueType_&& resolveValue, const Logger::LogSiteIdentifier& resolveSite) |
| { |
| static_assert(std::is_convertible_v<ResolveValueType_, ResolveValueT>, "resolve() argument must be implicitly convertible to NativePromise's ResolveValueT"); |
| Locker lock { m_lock }; |
| PROMISE_LOG(resolveSite, " resolving ", *this); |
| if constexpr (WithCrossThreadCopy || WithAutomaticCrossThreadCopy) |
| settleImpl(crossThreadCopy(std::forward<ResolveValueType_>(resolveValue)), lock); |
| else |
| settleImpl(std::forward<ResolveValueType_>(resolveValue), lock); |
| } |
| |
| template<typename = std::enable_if<std::is_void_v<ResolveValueT>>> |
| void resolve(const Logger::LogSiteIdentifier& resolveSite) |
| { |
| Locker lock { m_lock }; |
| PROMISE_LOG(resolveSite, " resolving ", *this); |
| settleImpl(Result { }, lock); |
| } |
| |
| template<typename RejectValueType_, typename = std::enable_if<!std::is_void_v<RejectValueT>>> |
| void reject(RejectValueType_&& rejectValue, const Logger::LogSiteIdentifier& rejectSite) |
| { |
| static_assert(std::is_convertible_v<RejectValueType_, RejectValueT>, "reject() argument must be implicitly convertible to NativePromise's RejectValueT"); |
| Locker lock { m_lock }; |
| PROMISE_LOG(rejectSite, " rejecting ", *this); |
| if constexpr (WithCrossThreadCopy || WithAutomaticCrossThreadCopy) |
| settleImpl(std::unexpected<RejectValueT>(crossThreadCopy(std::forward<RejectValueType_>(rejectValue))), lock); |
| else |
| settleImpl(std::unexpected<RejectValueT>(std::forward<RejectValueType_>(rejectValue)), lock); |
| } |
| |
| template<typename = std::enable_if<std::is_void_v<RejectValueT>>> |
| void reject(const Logger::LogSiteIdentifier& rejectSite) |
| { |
| Locker lock { m_lock }; |
| PROMISE_LOG(rejectSite, " rejecting ", *this); |
| settleImpl(makeUnexpected(detail::VoidPlaceholder()), lock); |
| } |
| |
| template<typename SettleValueType> |
| void settle(SettleValueType&& result, const Logger::LogSiteIdentifier& site) |
| { |
| static_assert(std::is_convertible_v<SettleValueType, Result>, "settle() argument must be implicitly convertible to NativePromise's Result"); |
| Locker lock { m_lock }; |
| PROMISE_LOG(site, " settling ", *this); |
| if constexpr (WithCrossThreadCopy || WithAutomaticCrossThreadCopy) |
| settleImpl(crossThreadCopy(std::forward<SettleValueType>(result)), lock); |
| else |
| settleImpl(std::forward<SettleValueType>(result), lock); |
| } |
| |
| void settleWithFunction(ResultRunnable&& result, const Logger::LogSiteIdentifier& site) |
| { |
| Locker lock { m_lock }; |
| PROMISE_LOG(site, " settling ", *this); |
| settleImpl(std::forward<ResultRunnable>(result), lock); |
| } |
| |
| template<typename StorageType> |
| void settleAsChainedPromise(StorageType&& storage, const Logger::LogSiteIdentifier& site) |
| { |
| Locker lock { m_lock }; |
| ASSERT(isNothing()); |
| PROMISE_LOG(site, " settling chained promise ", *this); |
| m_result = std::forward<StorageType>(storage); |
| dispatchAll(lock); |
| } |
| |
| void setDispatchMode(PromiseDispatchMode dispatchMode, const Logger::LogSiteIdentifier& site) |
| { |
| static_assert(IsExclusive, "setDispatchMode can only be used with exclusive promises"); |
| Locker lock { m_lock }; |
| PROMISE_LOG(site, " runSynchronouslyOnTarget ", *this); |
| ASSERT(isNothing(), "A Promise must not have been already resolved or rejected to set dispatch state"); |
| m_dispatchMode = dispatchMode; |
| } |
| |
| // We can't move the Result object with non-exclusive promise. |
| using ResultParam = std::conditional_t<IsExclusive, Result&&, const Result&>; |
| |
| class AllPromiseProducer : public ThreadSafeRefCounted<AllPromiseProducer> { |
| public: |
| explicit AllPromiseProducer(size_t dependentPromisesCount) |
| : m_producer(makeUnique<typename AllPromiseType::Producer>()) |
| , m_outstandingPromises(dependentPromisesCount) |
| { |
| ASSERT(dependentPromisesCount); |
| if constexpr (!std::is_void_v<ResolveValueT>) |
| m_resolveValues.grow(dependentPromisesCount); |
| } |
| |
| template<typename ResolveValueType_> |
| void resolve(size_t index, ResolveValueType_&& resolveValue) |
| { |
| Locker lock { m_lock }; |
| if (!m_producer) { |
| // Already resolved or rejected. |
| return; |
| } |
| |
| if constexpr (!std::is_void_v<ResolveValueT>) |
| m_resolveValues[index] = std::forward<ResolveValueType_>(resolveValue); |
| if (!--m_outstandingPromises) { |
| if constexpr (std::is_void_v<ResolveValueT>) |
| m_producer->resolve(); |
| else { |
| m_producer->resolve(WTF::map(std::exchange(m_resolveValues, { }), [](auto&& resolveValue) { |
| return WTF::move(*resolveValue); |
| })); |
| } |
| m_producer = nullptr; |
| } |
| } |
| |
| template<typename RejectValueType_> |
| void reject(RejectValueType_&& rejectValue) |
| { |
| Locker lock { m_lock }; |
| if (!m_producer) { |
| // Already resolved or rejected. |
| return; |
| } |
| if constexpr (std::is_void_v<RejectValueT>) |
| m_producer->reject(); |
| else |
| m_producer->reject(std::forward<RejectValueType_>(rejectValue)); |
| m_producer = nullptr; |
| if constexpr (!std::is_void_v<ResolveValueT>) |
| m_resolveValues.clear(); |
| } |
| |
| Ref<AllPromiseType> promise() |
| { |
| Locker lock { m_lock }; |
| return m_producer->promise(); |
| } |
| |
| private: |
| Lock m_lock; |
| NO_UNIQUE_ADDRESS std::conditional_t<!std::is_void_v<ResolveValueT>, Vector<std::optional<ResolveValueType>>, detail::VoidPlaceholder> m_resolveValues WTF_GUARDED_BY_LOCK(m_lock); |
| std::unique_ptr<typename AllPromiseType::Producer> m_producer WTF_GUARDED_BY_LOCK(m_lock); |
| size_t m_outstandingPromises WTF_GUARDED_BY_LOCK(m_lock); |
| }; |
| |
| class AllSettledPromiseProducer : public ThreadSafeRefCounted<AllSettledPromiseProducer> { |
| public: |
| explicit AllSettledPromiseProducer(size_t dependentPromisesCount) |
| : m_producer(makeUnique<typename AllSettledPromiseType::Producer>()) |
| , m_outstandingPromises(dependentPromisesCount) |
| { |
| ASSERT(dependentPromisesCount); |
| m_results.grow(dependentPromisesCount); |
| } |
| |
| void settle(size_t index, ResultParam result) |
| { |
| Locker lock { m_lock }; |
| if (!m_producer) { |
| // Already settled. |
| return; |
| } |
| |
| m_results[index].emplace(maybeMove(result)); |
| if (!--m_outstandingPromises) { |
| m_producer->resolve(WTF::map(std::exchange(m_results, { }), [](auto&& result) { |
| return WTF::move(*result); |
| })); |
| m_producer = nullptr; |
| } |
| } |
| |
| Ref<AllSettledPromiseType> promise() |
| { |
| Locker lock { m_lock }; |
| return m_producer->promise(); |
| } |
| |
| private: |
| Lock m_lock; |
| Vector<std::optional<Result>> m_results WTF_GUARDED_BY_LOCK(m_lock); |
| std::unique_ptr<typename AllSettledPromiseType::Producer> m_producer WTF_GUARDED_BY_LOCK(m_lock); |
| size_t m_outstandingPromises WTF_GUARDED_BY_LOCK(m_lock); |
| }; |
| |
| public: |
| static Ref<AllPromiseType> all(const Vector<Ref<NativePromise>>& promises) |
| { |
| if (promises.isEmpty()) { |
| if constexpr (std::is_void_v<ResolveValueT>) |
| return AllPromiseType::createAndResolve(); |
| else |
| return AllPromiseType::createAndResolve(typename AllPromiseType::ResolveValueType()); |
| } |
| auto producer = adoptRef(new AllPromiseProducer(promises.size())); |
| auto promise = producer->promise(); |
| for (size_t i = 0; i < promises.size(); ++i) { |
| promises[i]->whenSettled([producer, i] (ResultParam result) { |
| if (result) { |
| if constexpr (std::is_void_v<ResolveValueT>) |
| producer->resolve(i, detail::VoidPlaceholder()); |
| else |
| producer->resolve(i, maybeMove(result.value())); |
| return; |
| } |
| if constexpr (std::is_void_v<RejectValueT>) |
| producer->reject(detail::VoidPlaceholder()); |
| else |
| producer->reject(maybeMove(result.error())); |
| }); |
| } |
| return promise; |
| } |
| |
| static Ref<AllSettledPromiseType> allSettled(const Vector<Ref<NativePromise>>& promises) |
| { |
| if (promises.isEmpty()) |
| return AllSettledPromiseType::createAndResolve(Vector<Result>()); |
| |
| auto producer = adoptRef(new AllSettledPromiseProducer(promises.size())); |
| auto promise = producer->promise(); |
| for (size_t i = 0; i < promises.size(); ++i) { |
| promises[i]->whenSettled([producer, i] (ResultParam result) { |
| producer->settle(i, maybeMove(result)); |
| }); |
| } |
| return promise; |
| } |
| |
| private: |
| explicit NativePromise(const Logger::LogSiteIdentifier& creationSite) |
| : m_logSiteIdentifier(creationSite) |
| { |
| PROMISE_LOG("creating ", *this); |
| } |
| |
| class ThenCallbackBase : public NativePromiseRequest::Callback { |
| |
| public: |
| ThenCallbackBase(RefPtr<GuaranteedSerialFunctionDispatcher>&& targetQueue, const Logger::LogSiteIdentifier& callSite) |
| : m_targetQueue(WTF::move(targetQueue)) |
| , m_logSiteIdentifier(callSite) |
| { |
| } |
| |
| void assertIsDead() |
| { |
| // Ensure that there are no pending (that is either not disconnected or the completion promise itself is pending) |
| #if ASSERT_ENABLED |
| if (auto p = completionPromise()) |
| p->assertIsDead(); |
| else |
| ASSERT(m_disconnected); |
| #endif |
| } |
| |
| void dispatch(NativePromise& promise, Locker<Lock>& lock) |
| { |
| assertIsHeld(promise.m_lock); |
| |
| ASSERT(!promise.isNothing()); |
| |
| if (!m_targetQueue || (promise.m_dispatchMode == PromiseDispatchMode::RunSynchronouslyOnTarget && m_targetQueue->isCurrent())) [[unlikely]] { |
| if (m_disconnected) { |
| PROMISE_LOG("ThenCallback disconnected from ", promise, " aborting [callback:", (const void*)this, " callSite:", m_logSiteIdentifier, "]"); |
| return; |
| } |
| { |
| // Holding the lock is unnecessary while running the resolve/reject callback and we don't want to hold the lock for too long. |
| DropLockForScope unlocker(lock); |
| if (promise.hasRunnable()) { |
| ASSERT(IsExclusive); |
| processResult(promise, promise.takeResultRunnable()()); |
| } else { |
| if constexpr (IsExclusive) |
| processResult(promise, promise.takeResult()); |
| else |
| processResult(promise, promise.result()); |
| } |
| } |
| return; |
| } |
| m_targetQueue->dispatch([protectedThis = Ref { *this }, promise = Ref { promise }] () mutable { |
| if (protectedThis->m_disconnected) { |
| PROMISE_LOG("ThenCallback disconnected from ", promise.get(), " aborting [callback:", (const void*)protectedThis.ptr(), " callSite:", protectedThis->m_logSiteIdentifier, "]"); |
| return; |
| } |
| if (promise->hasRunnable()) { |
| ASSERT(IsExclusive); |
| protectedThis->processResult(promise, promise->takeResultRunnable()()); |
| } else { |
| if constexpr (IsExclusive) |
| protectedThis->processResult(promise, promise->takeResult()); |
| else |
| protectedThis->processResult(promise, promise->result()); |
| } |
| }); |
| } |
| |
| void disconnect() override |
| { |
| assertIsCurrent(*m_targetQueue); |
| |
| m_disconnected = true; |
| } |
| |
| protected: |
| virtual void processResult(NativePromise&, ResultParam) = 0; |
| const RefPtr<GuaranteedSerialFunctionDispatcher> m_targetQueue; |
| const Logger::LogSiteIdentifier m_logSiteIdentifier; |
| |
| #if ASSERT_ENABLED |
| virtual RefPtr<NativePromiseBase> completionPromise() = 0; |
| // In a debug build, m_disconnected is checked in the destructor |
| // Otherwise it is only ever modified and read on the target queue. |
| std::atomic<bool> m_disconnected { false }; |
| #else |
| bool m_disconnected { false }; |
| #endif |
| }; |
| friend LogArgument<NativePromise::ThenCallbackBase>; |
| |
| template<bool IsChaining, typename ReturnPromiseType_> |
| class ThenCallback : public ThenCallbackBase { |
| public: |
| using ReturnPromiseType = std::conditional_t<IsChaining, ReturnPromiseType_, GenericPromise>; |
| using CallBackType = std::conditional_t<IsChaining, Function<Ref<ReturnPromiseType_>(ResultParam)>, Function<void(ResultParam)>>; |
| |
| ThenCallback(RefPtr<GuaranteedSerialFunctionDispatcher>&& targetQueue, CallBackType&& function, const Logger::LogSiteIdentifier& callSite) |
| : ThenCallbackBase(WTF::move(targetQueue), callSite) |
| , m_settleFunction(WTF::move(function)) |
| { |
| } |
| |
| void disconnect() override |
| { |
| assertIsCurrent(*ThenCallbackBase::m_targetQueue); |
| ThenCallbackBase::disconnect(); |
| m_settleFunction = nullptr; |
| } |
| |
| void processResult(NativePromise& promise, ResultParam result) override |
| { |
| PROMISE_LOG(result ? "Resolving" : "Rejecting", " then() call made from ", ThenCallbackBase::m_logSiteIdentifier, "[", promise, " callback:", (const void*)this, "]"); |
| if (ThenCallbackBase::m_targetQueue) |
| assertIsCurrent(*ThenCallbackBase::m_targetQueue); |
| ASSERT(m_settleFunction); |
| auto completionProducer = [this] { |
| Locker lock { m_lock }; |
| return std::exchange(m_completionProducer, { }); |
| }(); |
| if constexpr (IsChaining) { |
| auto p = m_settleFunction(maybeMove(result)); |
| if (completionProducer) |
| p->chainTo(WTF::move(*completionProducer), { "<chained completion promise>", 0 }); |
| } else { |
| m_settleFunction(maybeMove(result)); |
| if (completionProducer) |
| completionProducer->resolve({ "<chained completion promise>", 0 }); |
| } |
| |
| m_settleFunction = nullptr; |
| } |
| |
| void setCompletionPromise(std::unique_ptr<typename ReturnPromiseType::Producer>&& completionProducer) |
| { |
| Locker lock { m_lock }; |
| m_completionProducer = WTF::move(completionProducer); |
| } |
| |
| #if ASSERT_ENABLED |
| RefPtr<NativePromiseBase> completionPromise() override |
| { |
| Locker lock { m_lock }; |
| return m_completionProducer ? m_completionProducer->promise().ptr() : nullptr; |
| } |
| #endif |
| |
| Lock m_lock; |
| std::unique_ptr<typename ReturnPromiseType::Producer> m_completionProducer WTF_GUARDED_BY_LOCK(m_lock); |
| private: |
| CallBackType m_settleFunction; |
| }; |
| |
| void maybeSettle(Ref<ThenCallbackBase>&& thenCallback, const Logger::LogSiteIdentifier& callSite) |
| { |
| Locker lock { m_lock }; |
| ASSERT(!IsExclusive || !m_haveRequest, "Using an exclusive promise in a non-exclusive fashion"); |
| m_haveRequest = true; |
| PROMISE_LOG(callSite, " invoking maybeSettle() [", *this, " callback:", (const void*)thenCallback.ptr(), " isNothing:", isNothing(), "]"); |
| if (!isNothing()) |
| thenCallback->dispatch(*this, lock); |
| else |
| m_thenCallbacks.append(WTF::move(thenCallback)); |
| } |
| |
| template<typename ThenCallbackType> |
| class ThenCommand : public ConvertibleToNativePromise { |
| // Allow Promise::then() to access the private constructor, |
| template<typename, typename, unsigned> |
| friend class NativePromise; |
| |
| // used by IsConvertibleToNativePromise to determine how to cast the result. |
| using PromiseType = typename ThenCallbackType::ReturnPromiseType; |
| |
| ThenCommand(NativePromise& promise, Ref<ThenCallbackType>&& thenCallback, const Logger::LogSiteIdentifier& callSite) |
| : m_promise(promise) |
| , m_thenCallback(WTF::move(thenCallback)) |
| , m_logSiteIdentifier(callSite) |
| { |
| } |
| |
| ThenCommand(ThenCommand&& other) = default; |
| ThenCommand& operator=(ThenCommand&& other) = default; |
| |
| public: |
| ~ThenCommand() |
| { |
| // Issue the request now if the return value of then()/whenSettled() is not used. |
| if (m_thenCallback) |
| m_promise->maybeSettle(m_thenCallback.releaseNonNull(), m_logSiteIdentifier); |
| } |
| |
| // Allow calling ->then()/whenSettled() again for more promise chaining or ->track() to |
| // end chaining and track the request for future disconnection. |
| // Defined -> operator for consistency in calling pattern. |
| ThenCommand* operator->() { return this; } |
| |
| operator RefPtr<PromiseType>() |
| { |
| return Ref<PromiseType>(*this); |
| } |
| |
| // Allow conversion from ThenCommand to Ref<NativePromise> like: |
| // Ref<NativePromise> p = somePromise->then(...); |
| // p->then(thread1, ...); |
| // p->then(thread2, ...); |
| operator Ref<PromiseType>() |
| { |
| return completionPromise(); |
| } |
| |
| // Allow calling then() again by converting the ThenCommand to Ref<NativePromise> |
| template<typename ResolveFunction, typename RejectFunction> |
| auto then(GuaranteedSerialFunctionDispatcher& targetQueue, ResolveFunction&& resolveFunction, RejectFunction&& rejectFunction, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| return completionPromise()->then(targetQueue, std::forward<ResolveFunction>(resolveFunction), std::forward<RejectFunction>(rejectFunction), callSite); |
| } |
| |
| template<typename ThisType, typename ResolveMethod, typename RejectMethod> |
| auto then(GuaranteedSerialFunctionDispatcher& targetQueue, ThisType& thisVal, ResolveMethod resolveMethod, RejectMethod rejectMethod, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| return completionPromise()->then(targetQueue, thisVal, std::forward<ResolveMethod>(resolveMethod), std::forward<RejectMethod>(rejectMethod), callSite); |
| } |
| |
| // Allow calling whenSettled() again by converting the ThenCommand to Ref<NativePromise> |
| template<typename SettleFunction> |
| auto whenSettled(GuaranteedSerialFunctionDispatcher& targetQueue, SettleFunction&& settleFunction, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| return completionPromise()->whenSettled(targetQueue, std::forward<SettleFunction>(settleFunction), callSite); |
| } |
| |
| template<typename ThisType, typename SettleMethod> |
| auto whenSettled(GuaranteedSerialFunctionDispatcher& targetQueue, ThisType& thisVal, SettleMethod settleMethod, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| return completionPromise()->whenSettled(targetQueue, thisVal, std::forward<SettleMethod>(settleMethod), callSite); |
| } |
| |
| void track(NativePromiseRequest& requestHolder) |
| { |
| ASSERT(m_thenCallback, "Can only track a request once"); |
| requestHolder.track(*m_thenCallback); |
| m_promise->maybeSettle(m_thenCallback.releaseNonNull(), m_logSiteIdentifier); |
| } |
| |
| void chainTo(Producer&& chainedPromise, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| completionPromise()->chainTo(WTF::move(chainedPromise), callSite); |
| } |
| |
| template<typename ResolveValueT2, typename RejectValueT2, unsigned options2 = 0> |
| void chainTo(NativePromiseProducer<ResolveValueT2, RejectValueT2, options2>&& chainedPromise, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| completionPromise()->template chainTo<ResolveValueT2, RejectValueT2, options2>(WTF::move(chainedPromise), callSite); |
| } |
| |
| private: |
| Ref<PromiseType> completionPromise() |
| { |
| ASSERT(m_thenCallback, "Conversion can only be done once"); |
| // We create a completion promise producer which will be resolved or rejected when the ThenCallback will be run |
| // with the value returned by the callbacks provided to then(). |
| auto producer = makeUnique<typename PromiseType::Producer>(PromiseDispatchMode::Default, Logger::LogSiteIdentifier { "<completion promise>", 0 }); |
| auto promise = producer->promise(); |
| protect(m_thenCallback)->setCompletionPromise(WTF::move(producer)); |
| m_promise->maybeSettle(m_thenCallback.releaseNonNull(), m_logSiteIdentifier); |
| return promise; |
| } |
| |
| const Ref<NativePromise> m_promise; |
| RefPtr<ThenCallbackType> m_thenCallback; |
| const Logger::LogSiteIdentifier m_logSiteIdentifier; |
| }; |
| |
| struct LambdaReturnTrait { |
| template <typename T, typename = std::enable_if_t<IsConvertibleToNativePromise<T>>> |
| Ref<typename T::PromiseType> lambda(); |
| |
| template <typename T, typename = std::enable_if_t<std::is_void_v<T>>> |
| void lambda(); |
| |
| template <typename T, typename = std::enable_if_t<IsConvertibleToNativePromise<T>>> |
| typename T::PromiseType type(); |
| |
| template <typename T, typename = std::enable_if_t<std::is_void_v<T>>> |
| void type(); |
| }; |
| |
| template <typename F, typename Arg> |
| static auto invokeWithVoidOrWithArg(F&& f, Arg&& arg) |
| { |
| if constexpr (std::is_invocable_v<F>) |
| return std::invoke(std::forward<F>(f)); |
| else |
| return std::invoke(std::forward<F>(f), std::forward<Arg>(arg)); |
| } |
| |
| template <typename ThisType, typename M, typename Arg> |
| static auto invokeWithVoidOrWithArg(ThisType& thisVal, M m, Arg&& arg) |
| { |
| if constexpr (std::is_invocable_v<M, ThisType>) |
| return std::invoke(m, thisVal); |
| else |
| return std::invoke(m, thisVal, std::forward<Arg>(arg)); |
| } |
| |
| template<typename SettleFunction> |
| auto whenSettled(SettleFunction&& settleFunction, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| using ThenCallbackType = ThenCallback<false, void>; |
| using ReturnType = ThenCommand<ThenCallbackType>; |
| |
| auto thenCallback = adoptRef(*new ThenCallbackType(RefPtr<GuaranteedSerialFunctionDispatcher> { }, std::forward<SettleFunction>(settleFunction), callSite)); |
| return ReturnType(*this, WTF::move(thenCallback), callSite); |
| } |
| |
| public: |
| template<typename SettleFunction> |
| auto whenSettled(GuaranteedSerialFunctionDispatcher& targetQueue, SettleFunction&& settleFunction, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| using R1 = typename RemoveSmartPointer<decltype(invokeWithVoidOrWithArg(std::forward<SettleFunction>(settleFunction), std::declval<Result>()))>::type; |
| using IsChaining = std::bool_constant<IsConvertibleToNativePromise<R1>>; |
| static_assert(IsConvertibleToNativePromise<R1> || std::is_void_v<R1>, "Settle method must return a promise or nothing"); |
| using LambdaReturnType = decltype(std::declval<LambdaReturnTrait>().template lambda<R1>()); |
| |
| auto lambda = [settleFunction = std::forward<SettleFunction>(settleFunction)] (ResultParam result) mutable -> LambdaReturnType { |
| return invokeWithVoidOrWithArg(WTF::move(settleFunction), maybeMove(result)); |
| }; |
| |
| using ThenCallbackType = ThenCallback<IsChaining::value, decltype(std::declval<LambdaReturnTrait>().template type<R1>())>; |
| using ReturnType = ThenCommand<ThenCallbackType>; |
| |
| auto thenCallback = adoptRef(*new ThenCallbackType(RefPtr { &targetQueue }, WTF::move(lambda), callSite)); |
| return ReturnType(*this, WTF::move(thenCallback), callSite); |
| } |
| |
| template<typename ThisType, typename SettleMethod> |
| auto whenSettled(GuaranteedSerialFunctionDispatcher& targetQueue, ThisType& thisVal, SettleMethod settleMethod, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| using R1 = typename RemoveSmartPointer<decltype(invokeWithVoidOrWithArg(thisVal, settleMethod, std::declval<Result>()))>::type; |
| static_assert(IsConvertibleToNativePromise<R1> || std::is_void_v<R1>, "Settle method must return a promise or nothing"); |
| using LambdaReturnType = decltype(std::declval<LambdaReturnTrait>().template lambda<R1>()); |
| |
| return whenSettled(targetQueue, [thisVal = Ref { thisVal }, settleMethod] (ResultParam result) mutable -> LambdaReturnType { |
| return invokeWithVoidOrWithArg(thisVal.get(), settleMethod, maybeMove(result)); |
| }, callSite); |
| } |
| |
| template<typename ResolveFunction, typename RejectFunction> |
| auto then(GuaranteedSerialFunctionDispatcher& targetQueue, ResolveFunction&& resolveFunction, RejectFunction&& rejectFunction, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| using R1 = typename RemoveSmartPointer<decltype(invokeWithVoidOrWithArg(std::forward<ResolveFunction>(resolveFunction), std::declval<std::conditional_t<std::is_void_v<ResolveValueT>, detail::VoidPlaceholder, ResolveValueType>>()))>::type; |
| using R2 = typename RemoveSmartPointer<decltype(invokeWithVoidOrWithArg(std::forward<RejectFunction>(rejectFunction), std::declval<RejectValueType>()))>::type; |
| using IsChaining = std::bool_constant<RelatedNativePromise<R1, R2>>; |
| static_assert(IsChaining::value || (std::is_void_v<R1> && std::is_void_v<R2>), "resolve/reject methods must return a promise of the same type or nothing"); |
| using LambdaReturnType = decltype(std::declval<LambdaReturnTrait>().template lambda<R1>()); |
| |
| return whenSettled(targetQueue, [resolveFunction = std::forward<ResolveFunction>(resolveFunction), rejectFunction = std::forward<RejectFunction>(rejectFunction)] (ResultParam result) mutable -> LambdaReturnType { |
| if (result) { |
| if constexpr (std::is_void_v<ResolveValueT>) |
| return invokeWithVoidOrWithArg(WTF::move(resolveFunction), detail::VoidPlaceholder()); |
| else |
| return invokeWithVoidOrWithArg(WTF::move(resolveFunction), maybeMove(result.value())); |
| } |
| return invokeWithVoidOrWithArg(WTF::move(rejectFunction), maybeMove(result.error())); |
| }, callSite); |
| } |
| |
| template<typename ThisType, typename ResolveMethod, typename RejectMethod> |
| auto then(GuaranteedSerialFunctionDispatcher& targetQueue, ThisType& thisVal, ResolveMethod resolveMethod, RejectMethod rejectMethod, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| using R1 = typename RemoveSmartPointer<decltype(invokeWithVoidOrWithArg(thisVal, resolveMethod, std::declval<std::conditional_t<std::is_void_v<ResolveValueT>, detail::VoidPlaceholder, ResolveValueType>>()))>::type; |
| using R2 = typename RemoveSmartPointer<decltype(invokeWithVoidOrWithArg(thisVal, rejectMethod, std::declval<RejectValueType>()))>::type; |
| using IsChaining = std::bool_constant<RelatedNativePromise<R1, R2>>; |
| static_assert(IsChaining::value || (std::is_void_v<R1> && std::is_void_v<R2>), "resolve/reject methods must return a promise of the same type or nothing"); |
| using LambdaReturnType = decltype(std::declval<LambdaReturnTrait>().template lambda<R1>()); |
| |
| return whenSettled(targetQueue, [thisVal = Ref { thisVal }, resolveMethod, rejectMethod] (ResultParam result) -> LambdaReturnType { |
| if (result) { |
| if constexpr (std::is_void_v<ResolveValueT>) |
| return invokeWithVoidOrWithArg(thisVal.get(), resolveMethod, detail::VoidPlaceholder()); |
| else |
| return invokeWithVoidOrWithArg(thisVal.get(), resolveMethod, maybeMove(result.value())); |
| } |
| return invokeWithVoidOrWithArg(thisVal.get(), rejectMethod, maybeMove(result.error())); |
| }, callSite); |
| } |
| |
| void chainTo(Producer&& chainedPromise, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| Locker lock { m_lock }; |
| ASSERT(!IsExclusive || !m_haveRequest, "Using an exclusive promise in a non-exclusive fashion"); |
| m_haveRequest = true; |
| PROMISE_LOG(callSite, " invoking chainTo() [", *this, " chainedPromise:", chainedPromise.promise().get(), " isNothing:", isNothing(), "]"); |
| |
| if constexpr (IsExclusive) |
| chainedPromise.setDispatchMode(m_dispatchMode, callSite); |
| |
| if (isNothing()) |
| m_chainedPromises.append(WTF::move(chainedPromise)); |
| else |
| settleChainedPromise(WTF::move(chainedPromise)); |
| } |
| |
| template<typename ResolveValueT2, typename RejectValueT2, unsigned options2 = 0> |
| void chainTo(NativePromiseProducer<ResolveValueT2, RejectValueT2, options2>&& chainedPromise, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| static_assert(std::is_convertible_v<ResolveValueT, ResolveValueT2> || std::is_void_v<ResolveValueT2>, "resolve type must be compatible"); |
| static_assert(std::is_convertible_v<RejectValueT, RejectValueT2> || std::is_void_v<RejectValueT2>, "reject type must be compatible"); |
| PROMISE_LOG(callSite, " invoking chainTo() [", *this, " chainedPromise:", chainedPromise.promise().get(), " isSettled:", isSettled(), "]"); |
| if constexpr (NativePromiseProducer<ResolveValueT2, RejectValueT2, options2>::PromiseType::IsExclusive && IsExclusive) |
| chainedPromise.setDispatchMode(m_dispatchMode, callSite); |
| |
| whenSettled([producer = WTF::move(chainedPromise)](auto&& result) { |
| if (!result) { |
| if constexpr (std::is_void_v<RejectValueT2>) |
| producer.reject(); |
| else |
| producer.reject(maybeMove(result.error())); |
| return; |
| } |
| if constexpr (std::is_void_v<ResolveValueT2>) |
| producer.resolve(); |
| else |
| producer.resolve(maybeMove(*result)); |
| }); |
| } |
| |
| void assertIsDead() final |
| { |
| Locker lock { m_lock }; |
| for (auto&& thenCallback : m_thenCallbacks) |
| thenCallback->assertIsDead(); |
| for (auto&& chained : m_chainedPromises) |
| chained.assertIsDead(); |
| } |
| |
| bool isResolved() const |
| { |
| Locker lock { m_lock }; |
| return m_result && m_result->has_value(); |
| } |
| |
| bool isSettled() const |
| { |
| Locker lock { m_lock }; |
| return !isNothing(); |
| } |
| |
| template<typename T> |
| Ref<T> convert(const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| static_assert(IsNativePromise<T>, "convert expects another promise type"); |
| typename T::Producer producer { PromiseDispatchMode::Default, callSite }; |
| Ref promise = producer.promise(); |
| chainTo(WTF::move(producer)); |
| return promise; |
| } |
| |
| template<typename newResolveValueT, typename newRejectValueT, unsigned newOptions = 0> |
| operator Ref<NativePromise<newResolveValueT, newRejectValueT, newOptions>>() |
| { |
| return convert<NativePromise<newResolveValueT, newRejectValueT, newOptions>>(); |
| } |
| |
| private: |
| bool isNothing() const |
| { |
| assertIsHeld(m_lock); |
| return !m_result; |
| } |
| |
| const Result& result() const |
| { |
| // Only called by SettleFunction on the target's queue once all operations are complete and settled. |
| // So we don't really need to hold the lock to access the value. |
| Locker lock { m_lock }; |
| ASSERT(m_result.hasResult()); |
| return *m_result; |
| } |
| |
| Result takeResult() |
| { |
| // Only called by SettleFunction on the target's queue once all operations are complete and settled. |
| // So we don't really need to hold the lock to access the value. |
| Locker lock { m_lock }; |
| ASSERT(m_result.hasResult()); |
| return WTF::move(*m_result); |
| } |
| |
| bool hasRunnable() const |
| { |
| Locker lock { m_lock }; |
| return m_result.hasRunnable(); |
| } |
| |
| ResultRunnable takeResultRunnable() |
| { |
| // Only called by SettleFunction on the target's queue once all operations are complete and settled. |
| // So we don't really need to hold the lock to access the value. |
| Locker lock { m_lock }; |
| ASSERT(m_result.hasRunnable()); |
| return WTF::move(m_result.runnable()); |
| } |
| |
| void dispatchAll(Locker<Lock>& lock) |
| { |
| assertIsHeld(m_lock); |
| // We move m_thenCallbacks and m_chainedPromises while holding the lock |
| // As dispatch() may release the lock when in synchronous run mode. |
| auto thenCallbacks = std::exchange(m_thenCallbacks, { }); |
| auto chainedPromises = std::exchange(m_chainedPromises, { }); |
| for (auto& thenCallback : thenCallbacks) |
| thenCallback->dispatch(*this, lock); |
| |
| for (auto&& chainedPromise : chainedPromises) |
| settleChainedPromise(WTF::move(chainedPromise)); |
| } |
| |
| void settleChainedPromise(Producer&& other) |
| { |
| assertIsHeld(m_lock); |
| ASSERT(!isNothing()); |
| auto producer = WTF::move(other); |
| producer.promise()->settleAsChainedPromise(maybeMove(m_result), { "<chained promise>", 0 }); |
| } |
| |
| // Replicate either std::optional<Result> if Exclusive or Ref<std::optional<Result>> otherwise. |
| class Storage { |
| struct NoResult { }; |
| |
| using StorageType = Variant<NoResult, Result, ResultRunnable>; |
| struct RefCountedResult : ThreadSafeRefCounted<RefCountedResult> { |
| StorageType result = NoResult { }; |
| }; |
| using ResultType = std::conditional_t<IsExclusive, StorageType, Ref<RefCountedResult>>; |
| ResultType m_result; |
| StorageType& optionalResult() |
| { |
| if constexpr (IsExclusive) |
| return m_result; |
| else |
| return m_result->result; |
| } |
| const StorageType& optionalResult() const |
| { |
| if constexpr (IsExclusive) |
| return m_result; |
| else |
| return m_result->result; |
| } |
| public: |
| Storage() |
| : m_result([] { |
| if constexpr(IsExclusive) |
| return NoResult { }; |
| else |
| return adoptRef(*new RefCountedResult); |
| }()) |
| { |
| } |
| bool hasResult() const |
| { |
| return std::holds_alternative<Result>(optionalResult()); |
| } |
| bool hasRunnable() const |
| { |
| return std::holds_alternative<ResultRunnable>(optionalResult()); |
| } |
| explicit operator bool() const |
| { |
| return !std::holds_alternative<NoResult>(optionalResult()); |
| } |
| Storage& operator=(Storage&&) = default; |
| Storage& operator=(const Storage&) = default; |
| const Result& operator*() const |
| { |
| ASSERT(hasResult()); |
| return std::get<Result>(optionalResult()); |
| } |
| Result& operator*() |
| { |
| ASSERT(hasResult() ); |
| return std::get<Result>(optionalResult()); |
| } |
| const Result* operator->() const |
| { |
| if (!hasResult()) |
| return nullptr; |
| return &(this->operator*()); |
| } |
| template <typename Arg> |
| void emplace(Arg&& arg) |
| { |
| if constexpr (std::is_same_v<Arg, ResultRunnable>) |
| optionalResult().template emplace<2>(std::forward<Arg>(arg)); |
| else |
| optionalResult().template emplace<1>(std::forward<Arg>(arg)); |
| } |
| ResultRunnable& runnable() |
| { |
| ASSERT(hasRunnable()); |
| return std::get<ResultRunnable>(optionalResult()); |
| } |
| }; |
| const Logger::LogSiteIdentifier m_logSiteIdentifier; // For logging |
| mutable Lock m_lock; |
| Storage m_result WTF_GUARDED_BY_LOCK(m_lock); // Set on any threads when the promise is resolved, only read on the promise's target queue. |
| // Experiments show that we never have more than 3 elements when IsExclusive is false. |
| // So '3' is a good value to avoid heap allocation in most cases. |
| Vector<Ref<ThenCallbackBase>, IsExclusive ? 1 : 3> m_thenCallbacks WTF_GUARDED_BY_LOCK(m_lock); |
| Vector<Producer> m_chainedPromises WTF_GUARDED_BY_LOCK(m_lock); |
| bool m_haveRequest WTF_GUARDED_BY_LOCK(m_lock) { false }; |
| std::atomic<PromiseDispatchMode> m_dispatchMode { PromiseDispatchMode::Default }; |
| }; |
| |
| template<typename ResolveValueT, typename RejectValueT, unsigned options> |
| class NativePromiseProducer final : public ConvertibleToNativePromise { |
| WTF_DEPRECATED_MAKE_FAST_ALLOCATED(NativePromiseProducer); |
| public: |
| // used by IsConvertibleToNativePromise to determine how to cast the result. |
| using PromiseType = NativePromise<ResolveValueT, RejectValueT, options & ~static_cast<unsigned>(PromiseOption::AutoRejectProducer)>; |
| static constexpr bool AutoReject = options & PromiseOption::AutoRejectProducer; |
| static constexpr bool AutoRejectNonVoid = AutoReject && !std::is_void_v<RejectValueT>; |
| |
| template<typename = std::enable_if<!AutoRejectNonVoid>> |
| explicit NativePromiseProducer(PromiseDispatchMode dispatchMode = PromiseDispatchMode::Default, const Logger::LogSiteIdentifier& creationSite = DEFAULT_LOGSITEIDENTIFIER) |
| : m_promise(adoptRef(new PromiseType(creationSite))) |
| , m_creationSite(creationSite) |
| { |
| if constexpr (PromiseType::IsExclusive) |
| protect(m_promise)->setDispatchMode(dispatchMode, creationSite); |
| } |
| |
| template<typename RejectValueT_ = RejectValueT, typename = std::enable_if<AutoRejectNonVoid>> |
| explicit NativePromiseProducer(RejectValueT_&& defaulReject, PromiseDispatchMode dispatchMode = PromiseDispatchMode::Default, const Logger::LogSiteIdentifier& creationSite = DEFAULT_LOGSITEIDENTIFIER) |
| : m_promise(adoptRef(new PromiseType(creationSite))) |
| , m_creationSite(creationSite) |
| , m_defaultReject(std::forward<RejectValueT_>(defaulReject)) |
| { |
| if constexpr (PromiseType::IsExclusive) |
| m_promise->setDispatchMode(dispatchMode, creationSite); |
| } |
| |
| NativePromiseProducer(NativePromiseProducer&& other) = default; |
| NativePromiseProducer& operator=(NativePromiseProducer&& other) = default; |
| |
| ~NativePromiseProducer() |
| { |
| if constexpr (AutoReject) { |
| if (m_promise && !protect(m_promise)->isSettled()) { |
| PROMISE_LOG("Non settled AutoRejectProducer, reject with default value", *protect(m_promise)); |
| if constexpr (std::is_void_v<RejectValueT>) |
| reject(); |
| else |
| reject(WTF::move(m_defaultReject)); |
| } |
| } |
| assertIsDead(); |
| } |
| |
| bool isSettled() const |
| { |
| ASSERT(m_promise, "used after moved"); |
| return m_promise && protect(m_promise)->isSettled(); |
| } |
| explicit operator bool() const { return isSettled(); } |
| bool isNothing() const |
| { |
| ASSERT(m_promise, "used after moved"); |
| return m_promise && !protect(m_promise)->isSettled(); |
| } |
| |
| template<typename ResolveValueType_, typename = std::enable_if<!std::is_void_v<ResolveValueT>>> |
| void resolve(ResolveValueType_&& resolveValue, const Logger::LogSiteIdentifier& resolveSite = DEFAULT_LOGSITEIDENTIFIER) const |
| { |
| ASSERT(isNothing()); |
| if (!isNothing()) { |
| PROMISE_LOG(resolveSite, " ignored already resolved or rejected ", *protect(m_promise)); |
| return; |
| } |
| protect(m_promise)->resolve(std::forward<ResolveValueType_>(resolveValue), resolveSite); |
| } |
| |
| template<typename = std::enable_if<std::is_void_v<ResolveValueT>>> |
| void resolve(const Logger::LogSiteIdentifier& resolveSite = DEFAULT_LOGSITEIDENTIFIER) const |
| { |
| ASSERT(isNothing()); |
| if (!isNothing()) { |
| PROMISE_LOG(resolveSite, " ignored already resolved or rejected ", *protect(m_promise)); |
| return; |
| } |
| protect(m_promise)->resolve(resolveSite); |
| } |
| |
| template<typename RejectValueType_, typename = std::enable_if<!std::is_void_v<RejectValueT>>> |
| void reject(RejectValueType_&& rejectValue, const Logger::LogSiteIdentifier& rejectSite = DEFAULT_LOGSITEIDENTIFIER) const |
| { |
| ASSERT(isNothing()); |
| if (!isNothing()) { |
| PROMISE_LOG(rejectSite, " ignored already resolved or rejected ", *protect(m_promise)); |
| return; |
| } |
| protect(m_promise)->reject(std::forward<RejectValueType_>(rejectValue), rejectSite); |
| } |
| |
| template<typename = std::enable_if<std::is_void_v<RejectValueT>>> |
| void reject(const Logger::LogSiteIdentifier& rejectSite = DEFAULT_LOGSITEIDENTIFIER) const |
| { |
| ASSERT(isNothing()); |
| if (!isNothing()) { |
| PROMISE_LOG(rejectSite, " ignored already resolved or rejected ", *protect(m_promise)); |
| return; |
| } |
| protect(m_promise)->reject(rejectSite); |
| } |
| |
| template<typename SettleValue> |
| void settle(SettleValue&& result, const Logger::LogSiteIdentifier& site = DEFAULT_LOGSITEIDENTIFIER) const |
| { |
| ASSERT(isNothing()); |
| if (!isNothing()) { |
| PROMISE_LOG(site, " ignored already resolved or rejected ", *protect(m_promise)); |
| return; |
| } |
| if constexpr (PromiseType::IsExclusive && std::is_invocable_r_v<typename PromiseType::Result, SettleValue>) |
| protect(m_promise)->settleWithFunction(std::forward<SettleValue>(result), site); |
| else |
| protect(m_promise)->settle(std::forward<SettleValue>(result), site); |
| } |
| |
| template<typename = std::enable_if<PromiseType::IsExclusive>> |
| void settleWithFunction(typename PromiseType::ResultRunnable&& resultRunnable, const Logger::LogSiteIdentifier& site = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| ASSERT(isNothing()); |
| if (!isNothing()) { |
| PROMISE_LOG(site, " ignored already resolved or rejected ", *protect(m_promise)); |
| return; |
| } |
| protect(m_promise)->settleWithFunction(WTF::move(resultRunnable), site); |
| } |
| |
| operator Ref<PromiseType>() const |
| { |
| ASSERT(m_promise, "used after move"); |
| RefPtr promise = m_promise; |
| return promise.releaseNonNull(); |
| } |
| |
| Ref<PromiseType> promise() const |
| { |
| ASSERT(m_promise, "used after move"); |
| RefPtr promise = m_promise; |
| return promise.releaseNonNull(); |
| } |
| |
| // Allow calling ->then()/whenSettled() again for more promise chaining. |
| // Defined -> operator for consistency in calling pattern. |
| NativePromiseProducer* operator->() { return this; } |
| |
| // Allow calling then() again by converting the ThenCommand to Ref<NativePromise> |
| template<typename ResolveFunction, typename RejectFunction> |
| auto then(GuaranteedSerialFunctionDispatcher& targetQueue, ResolveFunction&& resolveFunction, RejectFunction&& rejectFunction, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| ASSERT(m_promise, "used after move"); |
| return m_promise->then(targetQueue, std::forward<ResolveFunction>(resolveFunction), std::forward<RejectFunction>(rejectFunction), callSite); |
| } |
| |
| template<typename ThisType, typename ResolveMethod, typename RejectMethod> |
| auto then(GuaranteedSerialFunctionDispatcher& targetQueue, ThisType& thisVal, ResolveMethod resolveMethod, RejectMethod rejectMethod, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| ASSERT(m_promise, "used after move"); |
| return m_promise->then(targetQueue, thisVal, std::forward<ResolveMethod>(resolveMethod), std::forward<RejectMethod>(rejectMethod), callSite); |
| } |
| |
| // Allow calling whenSettled() again by converting the ThenCommand to Ref<NativePromise> |
| template<class DispatcherType, typename SettleFunction> |
| auto whenSettled(DispatcherType& targetQueue, SettleFunction&& settleFunction, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| ASSERT(m_promise, "used after move"); |
| return m_promise->whenSettled(targetQueue, std::forward<SettleFunction>(settleFunction), callSite); |
| } |
| |
| template<typename ThisType, typename SettleMethod> |
| auto whenSettled(GuaranteedSerialFunctionDispatcher& targetQueue, ThisType& thisVal, SettleMethod settleMethod, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| ASSERT(m_promise, "used after move"); |
| return m_promise->whenSettled(targetQueue, thisVal, std::forward<SettleMethod>(settleMethod), callSite); |
| } |
| |
| void chainTo(NativePromiseProducer&& chainedPromise, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| ASSERT(m_promise, "used after move"); |
| m_promise->chainTo(WTF::move(chainedPromise), callSite); |
| } |
| |
| template<typename ResolveValueT2, typename RejectValueT2, unsigned options2 = 0> |
| void chainTo(NativePromiseProducer<ResolveValueT2, RejectValueT2, options2>&& chainedPromise, const Logger::LogSiteIdentifier& callSite = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| ASSERT(m_promise, "used after move"); |
| m_promise->template chainTo<ResolveValueT2, RejectValueT2, options2>(WTF::move(chainedPromise), callSite); |
| } |
| |
| template<typename RejectValueType_, typename = std::enable_if<AutoRejectNonVoid>> |
| void setDefaultReject(RejectValueType_&& rejectValue) |
| { |
| m_defaultReject = std::forward<RejectValueType_>(rejectValue); |
| } |
| |
| private: |
| template<typename ResolveValueT2, typename RejectValueT2, unsigned options2> |
| friend class NativePromise; |
| |
| void setDispatchMode(PromiseDispatchMode dispatchMode, const Logger::LogSiteIdentifier& callSite) const |
| { |
| ASSERT(m_promise, "used after move"); |
| protect(m_promise)->setDispatchMode(dispatchMode, callSite); |
| } |
| |
| friend PromiseType; |
| void assertIsDead() const |
| { |
| if (m_promise) |
| protect(m_promise)->assertIsDead(); |
| } |
| |
| // The Producer may be moved to resolve/reject the completion promise. |
| // While we expect m_promise to never be null, it would cause a null dereference in the destructor if the destructor was called after a move. |
| RefPtr<PromiseType> m_promise; |
| const Logger::LogSiteIdentifier m_creationSite; // For logging |
| NO_UNIQUE_ADDRESS std::conditional_t<AutoRejectNonVoid, RejectValueT, detail::VoidPlaceholder> m_defaultReject; |
| }; |
| |
| // A generic promise type that does the trick for simple use cases. |
| using GenericPromise = NativePromise<void, void>; |
| |
| // A generic, non-exclusive promise type that does the trick for simple use cases. |
| using GenericNonExclusivePromise = NativePromise<void, void, PromiseOption::Default | PromiseOption::NonExclusive>; |
| |
| template<typename S, typename E> |
| Ref<NativePromise<S, E>> createSettledPromise(Expected<S, E>&& result) |
| { |
| return NativePromise<S, E>::createAndSettle(WTF::move(result)); |
| } |
| |
| // Invoke a function object (e.g., lambda) asynchronously. |
| // Returns a promise that the function should eventually resolve or reject once the original promise returned by the lambda |
| // is itself resolved or rejected. |
| // The lambda can return an Expected<T, U> or void. |
| template<typename Function> |
| auto invokeAsync(SerialFunctionDispatcher& targetQueue, Function&& function, const Logger::LogSiteIdentifier& callerName = DEFAULT_LOGSITEIDENTIFIER) |
| { |
| static_assert(!std::is_lvalue_reference_v<Function>, "Function object must not be passed by lvalue-ref (to avoid unplanned copies); WTF::move() the object."); |
| using ReturnType = decltype(function()); |
| using ReturnTypeNoRef = typename RemoveSmartPointer<ReturnType>::type; |
| static_assert((IsSmartRef<ReturnType>::value && IsConvertibleToNativePromise<ReturnTypeNoRef>) || IsExpected<ReturnType>::value || std::is_void_v<ReturnType>, "Function object must return Ref<NativePromise>, Expected<T, F> or void"); |
| |
| if constexpr (IsConvertibleToNativePromise<ReturnTypeNoRef>) { |
| typename ReturnTypeNoRef::PromiseType::Producer proxyPromiseProducer(PromiseDispatchMode::Default, callerName); |
| auto promise = proxyPromiseProducer.promise(); |
| targetQueue.dispatch([producer = WTF::move(proxyPromiseProducer), function = std::forward<Function>(function)] () mutable { |
| static_cast<Ref<typename ReturnTypeNoRef::PromiseType>>(function())->chainTo(WTF::move(producer), { "invokeAsync proxy", 0 }); |
| }); |
| return promise; |
| } else if constexpr (std::is_void_v<ReturnType>) { |
| GenericPromise::Producer proxyPromiseProducer(PromiseDispatchMode::Default, callerName); |
| auto promise = proxyPromiseProducer.promise(); |
| targetQueue.dispatch([producer = WTF::move(proxyPromiseProducer), function = std::forward<Function>(function)] () mutable { |
| function(); |
| producer.resolve({ "invokeAsync proxy", 0 }); |
| }); |
| return promise; |
| } else { |
| NativePromiseProducer<typename ReturnType::value_type, typename ReturnType::error_type> proxyPromiseProducer(PromiseDispatchMode::Default, callerName); |
| auto promise = proxyPromiseProducer.promise(); |
| targetQueue.dispatch([producer = WTF::move(proxyPromiseProducer), function = std::forward<Function>(function)] () mutable { |
| createSettledPromise(function())->chainTo(WTF::move(producer), { "invokeAsync proxy", 0 }); |
| }); |
| return promise; |
| } |
| } |
| |
| template<typename ResolveValueT, typename RejectValueT, unsigned options> |
| struct LogArgument<NativePromise<ResolveValueT, RejectValueT, options>> { |
| static String toString(const NativePromise<ResolveValueT, RejectValueT, options>& p) |
| { |
| return makeString("NativePromise"_s, LogArgument<const void*>::toString(&p), '<', LogArgument<Logger::LogSiteIdentifier>::toString(p.logSiteIdentifier()), '>'); |
| } |
| }; |
| |
| template<> |
| struct LogArgument<GenericPromise> { |
| static String toString(const GenericPromise& p) |
| { |
| return makeString("GenericPromise"_s, LogArgument<const void*>::toString(&p), '<', LogArgument<Logger::LogSiteIdentifier>::toString(p.logSiteIdentifier()), '>'); |
| } |
| }; |
| |
| struct GenericPromiseConverter { |
| static auto convertError(auto&&) { return makeUnexpected(GenericPromise::RejectValueType { }); } |
| }; |
| |
| } // namespace WTF |
| |
| #undef PROMISE_LOG |
| #undef HAS_SOURCE_LOCATION |
| #undef DEFAULT_LOGSITEIDENTIFIER |
| |
| using WTF::invokeAsync; |
| using WTF::GenericPromise; |
| using WTF::GenericNonExclusivePromise; |
| using WTF::NativePromise; |
| using WTF::NativePromiseRequest; |
| using WTF::PromiseDispatchMode; |
| using WTF::PromiseOption; |