@ -20,7 +20,7 @@ use crate::{
value ::JsValue ,
Context , JsResult ,
} ;
use boa_gc ::{ Finalize , Gc , Trace } ;
use boa_gc ::{ Cell as GcCell , Finalize , Gc , Trace } ;
use boa_profiler ::Profiler ;
use std ::{ cell ::Cell , rc ::Rc } ;
use tap ::{ Conv , Pipe } ;
@ -218,6 +218,7 @@ impl BuiltIn for Promise {
)
. name ( Self ::NAME )
. length ( Self ::LENGTH )
. static_method ( Self ::all , "all" , 1 )
. static_method ( Self ::race , "race" , 1 )
. static_method ( Self ::reject , "reject" , 1 )
. static_method ( Self ::resolve , "resolve" , 1 )
@ -316,6 +317,247 @@ impl Promise {
promise . conv ::< JsValue > ( ) . pipe ( Ok )
}
/// `Promise.all ( iterable )`
///
/// More information:
/// - [ECMAScript reference][spec]
/// - [MDN documentation][mdn]
///
/// [spec]: https://tc39.es/ecma262/#sec-promise.all
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
pub ( crate ) fn all (
this : & JsValue ,
args : & [ JsValue ] ,
context : & mut Context ,
) -> JsResult < JsValue > {
// 1. Let C be the this value.
let c = this ;
// 2. Let promiseCapability be ? NewPromiseCapability(C).
let promise_capability = PromiseCapability ::new ( c , context ) ? ;
// Note: We already checked that `C` is a constructor in `NewPromiseCapability(C)`.
let c = c . as_object ( ) . expect ( "must be a constructor" ) ;
// 3. Let promiseResolve be Completion(GetPromiseResolve(C)).
let promise_resolve = Self ::get_promise_resolve ( c , context ) ;
// 4. IfAbruptRejectPromise(promiseResolve, promiseCapability).
if_abrupt_reject_promise ! ( promise_resolve , promise_capability , context ) ;
// 5. Let iteratorRecord be Completion(GetIterator(iterable)).
let iterator_record = args . get_or_undefined ( 0 ) . get_iterator ( context , None , None ) ;
// 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
if_abrupt_reject_promise ! ( iterator_record , promise_capability , context ) ;
let mut iterator_record = iterator_record ;
// 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)).
let mut result = Self ::perform_promise_all (
& mut iterator_record ,
c ,
& promise_capability ,
& promise_resolve ,
context ,
)
. map ( JsValue ::from ) ;
// 8. If result is an abrupt completion, then
if result . is_err ( ) {
// a. If iteratorRecord.[[Done]] is false, set result to Completion(IteratorClose(iteratorRecord, result)).
if ! iterator_record . done ( ) {
result = iterator_record . close ( result , context ) ;
}
// b. IfAbruptRejectPromise(result, promiseCapability).
if_abrupt_reject_promise ! ( result , promise_capability , context ) ;
return Ok ( result ) ;
}
// 9. Return ? result.
result
}
/// `PerformPromiseAll ( iteratorRecord, constructor, resultCapability, promiseResolve )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-performpromiseall
fn perform_promise_all (
iterator_record : & mut IteratorRecord ,
constructor : & JsObject ,
result_capability : & PromiseCapability ,
promise_resolve : & JsObject ,
context : & mut Context ,
) -> JsResult < JsObject > {
#[ derive(Debug, Trace, Finalize) ]
struct ResolveElementCaptures {
#[ unsafe_ignore_trace ]
already_called : Rc < Cell < bool > > ,
index : usize ,
values : GcCell < Vec < JsValue > > ,
capability_resolve : JsFunction ,
#[ unsafe_ignore_trace ]
remaining_elements_count : Rc < Cell < i32 > > ,
}
// 1. Let values be a new empty List.
let values = GcCell ::new ( Vec ::new ( ) ) ;
// 2. Let remainingElementsCount be the Record { [[Value]]: 1 }.
let remaining_elements_count = Rc ::new ( Cell ::new ( 1 ) ) ;
// 3. Let index be 0.
let mut index = 0 ;
// 4. Repeat,
loop {
// a. Let next be Completion(IteratorStep(iteratorRecord)).
let next = iterator_record . step ( context ) ;
let next_value = match next {
Err ( e ) = > {
// b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record . set_done ( true ) ;
// c. ReturnIfAbrupt(next).
return Err ( e ) ;
}
// d. If next is false, then
Ok ( None ) = > {
// i. Set iteratorRecord.[[Done]] to true.
iterator_record . set_done ( true ) ;
// ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
remaining_elements_count . set ( remaining_elements_count . get ( ) - 1 ) ;
// iii. If remainingElementsCount.[[Value]] is 0, then
if remaining_elements_count . get ( ) = = 0 {
// 1. Let valuesArray be CreateArrayFromList(values).
let values_array = crate ::builtins ::Array ::create_array_from_list (
values . into_inner ( ) ,
context ,
) ;
// 2. Perform ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »).
result_capability . resolve . call (
& JsValue ::undefined ( ) ,
& [ values_array . into ( ) ] ,
context ,
) ? ;
}
// iv. Return resultCapability.[[Promise]].
return Ok ( result_capability . promise . clone ( ) ) ;
}
Ok ( Some ( next ) ) = > {
// e. Let nextValue be Completion(IteratorValue(next)).
let next_value = next . value ( context ) ;
match next_value {
Err ( e ) = > {
// f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record . set_done ( true ) ;
// g. ReturnIfAbrupt(nextValue).
return Err ( e ) ;
}
Ok ( next_value ) = > next_value ,
}
}
} ;
// h. Append undefined to values.
values . borrow_mut ( ) . push ( JsValue ::Undefined ) ;
// i. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »).
let next_promise =
promise_resolve . call ( & constructor . clone ( ) . into ( ) , & [ next_value ] , context ) ? ;
// j. Let steps be the algorithm steps defined in Promise.all Resolve Element Functions.
// k. Let length be the number of non-optional parameters of the function definition in Promise.all Resolve Element Functions.
// l. Let onFulfilled be CreateBuiltinFunction(steps, length, "", « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »).
// m. Set onFulfilled.[[AlreadyCalled]] to false.
// n. Set onFulfilled.[[Index]] to index.
// o. Set onFulfilled.[[Values]] to values.
// p. Set onFulfilled.[[Capability]] to resultCapability.
// q. Set onFulfilled.[[RemainingElements]] to remainingElementsCount.
let on_fulfilled = FunctionBuilder ::closure_with_captures (
context ,
| _ , args , captures , context | {
// https://tc39.es/ecma262/#sec-promise.all-resolve-element-functions
// 1. Let F be the active function object.
// 2. If F.[[AlreadyCalled]] is true, return undefined.
if captures . already_called . get ( ) {
return Ok ( JsValue ::undefined ( ) ) ;
}
// 3. Set F.[[AlreadyCalled]] to true.
captures . already_called . set ( true ) ;
// 4. Let index be F.[[Index]].
// 5. Let values be F.[[Values]].
// 6. Let promiseCapability be F.[[Capability]].
// 7. Let remainingElementsCount be F.[[RemainingElements]].
// 8. Set values[index] to x.
captures . values . borrow_mut ( ) [ captures . index ] = args . get_or_undefined ( 0 ) . clone ( ) ;
// 9. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
captures
. remaining_elements_count
. set ( captures . remaining_elements_count . get ( ) - 1 ) ;
// 10. If remainingElementsCount.[[Value]] is 0, then
if captures . remaining_elements_count . get ( ) = = 0 {
// a. Let valuesArray be CreateArrayFromList(values).
let values_array = crate ::builtins ::Array ::create_array_from_list (
captures . values . clone ( ) . into_inner ( ) ,
context ,
) ;
// b. Return ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »).
return captures . capability_resolve . call (
& JsValue ::undefined ( ) ,
& [ values_array . into ( ) ] ,
context ,
) ;
}
// 11. Return undefined.
Ok ( JsValue ::undefined ( ) )
} ,
ResolveElementCaptures {
already_called : Rc ::new ( Cell ::new ( false ) ) ,
index ,
values : values . clone ( ) ,
capability_resolve : result_capability . resolve . clone ( ) ,
remaining_elements_count : remaining_elements_count . clone ( ) ,
} ,
)
. name ( "" )
. length ( 1 )
. constructor ( false )
. build ( ) ;
// r. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] + 1.
remaining_elements_count . set ( remaining_elements_count . get ( ) + 1 ) ;
// s. Perform ? Invoke(nextPromise, "then", « onFulfilled, resultCapability.[[Reject]] »).
next_promise . invoke (
"then" ,
& [ on_fulfilled . into ( ) , result_capability . reject . clone ( ) . into ( ) ] ,
context ,
) ? ;
// t. Set index to index + 1.
index + = 1 ;
}
}
/// `CreateResolvingFunctions ( promise )`
///
/// More information:
@ -670,7 +912,7 @@ impl Promise {
& mut iterator_record ,
c ,
& promise_capability ,
& promise_resolve ,
& promise_resolve . into ( ) ,
context ,
) ;
@ -1185,16 +1427,16 @@ impl Promise {
fn get_promise_resolve (
promise_constructor : & JsObject ,
context : & mut Context ,
) -> JsResult < JsValue > {
) -> JsResult < JsObject > {
// 1. Let promiseResolve be ? Get(promiseConstructor, "resolve").
let promise_resolve = promise_constructor . get ( "resolve" , context ) ? ;
// 2. If IsCallable(promiseResolve) is false, throw a TypeError exception.
if ! promise_resolve . is_callable ( ) {
return context . throw_type_error ( "retrieving a non-callable promise resolver" ) ;
if let Some ( promise_resolve ) = promise_resolve . as_callable ( ) {
// 3. Return promiseResolve.
Ok ( promise_resolve . clone ( ) )
} else {
context . throw_type_error ( "retrieving a non-callable promise resolver" )
}
// 3. Return promiseResolve.
Ok ( promise_resolve )
}
}