Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 15 additions & 33 deletions core/engine/src/builtins/promise/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -474,12 +474,12 @@ impl Promise {
&self.state
}

/// [`Promise.try ( callbackfn, ...args )`][spec]
/// [`Promise.try ( callback, ...args )`][spec]
///
/// Calls the given function and returns a new promise that is resolved if the function
/// completes normally and rejected if it throws.
/// Calls the given function and returns a promise rejected with what it throws, or resolved
/// with what it returns. A returned promise made by the same constructor is returned as is.
///
/// [spec]: https://tc39.es/proposal-promise-try/#sec-promise.try
/// [spec]: https://tc39.es/ecma262/#sec-promise.try
pub(crate) fn r#try(
this: &JsValue,
args: &[JsValue],
Expand All @@ -488,43 +488,25 @@ impl Promise {
let callback = args.get_or_undefined(0);
let callback_args = args.get(1..).unwrap_or(&[]);

// 1. Let C be the this value.
// 2. If C is not an Object, throw a TypeError exception.
// 1. Let ctor be the this value.
// 2. If ctor is not an Object, throw a TypeError exception.
let c = this.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("Promise.try() called on a non-object")
})?;

// 3. Let promiseCapability be ? NewPromiseCapability(C).
let promise_capability = PromiseCapability::new(&c, context)?;

// 4. Let status be Completion(Call(callbackfn, undefined, args)).
// 3. Let status be Completion(Call(callback, undefined, args)).
let status = callback.call(&JsValue::undefined(), callback_args, context);

match status {
// 5. If status is an abrupt completion, then
Err(err) => {
let value = err.into_opaque(context)?;

// a. Perform ? Call(promiseCapability.[[Reject]], undefined, « status.[[Value]] »).
promise_capability.functions.reject.call(
&JsValue::undefined(),
&[value],
context,
)?;
}
// 6. Else,
Ok(value) => {
// a. Perform ? Call(promiseCapability.[[Resolve]], undefined, « status.[[Value]] »).
promise_capability.functions.resolve.call(
&JsValue::undefined(),
&[value],
context,
)?;
}
// 4. If status is an abrupt completion, then
// a. Let promiseCapability be ? NewPromiseCapability(ctor).
// b. Perform ? Call(promiseCapability.[[Reject]], undefined, « status.[[Value]] »).
// c. Return promiseCapability.[[Promise]].
Err(err) => Self::promise_reject(&c, err, context).map(JsValue::from),
// 5. Else,
// a. Return ? PromiseResolve(ctor, ! status).
Ok(value) => Self::promise_resolve(&c, value, context).map(JsValue::from),
}

// 7. Return promiseCapability.[[Promise]].
Ok(promise_capability.promise.clone().into())
}

/// [`Promise.withResolvers ( )`][spec]
Expand Down
49 changes: 49 additions & 0 deletions core/engine/src/builtins/promise/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{TestAction, run_test_actions};
use boa_macros::js_str;
use indoc::indoc;

#[test]
Expand Down Expand Up @@ -98,3 +99,51 @@ fn promise_race_resolves_first() {
TestAction::assert_eq("val", 10),
]);
}

#[test]
fn promise_try_returns_promise_of_same_constructor_as_is() {
run_test_actions([
TestAction::run(indoc! {r#"
var p = Promise.resolve(1);
class SubPromise extends Promise {}
var sub = SubPromise.resolve(2);
"#}),
TestAction::assert("Promise.try(() => p) === p"),
TestAction::assert("SubPromise.try(() => sub) === sub"),
// A promise from another constructor is still wrapped.
TestAction::assert("Promise.try(() => sub) !== sub"),
TestAction::assert("SubPromise.try(() => p) !== p"),
TestAction::assert("SubPromise.try(() => p) instanceof SubPromise"),
]);
}

#[test]
fn promise_try_constructs_only_after_calling_the_callback() {
run_test_actions([
TestAction::run(indoc! {r#"
var log = [];
function Ctor(executor) {
log.push("construct");
return new Promise(executor);
}
Promise.try.call(Ctor, () => { log.push("callback"); return 1; });
Promise.try.call(Ctor, () => { log.push("callback"); throw 2; });
"#}),
TestAction::assert_eq(
"log.join()",
js_str!("callback,construct,callback,construct"),
),
]);
}

#[test]
fn promise_try_rejects_with_the_thrown_value() {
run_test_actions([
TestAction::run(indoc! {r#"
var err = null;
Promise.try(() => { throw 3; }).catch(e => { err = e; });
"#}),
TestAction::inspect_context(|ctx| ctx.run_jobs().unwrap()),
TestAction::assert_eq("err", 3),
]);
}
Loading