[webkit-changes] [WebKit/WebKit] a53ad4: Implement NativePromise

Jean-Yves Avenard noreply at github.com
Tue Sep 19 06:24:34 PDT 2023


  Branch: refs/heads/main
  Home:   https://github.com/WebKit/WebKit
  Commit: a53ad484a1c9fd497ba71ddbad3d934edbdd1d9c
      https://github.com/WebKit/WebKit/commit/a53ad484a1c9fd497ba71ddbad3d934edbdd1d9c
  Author: Jean-Yves Avenard <jya at apple.com>
  Date:   2023-09-19 (Tue, 19 Sep 2023)

  Changed paths:
    M Source/WTF/WTF.xcodeproj/project.pbxproj
    M Source/WTF/wtf/CMakeLists.txt
    M Source/WTF/wtf/Logging.cpp
    M Source/WTF/wtf/Logging.h
    A Source/WTF/wtf/NativePromise.cpp
    A Source/WTF/wtf/NativePromise.h
    A Source/WTF/wtf/TypeTraits.h
    M Source/WebCore/platform/Logging.h
    M Tools/TestWebKitAPI/CMakeLists.txt
    M Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
    A Tools/TestWebKitAPI/Tests/WTF/NativePromise.cpp

  Log Message:
  -----------
  Implement NativePromise
https://bugs.webkit.org/show_bug.cgi?id=254502
rdar://107546076

Reviewed by Youenn Fablet.

This is a WTF C++ native JS's like Promise, inspired by Gecko's MozPromise object

There are some significant differences from the gecko's implemtation,
both from a modus of operations and syntax usage in order to better fit
with WebKit, in particular:
- NativePromise can have a resolveType of `void`
- NativePromise takes a SerialFunctionDispatcher with ref/deref methods and
  is a template argument.
- There's typically no need to specify the return types of the resolve/reject callbacks.
  `auto` should work in most cases.

About NativePromise:

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()ed on any thread.
The then() call accepts either a resolve and reject callback, while whenSettled() accepts a resolveOrReject one.

NativePromise::then() and NativePromise::whenSettled() returns a NativePromise::Request object. This request 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 have a move constructor if IsExclusive is true. 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.

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 (via NativePromise::Producer::create() 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() callbacks will not be run.

Example:
```
        // You can mix & match promise types and chain them together.
        using MyPromise = NativePromise<int, int, true>;
        GenericPromise::Producer p(__func__);
        using MyPromise = NativePromise<int, int, true>;
        p->whenSettled(queue, __func__, [] (GenericPromise::Result result) {
            return MyPromise::createAndResolve(1, __func__);
        })->whenSettled(queue, __func__, [queue] (MyPromise::Result result) {
            static_assert(std::is_same_v<MyPromise::Result::value_type, int>, "The type received is the same as the last promise returned");
            EXPECT_TRUE(result.has_value());
            EXPECT_EQ(result.value(), 1);
            queue->beginShutdown();
        });
        p.resolve(__func__);
```

API tests added (re-used from Gecko's source code and adapted for NativePromise)

Canonical link: https://commits.webkit.org/268120@main




More information about the webkit-changes mailing list