/* Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ecma-alloc.h" #include "ecma-array-object.h" #include "ecma-builtin-helpers.h" #include "ecma-builtin-handlers.h" #include "ecma-exceptions.h" #include "ecma-function-object.h" #include "ecma-gc.h" #include "ecma-globals.h" #include "ecma-iterator-object.h" #include "ecma-number-object.h" #include "ecma-promise-object.h" #include "jcontext.h" #if JERRY_BUILTIN_PROMISE #define ECMA_BUILTINS_INTERNAL #include "ecma-builtins-internal.h" /** * This object has a custom dispatch function. */ #define BUILTIN_CUSTOM_DISPATCH /** * List of built-in routine identifiers. */ enum { ECMA_PROMISE_ROUTINE_START = 0, ECMA_PROMISE_ROUTINE_REJECT, ECMA_PROMISE_ROUTINE_RESOLVE, ECMA_PROMISE_ROUTINE_RACE, ECMA_PROMISE_ROUTINE_ALL, ECMA_PROMISE_ROUTINE_ALLSETTLED, ECMA_PROMISE_ROUTINE_SPECIES_GET }; #define BUILTIN_INC_HEADER_NAME "ecma-builtin-promise.inc.h" #define BUILTIN_UNDERSCORED_ID promise #include "ecma-builtin-internal-routines-template.inc.h" /** \addtogroup ecma ECMA * @{ * * \addtogroup ecmabuiltins * @{ * * \addtogroup promise ECMA Promise object built-in * @{ */ /** * Reject the promise if the value is error. * * See also: * ES2015 25.4.1.1.1 * * @return ecma value of the new promise. * Returned value must be freed with ecma_free_value. */ static inline ecma_value_t ecma_builtin_promise_reject_abrupt (ecma_value_t value, /**< value */ ecma_object_t *capability_obj_p) /**< capability */ { JERRY_ASSERT (ecma_object_class_is (capability_obj_p, LIT_INTERNAL_MAGIC_PROMISE_CAPABILITY)); if (!ECMA_IS_VALUE_ERROR (value)) { return value; } ecma_value_t reason = jcontext_take_exception (); ecma_promise_capabality_t *capability_p = (ecma_promise_capabality_t *) capability_obj_p; ecma_value_t call_ret = ecma_op_function_call (ecma_get_object_from_value (capability_p->reject), ECMA_VALUE_UNDEFINED, &reason, 1); ecma_free_value (reason); if (ECMA_IS_VALUE_ERROR (call_ret)) { return call_ret; } ecma_free_value (call_ret); return ecma_copy_value (capability_p->header.u.class_prop.u.promise); } /* ecma_builtin_promise_reject_abrupt */ /** * Runtime Semantics: PerformPromiseRace. * * See also: * ES2020 25.6.4.4.1 * * @return ecma value of the new promise. * Returned value must be freed with ecma_free_value. */ static inline ecma_value_t ecma_builtin_promise_perform_race (ecma_value_t iterator, /**< the iterator for race */ ecma_value_t next_method, /**< next method */ ecma_object_t *capability_obj_p, /**< PromiseCapability record */ ecma_value_t ctor, /**< Constructor value */ ecma_value_t resolve, /** the resolve of Promise.all */ bool *done_p) /**< [out] iteratorRecord[[done]] */ { JERRY_ASSERT (ecma_is_value_object (iterator)); JERRY_ASSERT (ecma_object_class_is (capability_obj_p, LIT_INTERNAL_MAGIC_PROMISE_CAPABILITY)); JERRY_ASSERT (ecma_is_constructor (ctor)); ecma_promise_capabality_t *capability_p = (ecma_promise_capabality_t *) capability_obj_p; ecma_object_t *resolve_func_p = ecma_get_object_from_value (resolve); ecma_value_t ret_value = ECMA_VALUE_ERROR; /* 5. */ while (true) { /* a. */ ecma_value_t next = ecma_op_iterator_step (iterator, next_method); /* b, c. */ if (ECMA_IS_VALUE_ERROR (next)) { goto done; } /* d. */ if (ecma_is_value_false (next)) { /* ii. */ ret_value = ecma_copy_value (capability_p->header.u.class_prop.u.promise); goto done; } /* e. */ ecma_value_t next_val = ecma_op_iterator_value (next); ecma_free_value (next); /* f, g. */ if (ECMA_IS_VALUE_ERROR (next_val)) { goto done; } /* h. */ ecma_value_t next_promise = ecma_op_function_call (resolve_func_p, ctor, &next_val, 1); ecma_free_value (next_val); if (ECMA_IS_VALUE_ERROR (next_promise)) { goto exit; } /* i. */ ecma_value_t args[2] = {capability_p->resolve, capability_p->reject}; ecma_value_t result = ecma_op_invoke_by_magic_id (next_promise, LIT_MAGIC_STRING_THEN, args, 2); ecma_free_value (next_promise); if (ECMA_IS_VALUE_ERROR (result)) { goto exit; } ecma_free_value (result); } done: *done_p = true; exit: return ret_value; } /* ecma_builtin_promise_perform_race */ /** * Runtime Semantics: PerformPromise all or allSettled. * * See also: * ES2020 25.6.4.1.1 * * @return ecma value of the new promise. * Returned value must be freed with ecma_free_value. */ static inline ecma_value_t ecma_builtin_promise_perform_all_or_all_settled (ecma_value_t iterator, /**< iteratorRecord */ ecma_value_t next_method, /**< next method */ ecma_object_t *capability_obj_p, /**< PromiseCapability record */ ecma_value_t ctor, /**< the caller of Promise.all */ ecma_value_t resolve, /** the resolve of Promise.all */ uint8_t builtin_routine_id, /**< built-in wide routine * identifier */ bool *done_p) /**< [out] iteratorRecord[[done]] */ { /* 1. - 2. */ JERRY_ASSERT (ecma_object_class_is (capability_obj_p, LIT_INTERNAL_MAGIC_PROMISE_CAPABILITY)); JERRY_ASSERT (ecma_is_constructor (ctor)); ecma_promise_capabality_t *capability_p = (ecma_promise_capabality_t *) capability_obj_p; ecma_object_t *resolve_func_p = ecma_get_object_from_value (resolve); /* 3. */ ecma_object_t *values_array_obj_p = ecma_op_new_array_object (0); ecma_value_t values_array = ecma_make_object_value (values_array_obj_p); /* 4. */ ecma_value_t remaining = ecma_op_create_number_object (ecma_make_integer_value (1)); /* 5. */ uint32_t idx = 0; ecma_value_t ret_value = ECMA_VALUE_ERROR; /* 6. */ while (true) { /* a. */ ecma_value_t next = ecma_op_iterator_step (iterator, next_method); /* b. - c. */ if (ECMA_IS_VALUE_ERROR (next)) { goto done; } /* d. */ if (ecma_is_value_false (next)) { /* ii. - iii. */ if (ecma_promise_remaining_inc_or_dec (remaining, false) == 0) { /* 2. */ ecma_value_t resolve_result = ecma_op_function_call (ecma_get_object_from_value (capability_p->resolve), ECMA_VALUE_UNDEFINED, &values_array, 1); /* 3. */ if (ECMA_IS_VALUE_ERROR (resolve_result)) { goto done; } ecma_free_value (resolve_result); } /* iv. */ ret_value = ecma_copy_value (capability_p->header.u.class_prop.u.promise); goto done; } /* e. */ ecma_value_t next_value = ecma_op_iterator_value (next); ecma_free_value (next); /* f. - g. */ if (ECMA_IS_VALUE_ERROR (next_value)) { goto done; } /* h. */ ecma_builtin_helper_def_prop_by_index (values_array_obj_p, idx, ECMA_VALUE_UNDEFINED, ECMA_PROPERTY_CONFIGURABLE_ENUMERABLE_WRITABLE); /* i. */ ecma_value_t next_promise = ecma_op_function_call (resolve_func_p, ctor, &next_value, 1); ecma_free_value (next_value); /* j. */ if (ECMA_IS_VALUE_ERROR (next_promise)) { goto exit; } if (JERRY_UNLIKELY (idx == UINT32_MAX - 1)) { ecma_raise_range_error (ECMA_ERR_MSG ("Promise.all remaining elements limit reached")); goto exit; } /* k. */ ecma_object_t *executor_func_p = ecma_op_create_native_handler (ECMA_NATIVE_HANDLER_PROMISE_ALL_HELPER, sizeof (ecma_promise_all_executor_t)); ecma_promise_all_executor_t *executor_p = (ecma_promise_all_executor_t *) executor_func_p; /* m. + t. */ executor_p->index = ++idx; /* n. */ executor_p->values = values_array; /* o. */ executor_p->capability = ecma_make_object_value (capability_obj_p); /* p. */ executor_p->remaining_elements = remaining; executor_p->header.u.class_prop.extra_info = ECMA_PROMISE_ALL_RESOLVE; if (builtin_routine_id == ECMA_PROMISE_ROUTINE_ALLSETTLED) { executor_p->header.u.class_prop.extra_info = ECMA_PROMISE_ALLSETTLED_RESOLVE; } ecma_value_t args[2]; args[0] = ecma_make_object_value (executor_func_p); /* q. */ ecma_promise_remaining_inc_or_dec (remaining, true); ecma_value_t result; if (builtin_routine_id == ECMA_PROMISE_ROUTINE_ALLSETTLED) { ecma_object_t *reject_func_p = ecma_op_create_native_handler (ECMA_NATIVE_HANDLER_PROMISE_ALL_HELPER, sizeof (ecma_promise_all_executor_t)); ecma_promise_all_executor_t *reject_p = (ecma_promise_all_executor_t *) reject_func_p; reject_p->index = idx; reject_p->values = values_array; reject_p->capability = ecma_make_object_value (capability_obj_p); reject_p->remaining_elements = remaining; reject_p->header.u.class_prop.extra_info = ECMA_PROMISE_ALLSETTLED_REJECT; args[1] = ecma_make_object_value (reject_func_p); result = ecma_op_invoke_by_magic_id (next_promise, LIT_MAGIC_STRING_THEN, args, 2); ecma_free_value (next_promise); ecma_deref_object (executor_func_p); ecma_deref_object (reject_func_p); } else { args[1] = capability_p->reject; result = ecma_op_invoke_by_magic_id (next_promise, LIT_MAGIC_STRING_THEN, args, 2); ecma_free_value (next_promise); ecma_deref_object (executor_func_p); } /* s. */ if (ECMA_IS_VALUE_ERROR (result)) { goto exit; } ecma_free_value (result); } done: *done_p = true; exit: ecma_free_value (remaining); ecma_deref_object (values_array_obj_p); return ret_value; } /* ecma_builtin_promise_perform_all_or_all_settled */ /** * The common function for Promise.race, Promise.all and Promise.allSettled. * * @return ecma value of the new promise. * Returned value must be freed with ecma_free_value. */ static ecma_value_t ecma_builtin_promise_helper (ecma_value_t this_arg, /**< 'this' argument */ ecma_value_t iterable, /**< the items to be resolved */ uint8_t builtin_routine_id) /**< built-in wide routine * identifier */ { ecma_object_t *capability_obj_p = ecma_promise_new_capability (this_arg, ECMA_VALUE_UNDEFINED); if (JERRY_UNLIKELY (capability_obj_p == NULL)) { return ECMA_VALUE_ERROR; } ecma_value_t resolve = ecma_op_object_get_by_magic_id (ecma_get_object_from_value (this_arg), LIT_MAGIC_STRING_RESOLVE); if (ECMA_IS_VALUE_ERROR (resolve)) { resolve = ecma_builtin_promise_reject_abrupt (resolve, capability_obj_p); ecma_deref_object (capability_obj_p); return resolve; } if (!ecma_op_is_callable (resolve)) { ecma_free_value (resolve); ecma_raise_type_error (ECMA_ERR_MSG ("Resolve method must be callable")); resolve = ecma_builtin_promise_reject_abrupt (ECMA_VALUE_ERROR, capability_obj_p); ecma_deref_object (capability_obj_p); return resolve; } ecma_value_t next_method; ecma_value_t iterator = ecma_op_get_iterator (iterable, ECMA_VALUE_SYNC_ITERATOR, &next_method); iterator = ecma_builtin_promise_reject_abrupt (iterator, capability_obj_p); if (ECMA_IS_VALUE_ERROR (iterator)) { ecma_deref_object (capability_obj_p); return iterator; } ecma_value_t ret = ECMA_VALUE_EMPTY; bool is_done = false; if (builtin_routine_id == ECMA_PROMISE_ROUTINE_RACE) { ret = ecma_builtin_promise_perform_race (iterator, next_method, capability_obj_p, this_arg, resolve, &is_done); } else { ret = ecma_builtin_promise_perform_all_or_all_settled (iterator, next_method, capability_obj_p, this_arg, resolve, builtin_routine_id, &is_done); } if (ECMA_IS_VALUE_ERROR (ret)) { if (!is_done) { ret = ecma_op_iterator_close (iterator); } ret = ecma_builtin_promise_reject_abrupt (ret, capability_obj_p); } ecma_free_value (iterator); ecma_free_value (next_method); ecma_free_value (resolve); ecma_deref_object (capability_obj_p); return ret; } /* ecma_builtin_promise_helper */ /** * Handle calling [[Call]] of built-in Promise object. * * ES2015 25.4.3 Promise is not intended to be called * as a function and will throw an exception when called * in that manner. * * @return ecma value */ ecma_value_t ecma_builtin_promise_dispatch_call (const ecma_value_t *arguments_list_p, /**< arguments list */ uint32_t arguments_list_len) /**< number of arguments */ { JERRY_ASSERT (arguments_list_len == 0 || arguments_list_p != NULL); return ecma_raise_type_error (ECMA_ERR_MSG ("Constructor Promise requires 'new'")); } /* ecma_builtin_promise_dispatch_call */ /** * Handle calling [[Construct]] of built-in Promise object. * * @return ecma value */ ecma_value_t ecma_builtin_promise_dispatch_construct (const ecma_value_t *arguments_list_p, /**< arguments list */ uint32_t arguments_list_len) /**< number of arguments */ { JERRY_ASSERT (arguments_list_len == 0 || arguments_list_p != NULL); if (arguments_list_len == 0 || !ecma_op_is_callable (arguments_list_p[0])) { return ecma_raise_type_error (ECMA_ERR_MSG ("First parameter must be callable")); } return ecma_op_create_promise_object (arguments_list_p[0], ECMA_VALUE_UNDEFINED, JERRY_CONTEXT (current_new_target_p)); } /* ecma_builtin_promise_dispatch_construct */ /** * Dispatcher of the built-in's routines * * @return ecma value * Returned value must be freed with ecma_free_value. */ ecma_value_t ecma_builtin_promise_dispatch_routine (uint8_t builtin_routine_id, /**< built-in wide routine * identifier */ ecma_value_t this_arg, /**< 'this' argument value */ const ecma_value_t arguments_list_p[], /**< list of arguments * passed to routine */ uint32_t arguments_number) /**< length of arguments' list */ { ecma_value_t argument = (arguments_number > 0) ? arguments_list_p[0] : ECMA_VALUE_UNDEFINED; switch (builtin_routine_id) { case ECMA_PROMISE_ROUTINE_REJECT: case ECMA_PROMISE_ROUTINE_RESOLVE: { bool is_resolve = (builtin_routine_id == ECMA_PROMISE_ROUTINE_RESOLVE); return ecma_promise_reject_or_resolve (this_arg, argument, is_resolve); } case ECMA_PROMISE_ROUTINE_RACE: case ECMA_PROMISE_ROUTINE_ALL: case ECMA_PROMISE_ROUTINE_ALLSETTLED: { return ecma_builtin_promise_helper (this_arg, argument, builtin_routine_id); } case ECMA_PROMISE_ROUTINE_SPECIES_GET: { return ecma_copy_value (this_arg); } default: { JERRY_UNREACHABLE (); } } } /* ecma_builtin_promise_dispatch_routine */ /** * @} * @} * @} */ #endif /* JERRY_BUILTIN_PROMISE */