Skip to content

Commit cad95ce

Browse files
linusgawesomekling
authored andcommitted
LibJS: Implement Promise.try()
See: https://github.com/tc39/proposal-promise-try
1 parent 2317a8a commit cad95ce

File tree

4 files changed

+104
-1
lines changed

4 files changed

+104
-1
lines changed

Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,7 @@ struct CommonPropertyNames {
614614
PropertyKey register_ { "register", PropertyKey::StringMayBeNumber::No };
615615
PropertyKey return_ { "return", PropertyKey::StringMayBeNumber::No };
616616
PropertyKey throw_ { "throw", PropertyKey::StringMayBeNumber::No };
617+
PropertyKey try_ { "try", PropertyKey::StringMayBeNumber::No };
617618
PropertyKey union_ { "union", PropertyKey::StringMayBeNumber::No };
618619
PropertyKey xor_ { "xor", PropertyKey::StringMayBeNumber::No };
619620
PropertyKey inputAlias { "$_", PropertyKey::StringMayBeNumber::No };

Userland/Libraries/LibJS/Runtime/PromiseConstructor.cpp

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
2+
* Copyright (c) 2021-2024, Linus Groh <linusg@serenityos.org>
33
*
44
* SPDX-License-Identifier: BSD-2-Clause
55
*/
@@ -238,6 +238,7 @@ void PromiseConstructor::initialize(Realm& realm)
238238
define_native_function(realm, vm.names.race, race, 1, attr);
239239
define_native_function(realm, vm.names.reject, reject, 1, attr);
240240
define_native_function(realm, vm.names.resolve, resolve, 1, attr);
241+
define_native_function(realm, vm.names.try_, try_, 1, attr);
241242
define_native_function(realm, vm.names.withResolvers, with_resolvers, 0, attr);
242243

243244
define_native_accessor(realm, vm.well_known_symbol_species(), symbol_species_getter, {}, Attribute::Configurable);
@@ -458,6 +459,43 @@ JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::resolve)
458459
return TRY(promise_resolve(vm, constructor.as_object(), value));
459460
}
460461

462+
// 1 Promise.try ( callbackfn, ...args ), https://tc39.es/proposal-promise-try/#sec-promise.try
463+
JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::try_)
464+
{
465+
auto callbackfn = vm.argument(0);
466+
Span<Value> args;
467+
if (vm.argument_count() > 1) {
468+
args = vm.running_execution_context().arguments.span().slice(1, vm.argument_count() - 1);
469+
}
470+
471+
// 1. Let C be the this value.
472+
auto constructor = vm.this_value();
473+
474+
// 2. If C is not an Object, throw a TypeError exception.
475+
if (!constructor.is_object())
476+
return vm.throw_completion<TypeError>(ErrorType::NotAnObject, constructor.to_string_without_side_effects());
477+
478+
// 3. Let promiseCapability be ? NewPromiseCapability(C).
479+
auto promise_capability = TRY(new_promise_capability(vm, constructor));
480+
481+
// 4. Let status be Completion(Call(callbackfn, undefined, args)).
482+
auto status = JS::call(vm, callbackfn, js_undefined(), args);
483+
484+
// 5. If status is an abrupt completion, then
485+
if (status.is_throw_completion()) {
486+
// a. Perform ? Call(promiseCapability.[[Reject]], undefined, « status.[[Value]] »).
487+
TRY(JS::call(vm, *promise_capability->reject(), js_undefined(), *status.throw_completion().value()));
488+
}
489+
// 6. Else,
490+
else {
491+
// a. Perform ? Call(promiseCapability.[[Resolve]], undefined, « status.[[Value]] »).
492+
TRY(JS::call(vm, *promise_capability->resolve(), js_undefined(), status.value()));
493+
}
494+
495+
// 7. Return promiseCapability.[[Promise]].
496+
return promise_capability->promise();
497+
}
498+
461499
// 27.2.4.8 Promise.withResolvers ( ), https://tc39.es/ecma262/#sec-promise.withResolvers
462500
JS_DEFINE_NATIVE_FUNCTION(PromiseConstructor::with_resolvers)
463501
{

Userland/Libraries/LibJS/Runtime/PromiseConstructor.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class PromiseConstructor final : public NativeFunction {
3333
JS_DECLARE_NATIVE_FUNCTION(reject);
3434
JS_DECLARE_NATIVE_FUNCTION(resolve);
3535
JS_DECLARE_NATIVE_FUNCTION(symbol_species_getter);
36+
JS_DECLARE_NATIVE_FUNCTION(try_);
3637
JS_DECLARE_NATIVE_FUNCTION(with_resolvers);
3738
};
3839

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
describe("errors", () => {
2+
test("this value must be a constructor", () => {
3+
expect(() => {
4+
Promise.try.call({});
5+
}).toThrowWithMessage(TypeError, "[object Object] is not a constructor");
6+
});
7+
});
8+
9+
describe("normal behavior", () => {
10+
test("length is 1", () => {
11+
expect(Promise.try).toHaveLength(1);
12+
});
13+
14+
test("returned promise is a Promise", () => {
15+
const fn = () => {};
16+
const promise = Promise.try(fn);
17+
expect(promise).toBeInstanceOf(Promise);
18+
});
19+
20+
test("returned promise is resolved when function completes normally", () => {
21+
const fn = () => {};
22+
const promise = Promise.try(fn);
23+
24+
let fulfillmentValue = null;
25+
promise.then(value => {
26+
fulfillmentValue = value;
27+
});
28+
29+
runQueuedPromiseJobs();
30+
31+
expect(fulfillmentValue).toBe(undefined);
32+
});
33+
34+
test("returned promise is rejected when function throws", () => {
35+
const fn = () => {
36+
throw "error";
37+
};
38+
const promise = Promise.try(fn);
39+
40+
let rejectionReason = null;
41+
promise.catch(value => {
42+
rejectionReason = value;
43+
});
44+
45+
runQueuedPromiseJobs();
46+
47+
expect(rejectionReason).toBe("error");
48+
});
49+
50+
test("arguments are forwarded to the function", () => {
51+
const fn = (...args) => args;
52+
const promise = Promise.try(fn, "foo", 123, true);
53+
54+
let fulfillmentValue = null;
55+
promise.then(value => {
56+
fulfillmentValue = value;
57+
});
58+
59+
runQueuedPromiseJobs();
60+
61+
expect(fulfillmentValue).toEqual(["foo", 123, true]);
62+
});
63+
});

0 commit comments

Comments
 (0)