diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 4b4ae47950..67012feafd 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -405,6 +405,7 @@ namespace JS { P(pop) \ P(pow) \ P(preventExtensions) \ + P(promise) \ P(propertyIsEnumerable) \ P(prototype) \ P(proxy) \ @@ -582,6 +583,7 @@ namespace JS { P(withCalendar) \ P(withPlainDate) \ P(withPlainTime) \ + P(withResolvers) \ P(withTimeZone) \ P(writable) \ P(year) \ diff --git a/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp b/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp index 86e80e0632..754dbf0ca5 100644 --- a/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp @@ -259,6 +259,7 @@ ThrowCompletionOr PromiseConstructor::initialize(Realm& realm) define_native_function(realm, vm.names.race, race, 1, attr); define_native_function(realm, vm.names.reject, reject, 1, attr); define_native_function(realm, vm.names.resolve, resolve, 1, attr); + define_native_function(realm, vm.names.withResolvers, with_resolvers, 0, attr); define_native_accessor(realm, vm.well_known_symbol_species(), symbol_species_getter, {}, Attribute::Configurable); @@ -487,4 +488,31 @@ JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::symbol_species_getter) return vm.this_value(); } +// 1.1.1.1 Promise.withResolvers ( ), https://tc39.es/proposal-promise-with-resolvers/#sec-promise.withResolvers +JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::with_resolvers) +{ + auto& realm = *vm.current_realm(); + + // 1. Let C be the this value. + auto constructor = vm.this_value(); + + // 2. Let promiseCapability be ? NewPromiseCapability(C). + auto promise_capability = TRY(new_promise_capability(vm, constructor)); + + // 3. Let obj be OrdinaryObjectCreate(%Object.prototype%). + auto object = Object::create(realm, realm.intrinsics().object_prototype()); + + // 4. Perform ! CreateDataPropertyOrThrow(obj, "promise", promiseCapability.[[Promise]]). + MUST(object->create_data_property_or_throw(vm.names.promise, promise_capability->promise())); + + // 5. Perform ! CreateDataPropertyOrThrow(obj, "resolve", promiseCapability.[[Resolve]]). + MUST(object->create_data_property_or_throw(vm.names.resolve, promise_capability->resolve())); + + // 6. Perform ! CreateDataPropertyOrThrow(obj, "reject", promiseCapability.[[Reject]]). + MUST(object->create_data_property_or_throw(vm.names.reject, promise_capability->reject())); + + // 7. Return obj. + return object; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/PromiseConstructor.h b/Userland/Libraries/LibJS/Runtime/PromiseConstructor.h index a3874eaf2d..4f81ed64f5 100644 --- a/Userland/Libraries/LibJS/Runtime/PromiseConstructor.h +++ b/Userland/Libraries/LibJS/Runtime/PromiseConstructor.h @@ -31,8 +31,8 @@ private: JS_DECLARE_NATIVE_FUNCTION(race); JS_DECLARE_NATIVE_FUNCTION(reject); JS_DECLARE_NATIVE_FUNCTION(resolve); - JS_DECLARE_NATIVE_FUNCTION(symbol_species_getter); + JS_DECLARE_NATIVE_FUNCTION(with_resolvers); }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Promise/Promise.withResolvers.js b/Userland/Libraries/LibJS/Tests/builtins/Promise/Promise.withResolvers.js new file mode 100644 index 0000000000..199fa22aeb --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Promise/Promise.withResolvers.js @@ -0,0 +1,56 @@ +describe("errors", () => { + test("this value must be a constructor", () => { + expect(() => { + Promise.withResolvers.call(Symbol.hasInstance); + }).toThrowWithMessage(TypeError, "Symbol(Symbol.hasInstance) is not a constructor"); + }); +}); + +describe("normal behavior", () => { + test("length is 0", () => { + expect(Promise.withResolvers).toHaveLength(0); + }); + + test("returned promise is a Promise", () => { + const { promise, resolve, reject } = Promise.withResolvers(); + expect(promise).toBeInstanceOf(Promise); + }); + + test("returned resolve/reject are unary functions", () => { + const { promise, resolve, reject } = Promise.withResolvers(); + + expect(resolve).toBeInstanceOf(Function); + expect(resolve).toHaveLength(1); + + expect(reject).toBeInstanceOf(Function); + expect(reject).toHaveLength(1); + }); + + test("returned promise can be resolved", () => { + const { promise, resolve, reject } = Promise.withResolvers(); + + let fulfillmentValue = null; + promise.then(value => { + fulfillmentValue = value; + }); + + resolve("Some value"); + runQueuedPromiseJobs(); + + expect(fulfillmentValue).toBe("Some value"); + }); + + test("returned promise can be rejected", () => { + const { promise, resolve, reject } = Promise.withResolvers(); + + let rejectionReason = null; + promise.catch(value => { + rejectionReason = value; + }); + + reject("Some value"); + runQueuedPromiseJobs(); + + expect(rejectionReason).toBe("Some value"); + }); +});