LibJS: Implement Promise.try()

See: https://github.com/tc39/proposal-promise-try
This commit is contained in:
Linus Groh 2024-04-09 07:02:15 +01:00 committed by Andreas Kling
parent 2317a8a4eb
commit cad95ce274
4 changed files with 104 additions and 1 deletions

View file

@ -614,6 +614,7 @@ struct CommonPropertyNames {
PropertyKey register_ { "register", PropertyKey::StringMayBeNumber::No };
PropertyKey return_ { "return", PropertyKey::StringMayBeNumber::No };
PropertyKey throw_ { "throw", PropertyKey::StringMayBeNumber::No };
PropertyKey try_ { "try", PropertyKey::StringMayBeNumber::No };
PropertyKey union_ { "union", PropertyKey::StringMayBeNumber::No };
PropertyKey xor_ { "xor", PropertyKey::StringMayBeNumber::No };
PropertyKey inputAlias { "$_", PropertyKey::StringMayBeNumber::No };

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021-2024, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -238,6 +238,7 @@ void 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.try_, try_, 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);
@ -458,6 +459,43 @@ JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::resolve)
return TRY(promise_resolve(vm, constructor.as_object(), value));
}
// 1 Promise.try ( callbackfn, ...args ), https://tc39.es/proposal-promise-try/#sec-promise.try
JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::try_)
{
auto callbackfn = vm.argument(0);
Span<Value> args;
if (vm.argument_count() > 1) {
args = vm.running_execution_context().arguments.span().slice(1, vm.argument_count() - 1);
}
// 1. Let C be the this value.
auto constructor = vm.this_value();
// 2. If C is not an Object, throw a TypeError exception.
if (!constructor.is_object())
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, constructor.to_string_without_side_effects());
// 3. Let promiseCapability be ? NewPromiseCapability(C).
auto promise_capability = TRY(new_promise_capability(vm, constructor));
// 4. Let status be Completion(Call(callbackfn, undefined, args)).
auto status = JS::call(vm, callbackfn, js_undefined(), args);
// 5. If status is an abrupt completion, then
if (status.is_throw_completion()) {
// a. Perform ? Call(promiseCapability.[[Reject]], undefined, « status.[[Value]] »).
TRY(JS::call(vm, *promise_capability->reject(), js_undefined(), *status.throw_completion().value()));
}
// 6. Else,
else {
// a. Perform ? Call(promiseCapability.[[Resolve]], undefined, « status.[[Value]] »).
TRY(JS::call(vm, *promise_capability->resolve(), js_undefined(), status.value()));
}
// 7. Return promiseCapability.[[Promise]].
return promise_capability->promise();
}
// 27.2.4.8 Promise.withResolvers ( ), https://tc39.es/ecma262/#sec-promise.withResolvers
JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::with_resolvers)
{

View file

@ -33,6 +33,7 @@ private:
JS_DECLARE_NATIVE_FUNCTION(reject);
JS_DECLARE_NATIVE_FUNCTION(resolve);
JS_DECLARE_NATIVE_FUNCTION(symbol_species_getter);
JS_DECLARE_NATIVE_FUNCTION(try_);
JS_DECLARE_NATIVE_FUNCTION(with_resolvers);
};

View file

@ -0,0 +1,63 @@
describe("errors", () => {
test("this value must be a constructor", () => {
expect(() => {
Promise.try.call({});
}).toThrowWithMessage(TypeError, "[object Object] is not a constructor");
});
});
describe("normal behavior", () => {
test("length is 1", () => {
expect(Promise.try).toHaveLength(1);
});
test("returned promise is a Promise", () => {
const fn = () => {};
const promise = Promise.try(fn);
expect(promise).toBeInstanceOf(Promise);
});
test("returned promise is resolved when function completes normally", () => {
const fn = () => {};
const promise = Promise.try(fn);
let fulfillmentValue = null;
promise.then(value => {
fulfillmentValue = value;
});
runQueuedPromiseJobs();
expect(fulfillmentValue).toBe(undefined);
});
test("returned promise is rejected when function throws", () => {
const fn = () => {
throw "error";
};
const promise = Promise.try(fn);
let rejectionReason = null;
promise.catch(value => {
rejectionReason = value;
});
runQueuedPromiseJobs();
expect(rejectionReason).toBe("error");
});
test("arguments are forwarded to the function", () => {
const fn = (...args) => args;
const promise = Promise.try(fn, "foo", 123, true);
let fulfillmentValue = null;
promise.then(value => {
fulfillmentValue = value;
});
runQueuedPromiseJobs();
expect(fulfillmentValue).toEqual(["foo", 123, true]);
});
});