Skip to content

Commit

Permalink
invokeAsync should let you call method returning Expected<T, U>
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=265175
rdar://118671806

Reviewed by Youenn Fablet.

Add createSettledPromise(Expected&&> utility method.

Add API tests.

* Source/WTF/wtf/NativePromise.h:
(WTF::createSettledPromise):
(WTF::invokeAsync):
* Source/WTF/wtf/TypeTraits.h: Add IsExpected template.
* Tools/TestWebKitAPI/Tests/WTF/NativePromise.cpp:
(TestWebKitAPI::TEST):

Canonical link: https://commits.webkit.org/271010@main
  • Loading branch information
jyavenard committed Nov 21, 2023
1 parent f2d7213 commit 9d59d5e
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 11 deletions.
46 changes: 35 additions & 11 deletions Source/WTF/wtf/NativePromise.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ class ConvertibleToNativePromise { };
template<typename T>
class NativePromiseRequest;

template<typename ResolveValueT, typename RejectValueT, unsigned options>
template<typename ResolveValueT, typename RejectValueT, unsigned options = 0>
class NativePromiseProducer;

enum class PromiseDispatchMode : uint8_t {
Expand Down Expand Up @@ -1466,23 +1466,47 @@ class NativePromiseRequest final {
RefPtr<typename PromiseType::Request> m_request;
};

template<typename S, typename E>
Ref<NativePromise<S, E>> createSettledPromise(Expected<S, E>&& result)
{
return NativePromise<S, E>::createAndSettle(WTFMove(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>
static 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); WTFMove() the object.");
static_assert(IsConvertibleToNativePromise<typename RemoveSmartPointer<decltype(function())>::type>, "Function object must return Ref<NativePromise>");
using ReturnType = typename RemoveSmartPointer<decltype(function())>::type;

typename ReturnType::PromiseType::Producer proxyPromiseProducer(PromiseDispatchMode::Default, callerName);
auto promise = proxyPromiseProducer.promise();
targetQueue.dispatch([producer = WTFMove(proxyPromiseProducer), function = WTFMove(function)] () mutable {
Ref<typename ReturnType::PromiseType> p = function();
p->chainTo(WTFMove(producer), { "invokeAsync proxy", nullptr });
});
return promise;
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 = WTFMove(proxyPromiseProducer), function = WTFMove(function)] () mutable {
static_cast<Ref<typename ReturnTypeNoRef::PromiseType>>(function())->chainTo(WTFMove(producer), { "invokeAsync proxy", nullptr });
});
return promise;
} else if constexpr (std::is_void_v<ReturnType>) {
GenericPromise::Producer proxyPromiseProducer(PromiseDispatchMode::Default, callerName);
auto promise = proxyPromiseProducer.promise();
targetQueue.dispatch([producer = WTFMove(proxyPromiseProducer), function = WTFMove(function)] () mutable {
function();
producer.resolve({ "invokeAsync proxy", nullptr });
});
return promise;
} else {
NativePromiseProducer<typename ReturnType::value_type, typename ReturnType::error_type> proxyPromiseProducer(PromiseDispatchMode::Default, callerName);
auto promise = proxyPromiseProducer.promise();
targetQueue.dispatch([producer = WTFMove(proxyPromiseProducer), function = WTFMove(function)] () mutable {
createSettledPromise(function())->chainTo(WTFMove(producer), { "invokeAsync proxy", nullptr });
});
return promise;
}
}

template<typename ResolveValueT, typename RejectValueT, unsigned options>
Expand Down
8 changes: 8 additions & 0 deletions Source/WTF/wtf/TypeTraits.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#pragma once

#include <type_traits>
#include <wtf/Forward.h>
#include <wtf/FunctionDispatcher.h>
#include <wtf/Ref.h>
#include <wtf/RefPtr.h>
Expand Down Expand Up @@ -170,4 +171,11 @@ template <typename T, typename U>
constexpr bool RelatedNativePromise = IsConvertibleToNativePromise<T> && IsConvertibleToNativePromise<U>;
#endif


template <typename T>
struct IsExpected : std::false_type { };

template <typename T, typename E>
struct IsExpected<Expected<T, E>> : std::true_type { };

} // namespace NTF
45 changes: 45 additions & 0 deletions Tools/TestWebKitAPI/Tests/WTF/NativePromise.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,35 @@ TEST(NativePromise, InvokeAsyncAutoConversion)
Util::run(&done);
}

TEST(NativePromise, InvokeAsyncWithExpected)
{
runInCurrentRunLoopUntilDone([](auto& runLoop, bool& done) {
auto asyncMethodWithExpected = [] {
return Expected<int, long> { 1 };
};

invokeAsync(runLoop, WTFMove(asyncMethodWithExpected))->whenSettled(runLoop, [&](auto&& result) {
EXPECT_TRUE(!!result);
EXPECT_EQ(result.value(), 1L);
done = true;
});
});
}

TEST(NativePromise, InvokeAsyncWithVoid)
{
runInCurrentRunLoopUntilDone([](auto& runLoop, bool& done) {
invokeAsync(runLoop, [] {
EXPECT_TRUE(true);
})->whenSettled(runLoop, [&](auto&& result) {
EXPECT_TRUE(!!result);
static_assert(std::is_same_v<std::remove_reference_t<decltype(result)>, GenericPromise::Result>, "We must be getting a GenericPromise");
static_assert(std::is_void_v<typename std::remove_reference_t<decltype(result)>::value_type>);
done = true;
});
});
}

static Ref<GenericPromise> myMethodReturningProducer()
{
assertIsCurrent(RunLoop::main());
Expand Down Expand Up @@ -1616,6 +1645,22 @@ TEST(NativePromise, MismatchChainToVoidPromise)
});
}

TEST(NativePromise, CreateSettledPromise)
{
runInCurrentRunLoopUntilDone([](auto& runLoop, bool& done) {
using MyExpected = Expected<int, long>;
createSettledPromise(MyExpected { makeUnexpected<long>(1) })->whenSettled(runLoop, [](auto&& result) {
EXPECT_TRUE(!result);
EXPECT_EQ(result.error(), 1L);
});
createSettledPromise(MyExpected { 1 })->whenSettled(runLoop, [&](auto&& result) {
EXPECT_TRUE(!!result);
EXPECT_EQ(result.value(), 1);
done = true;
});
});
}

// 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.
Expand Down

0 comments on commit 9d59d5e

Please sign in to comment.