/* 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-boolean-object.h" #include "ecma-builtins.h" #include "ecma-exceptions.h" #include "ecma-function-object.h" #include "ecma-gc.h" #include "ecma-globals.h" #include "ecma-helpers.h" #include "ecma-jobqueue.h" #include "ecma-objects.h" #include "ecma-objects-general.h" #include "ecma-promise-object.h" #include "jcontext.h" #if ENABLED (JERRY_BUILTIN_PROMISE) /** \addtogroup ecma ECMA * @{ * * \addtogroup ecmapromiseobject ECMA Promise object related routines * @{ */ /** * Check if an object is promise. * * @return true - if the object is a promise. * false - otherwise. */ inline bool JERRY_ATTR_ALWAYS_INLINE ecma_is_promise (ecma_object_t *obj_p) /**< points to object */ { return ecma_object_class_is (obj_p, LIT_MAGIC_STRING_PROMISE_UL); } /* ecma_is_promise */ /** * Get the result of the promise. * * @return ecma value of the promise result. * Returned value must be freed with ecma_free_value */ ecma_value_t ecma_promise_get_result (ecma_object_t *obj_p) /**< points to promise object */ { JERRY_ASSERT (ecma_is_promise (obj_p)); ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) obj_p; return ecma_copy_value (ext_object_p->u.class_prop.u.value); } /* ecma_promise_get_result */ /** * Set the PromiseResult of promise. */ static inline void JERRY_ATTR_ALWAYS_INLINE ecma_promise_set_result (ecma_object_t *obj_p, /**< points to promise object */ ecma_value_t result) /**< the result value */ { JERRY_ASSERT (ecma_is_promise (obj_p)); ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) obj_p; JERRY_ASSERT (ext_object_p->u.class_prop.u.value == ECMA_VALUE_UNDEFINED); ext_object_p->u.class_prop.u.value = result; } /* ecma_promise_set_result */ /** * Get the PromiseState of promise. * * @return the state's enum value */ uint16_t ecma_promise_get_flags (ecma_object_t *obj_p) /**< points to promise object */ { JERRY_ASSERT (ecma_is_promise (obj_p)); return ((ecma_extended_object_t *) obj_p)->u.class_prop.extra_info; } /* ecma_promise_get_flags */ /** * Set the PromiseState of promise. */ static inline void JERRY_ATTR_ALWAYS_INLINE ecma_promise_set_state (ecma_object_t *obj_p, /**< points to promise object */ bool is_fulfilled) /**< new flags */ { JERRY_ASSERT (ecma_is_promise (obj_p)); JERRY_ASSERT (ecma_promise_get_flags (obj_p) & ECMA_PROMISE_IS_PENDING); uint16_t flags_to_invert = (is_fulfilled ? (ECMA_PROMISE_IS_PENDING | ECMA_PROMISE_IS_FULFILLED) : ECMA_PROMISE_IS_PENDING); ((ecma_extended_object_t *) obj_p)->u.class_prop.extra_info ^= flags_to_invert; } /* ecma_promise_set_state */ /** * Take a collection of Reactions and enqueue a new PromiseReactionJob for each Reaction. * * See also: ES2015 25.4.1.8 */ static void ecma_promise_trigger_reactions (ecma_collection_t *reactions, /**< lists of reactions */ ecma_value_t value, /**< value for resolve or reject */ bool is_reject) /**< true if promise is rejected, false otherwise */ { ecma_value_t *buffer_p = reactions->buffer_p; ecma_value_t *buffer_end_p = buffer_p + reactions->item_count; while (buffer_p < buffer_end_p) { ecma_value_t object_with_tag = *buffer_p++; ecma_object_t *object_p = ECMA_GET_NON_NULL_POINTER_FROM_POINTER_TAG (ecma_object_t, object_with_tag); ecma_value_t object = ecma_make_object_value (object_p); if (JMEM_CP_GET_THIRD_BIT_FROM_POINTER_TAG (object_with_tag)) { ecma_enqueue_promise_async_reaction_job (object, value, is_reject); continue; } if (!is_reject) { ecma_value_t handler = ECMA_VALUE_TRUE; if (JMEM_CP_GET_FIRST_BIT_FROM_POINTER_TAG (object_with_tag)) { handler = *buffer_p++; } ecma_enqueue_promise_reaction_job (object, handler, value); } else if (JMEM_CP_GET_FIRST_BIT_FROM_POINTER_TAG (object_with_tag)) { buffer_p++; } if (is_reject) { ecma_value_t handler = ECMA_VALUE_FALSE; if (JMEM_CP_GET_SECOND_BIT_FROM_POINTER_TAG (object_with_tag)) { handler = *buffer_p++; } ecma_enqueue_promise_reaction_job (object, handler, value); } else if (JMEM_CP_GET_SECOND_BIT_FROM_POINTER_TAG (object_with_tag)) { buffer_p++; } } } /* ecma_promise_trigger_reactions */ /** * Checks whether a resolver is called before. * * @return true if it was called before, false otherwise */ static bool ecma_is_resolver_already_called (ecma_object_t *resolver_p, /**< resolver */ ecma_object_t *promise_obj_p) /**< promise */ { ecma_string_t *str_already_resolved_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_ALREADY_RESOLVED); ecma_property_t *property_p = ecma_find_named_property (resolver_p, str_already_resolved_p); if (property_p == NULL) { return (ecma_promise_get_flags (promise_obj_p) & ECMA_PROMISE_ALREADY_RESOLVED) != 0; } JERRY_ASSERT (ECMA_PROPERTY_GET_TYPE (*property_p) == ECMA_PROPERTY_TYPE_NAMEDDATA); ecma_value_t already_resolved = ECMA_PROPERTY_VALUE_PTR (property_p)->value; ecma_object_t *object_p = ecma_get_object_from_value (already_resolved); JERRY_ASSERT (ecma_get_object_type (object_p) == ECMA_OBJECT_TYPE_CLASS); ecma_extended_object_t *already_resolved_p = (ecma_extended_object_t *) object_p; JERRY_ASSERT (already_resolved_p->u.class_prop.class_id == LIT_MAGIC_STRING_BOOLEAN_UL); ecma_value_t current_value = already_resolved_p->u.class_prop.u.value; already_resolved_p->u.class_prop.u.value = ECMA_VALUE_TRUE; return current_value == ECMA_VALUE_TRUE; } /* ecma_is_resolver_already_called */ /** * Reject a Promise with a reason. * * See also: ES2015 25.4.1.7 */ void ecma_reject_promise (ecma_value_t promise, /**< promise */ ecma_value_t reason) /**< reason for reject */ { ecma_object_t *obj_p = ecma_get_object_from_value (promise); JERRY_ASSERT (ecma_promise_get_flags (obj_p) & ECMA_PROMISE_IS_PENDING); ecma_promise_set_state (obj_p, false); ecma_promise_set_result (obj_p, ecma_copy_value_if_not_object (reason)); ecma_promise_object_t *promise_p = (ecma_promise_object_t *) obj_p; /* GC can be triggered by ecma_new_collection so freeing the collection first and creating a new one might cause a heap after use event. */ ecma_collection_t *reactions = promise_p->reactions; /* Fulfill reactions will never be triggered. */ ecma_promise_trigger_reactions (reactions, reason, true); promise_p->reactions = ecma_new_collection (); ecma_collection_destroy (reactions); } /* ecma_reject_promise */ /** * Fulfill a Promise with a value. * * See also: ES2015 25.4.1.4 */ void ecma_fulfill_promise (ecma_value_t promise, /**< promise */ ecma_value_t value) /**< fulfilled value */ { ecma_object_t *obj_p = ecma_get_object_from_value (promise); JERRY_ASSERT (ecma_promise_get_flags (obj_p) & ECMA_PROMISE_IS_PENDING); if (promise == value) { ecma_raise_type_error (ECMA_ERR_MSG ("A promise cannot be resolved with itself.")); ecma_value_t exception = jcontext_take_exception (); ecma_reject_promise (promise, exception); ecma_free_value (exception); return; } if (ecma_is_value_object (value)) { ecma_value_t then = ecma_op_object_get_by_magic_id (ecma_get_object_from_value (value), LIT_MAGIC_STRING_THEN); if (ECMA_IS_VALUE_ERROR (then)) { then = jcontext_take_exception (); ecma_reject_promise (promise, then); ecma_free_value (then); return; } if (ecma_op_is_callable (then)) { ecma_enqueue_promise_resolve_thenable_job (promise, value, then); ecma_free_value (then); return; } ecma_free_value (then); } ecma_promise_set_state (obj_p, true); ecma_promise_set_result (obj_p, ecma_copy_value_if_not_object (value)); ecma_promise_object_t *promise_p = (ecma_promise_object_t *) obj_p; /* GC can be triggered by ecma_new_collection so freeing the collection first and creating a new one might cause a heap after use event. */ ecma_collection_t *reactions = promise_p->reactions; /* Reject reactions will never be triggered. */ ecma_promise_trigger_reactions (reactions, value, false); promise_p->reactions = ecma_new_collection (); ecma_collection_destroy (reactions); } /* ecma_fulfill_promise */ /** * Native handler for Promise Reject Function. * * See also: ES2015 25.4.1.3.1 * * @return ecma value of undefined. */ ecma_value_t ecma_promise_reject_handler (const ecma_value_t function, /**< the function itself */ const ecma_value_t this, /**< this_arg of the function */ const ecma_value_t argv[], /**< argument list */ const uint32_t argc) /**< argument number */ { JERRY_UNUSED (this); ecma_object_t *function_p = ecma_get_object_from_value (function); /* 2. */ ecma_value_t promise = ecma_op_object_get_by_magic_id (function_p, LIT_INTERNAL_MAGIC_STRING_PROMISE); /* 1. */ ecma_object_t *promise_obj_p = ecma_get_object_from_value (promise); JERRY_ASSERT (ecma_is_promise (promise_obj_p)); /* 3., 4. */ if (!ecma_is_resolver_already_called (function_p, promise_obj_p)) { /* 5. */ ((ecma_extended_object_t *) promise_obj_p)->u.class_prop.extra_info |= ECMA_PROMISE_ALREADY_RESOLVED; /* 6. */ ecma_value_t reject_value = (argc == 0) ? ECMA_VALUE_UNDEFINED : argv[0]; ecma_reject_promise (promise, reject_value); } ecma_free_value (promise); return ECMA_VALUE_UNDEFINED; } /* ecma_promise_reject_handler */ /** * Native handler for Promise Resolve Function. * * See also: ES2015 25.4.1.3.2 * * @return ecma value of undefined. */ ecma_value_t ecma_promise_resolve_handler (const ecma_value_t function, /**< the function itself */ const ecma_value_t this, /**< this_arg of the function */ const ecma_value_t argv[], /**< argument list */ const uint32_t argc) /**< argument number */ { JERRY_UNUSED (this); ecma_object_t *function_p = ecma_get_object_from_value (function); /* 2. */ ecma_value_t promise = ecma_op_object_get_by_magic_id (function_p, LIT_INTERNAL_MAGIC_STRING_PROMISE); /* 1. */ ecma_object_t *promise_obj_p = ecma_get_object_from_value (promise); JERRY_ASSERT (ecma_is_promise (promise_obj_p)); /* 3., 4. */ if (!ecma_is_resolver_already_called (function_p, promise_obj_p)) { /* 5. */ ((ecma_extended_object_t *) promise_obj_p)->u.class_prop.extra_info |= ECMA_PROMISE_ALREADY_RESOLVED; ecma_fulfill_promise (promise, (argc == 0) ? ECMA_VALUE_UNDEFINED : argv[0]); } ecma_free_value (promise); return ECMA_VALUE_UNDEFINED; } /* ecma_promise_resolve_handler */ /** * Helper function for PromiseCreateResovingFucntions. * * See also: ES2015 25.4.1.3 2. - 7. * * @return pointer to the resolving function */ static ecma_value_t ecma_promise_create_resolving_functions_helper (ecma_object_t *obj_p, /**< Promise Object */ ecma_external_handler_t handler_cb) /**< Callback handler */ { ecma_string_t *str_promise_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_PROMISE); ecma_object_t *func_obj_p = ecma_op_create_external_function_object (handler_cb); ecma_op_object_put (func_obj_p, str_promise_p, ecma_make_object_value (obj_p), false); return ecma_make_object_value (func_obj_p); } /* ecma_promise_create_resolving_functions_helper */ /** * Create a PromiseCreateResovingFucntions. * * See also: ES2015 25.4.1.3 * * @return pointer to the resolving functions */ void ecma_promise_create_resolving_functions (ecma_object_t *object_p, /**< the promise object */ ecma_promise_resolving_functions_t *funcs, /**< [out] resolving functions */ bool create_already_resolved) /**< create already resolved flag */ { /* 2. - 4. */ funcs->resolve = ecma_promise_create_resolving_functions_helper (object_p, ecma_promise_resolve_handler); /* 5. - 7. */ funcs->reject = ecma_promise_create_resolving_functions_helper (object_p, ecma_promise_reject_handler); if (!create_already_resolved) { return; } ecma_value_t already_resolved = ecma_op_create_boolean_object (ECMA_VALUE_FALSE); ecma_string_t *str_already_resolved_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_ALREADY_RESOLVED); ecma_property_value_t *value_p; value_p = ecma_create_named_data_property (ecma_get_object_from_value (funcs->resolve), str_already_resolved_p, ECMA_PROPERTY_FIXED, NULL); value_p->value = already_resolved; value_p = ecma_create_named_data_property (ecma_get_object_from_value (funcs->reject), str_already_resolved_p, ECMA_PROPERTY_FIXED, NULL); value_p->value = already_resolved; ecma_free_value (already_resolved); } /* ecma_promise_create_resolving_functions */ /** * Free the heap and the member of the resolving functions. */ void ecma_promise_free_resolving_functions (ecma_promise_resolving_functions_t *funcs) /**< points to the functions */ { ecma_free_value (funcs->resolve); ecma_free_value (funcs->reject); } /* ecma_promise_free_resolving_functions */ /** * Create a promise object. * * See also: ES2015 25.4.3.1 * * @return ecma value of the new promise object * Returned value must be freed with ecma_free_value */ ecma_value_t ecma_op_create_promise_object (ecma_value_t executor, /**< the executor function or object */ ecma_promise_executor_type_t type) /**< indicates the type of executor */ { JERRY_ASSERT (JERRY_CONTEXT (current_new_target) != NULL); /* 3. */ ecma_object_t *proto_p = ecma_op_get_prototype_from_constructor (JERRY_CONTEXT (current_new_target), ECMA_BUILTIN_ID_PROMISE_PROTOTYPE); if (JERRY_UNLIKELY (proto_p == NULL)) { return ECMA_VALUE_ERROR; } /* Calling ecma_new_collection might trigger a GC call, so this * allocation is performed before the object is constructed. */ ecma_collection_t *reactions = ecma_new_collection (); ecma_object_t *object_p = ecma_create_object (proto_p, sizeof (ecma_promise_object_t), ECMA_OBJECT_TYPE_CLASS); ecma_deref_object (proto_p); ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) object_p; ext_object_p->u.class_prop.class_id = LIT_MAGIC_STRING_PROMISE_UL; /* 5 */ ext_object_p->u.class_prop.extra_info = ECMA_PROMISE_IS_PENDING; ext_object_p->u.class_prop.u.value = ECMA_VALUE_UNDEFINED; ecma_promise_object_t *promise_object_p = (ecma_promise_object_t *) object_p; /* 6-7. */ promise_object_p->reactions = reactions; /* 8. */ ecma_promise_resolving_functions_t funcs; ecma_promise_create_resolving_functions (object_p, &funcs, false); ecma_string_t *str_resolve_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_RESOLVE_FUNCTION); ecma_string_t *str_reject_p = ecma_get_magic_string (LIT_INTERNAL_MAGIC_STRING_REJECT_FUNCTION); ecma_op_object_put (object_p, str_resolve_p, funcs.resolve, false); ecma_op_object_put (object_p, str_reject_p, funcs.reject, false); /* 9. */ ecma_value_t completion = ECMA_VALUE_UNDEFINED; if (type == ECMA_PROMISE_EXECUTOR_FUNCTION) { JERRY_ASSERT (ecma_op_is_callable (executor)); ecma_value_t argv[] = { funcs.resolve, funcs.reject }; completion = ecma_op_function_call (ecma_get_object_from_value (executor), ECMA_VALUE_UNDEFINED, argv, 2); } else { JERRY_ASSERT (type == ECMA_PROMISE_EXECUTOR_EMPTY); JERRY_UNUSED (executor); } ecma_value_t status = ECMA_VALUE_EMPTY; if (ECMA_IS_VALUE_ERROR (completion)) { /* 10.a. */ completion = jcontext_take_exception (); status = ecma_op_function_call (ecma_get_object_from_value (funcs.reject), ECMA_VALUE_UNDEFINED, &completion, 1); } ecma_promise_free_resolving_functions (&funcs); ecma_free_value (completion); /* 10.b. */ if (ECMA_IS_VALUE_ERROR (status)) { ecma_deref_object (object_p); return status; } /* 11. */ ecma_free_value (status); return ecma_make_object_value (object_p); } /* ecma_op_create_promise_object */ /** * Helper function for increase or decrease the remaining count. * * @return the current remaining count after increase or decrease. */ uint32_t ecma_promise_remaining_inc_or_dec (ecma_value_t remaining, /**< the remaining count */ bool is_inc) /**< whether to increase the count */ { JERRY_ASSERT (ecma_is_value_object (remaining)); ecma_object_t *remaining_p = ecma_get_object_from_value (remaining); ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) remaining_p; JERRY_ASSERT (ext_object_p->u.class_prop.class_id == LIT_MAGIC_STRING_NUMBER_UL); JERRY_ASSERT (ecma_is_value_integer_number (ext_object_p->u.class_prop.u.value)); uint32_t current = (uint32_t) ecma_get_integer_from_value (ext_object_p->u.class_prop.u.value); if (is_inc) { current++; } else { current--; } ext_object_p->u.class_prop.u.value = ecma_make_uint32_value (current); return current; } /* ecma_promise_remaining_inc_or_dec */ /** * Native handler for Promise.all Resolve Element Function. * * See also: * ES2015 25.4.4.1.2 * * @return ecma value of undefined. */ ecma_value_t ecma_promise_all_handler_cb (const ecma_value_t function_obj, /**< the function itself */ const ecma_value_t this_val, /**< this_arg of the function */ const ecma_value_t args_p[], /**< argument list */ const uint32_t args_count) /**< argument number */ { JERRY_UNUSED (this_val); JERRY_UNUSED (args_count); ecma_promise_all_executor_t *executor_p = (ecma_promise_all_executor_t *) ecma_get_object_from_value (function_obj); /* 1 - 2. */ if (executor_p->index == 0) { return ECMA_VALUE_UNDEFINED; } /* 8. */ ecma_op_object_put_by_index (ecma_get_object_from_value (executor_p->values), (uint32_t) (executor_p->index - 1), args_p[0], false); /* 3. */ executor_p->index = 0; /* 9-10. */ ecma_value_t ret = ECMA_VALUE_UNDEFINED; if (ecma_promise_remaining_inc_or_dec (executor_p->remaining_elements, false) == 0) { ecma_value_t capability = executor_p->capability; ecma_promise_capabality_t *capability_p = (ecma_promise_capabality_t *) ecma_get_object_from_value (capability); ret = ecma_op_function_call (ecma_get_object_from_value (capability_p->resolve), ECMA_VALUE_UNDEFINED, &executor_p->values, 1); } return ret; } /* ecma_promise_all_handler_cb */ /** * GetCapabilitiesExecutor Functions * * Checks and sets a promiseCapability's resolve and reject properties. * * See also: ES11 25.6.1.5.1 * * @return ECMA_VALUE_UNDEFINED or TypeError * returned value must be freed with ecma_free_value */ ecma_value_t ecma_op_get_capabilities_executor_cb (const ecma_value_t function_obj, /**< the function itself */ const ecma_value_t this_val, /**< this_arg of the function */ const ecma_value_t args_p[], /**< argument list */ const uint32_t args_count) /**< argument number */ { JERRY_UNUSED (this_val); /* 1. */ ecma_promise_capability_executor_t *executor_p; executor_p = (ecma_promise_capability_executor_t *) ecma_get_object_from_value (function_obj); /* 2-3. */ ecma_object_t *capability_obj_p = ecma_get_object_from_value (executor_p->capability); JERRY_ASSERT (ecma_object_class_is (capability_obj_p, LIT_INTERNAL_MAGIC_PROMISE_CAPABILITY)); ecma_promise_capabality_t *capability_p = (ecma_promise_capabality_t *) capability_obj_p; /* 4. */ if (!ecma_is_value_undefined (capability_p->resolve)) { return ecma_raise_type_error (ECMA_ERR_MSG ("Resolve must be undefined")); } /* 5. */ if (!ecma_is_value_undefined (capability_p->reject)) { return ecma_raise_type_error (ECMA_ERR_MSG ("Reject must be undefined")); } /* 6. */ capability_p->resolve = args_count > 0 ? args_p[0] : ECMA_VALUE_UNDEFINED; /* 7. */ capability_p->reject = args_count > 1 ? args_p[1] : ECMA_VALUE_UNDEFINED; /* 8. */ return ECMA_VALUE_UNDEFINED; } /* ecma_op_get_capabilities_executor_cb */ /** * Create a new PromiseCapability. * * See also: ES11 25.6.1.5 * * @return NULL - if the operation raises error * new PromiseCapability object - otherwise */ ecma_object_t * ecma_promise_new_capability (ecma_value_t constructor) { /* 1. */ if (!ecma_is_constructor (constructor)) { ecma_raise_type_error (ECMA_ERR_MSG ("Invalid capability")); return NULL; } ecma_object_t *constructor_obj_p = ecma_get_object_from_value (constructor); /* 3. */ ecma_object_t *capability_obj_p = ecma_create_object (ecma_builtin_get (ECMA_BUILTIN_ID_OBJECT_PROTOTYPE), sizeof (ecma_promise_capabality_t), ECMA_OBJECT_TYPE_CLASS); ecma_promise_capabality_t *capability_p = (ecma_promise_capabality_t *) capability_obj_p; capability_p->header.u.class_prop.class_id = LIT_INTERNAL_MAGIC_PROMISE_CAPABILITY; capability_p->header.u.class_prop.u.promise = ECMA_VALUE_UNDEFINED; capability_p->resolve = ECMA_VALUE_UNDEFINED; capability_p->reject = ECMA_VALUE_UNDEFINED; /* 4-5. */ ecma_object_t *executor_p = ecma_create_object (ecma_builtin_get (ECMA_BUILTIN_ID_FUNCTION_PROTOTYPE), sizeof (ecma_promise_capability_executor_t), ECMA_OBJECT_TYPE_EXTERNAL_FUNCTION); /* 6. */ ecma_promise_capability_executor_t *executor_func_p = (ecma_promise_capability_executor_t *) executor_p; executor_func_p->header.u.external_handler_cb = ecma_op_get_capabilities_executor_cb; executor_func_p->capability = ecma_make_object_value (capability_obj_p); /* 7. */ ecma_value_t executor = ecma_make_object_value (executor_p); ecma_value_t promise = ecma_op_function_construct (constructor_obj_p, constructor_obj_p, &executor, 1); ecma_deref_object (executor_p); if (ECMA_IS_VALUE_ERROR (promise)) { ecma_deref_object (capability_obj_p); return NULL; } /* 8. */ if (!ecma_op_is_callable (capability_p->resolve)) { ecma_free_value (promise); ecma_deref_object (capability_obj_p); ecma_raise_type_error (ECMA_ERR_MSG ("'resolve' parameter must be callable.")); return NULL; } /* 9. */ if (!ecma_op_is_callable (capability_p->reject)) { ecma_free_value (promise); ecma_deref_object (capability_obj_p); ecma_raise_type_error (ECMA_ERR_MSG ("'reject' parameter must be callable.")); return NULL; } /* 10. */ capability_p->header.u.class_prop.u.promise = promise; ecma_free_value (promise); /* 11. */ return capability_obj_p; } /* ecma_promise_new_capability */ /** * The common function for 'reject' and 'resolve'. * * @return ecma value * Returned value must be freed with ecma_free_value. */ ecma_value_t ecma_promise_reject_or_resolve (ecma_value_t this_arg, /**< "this" argument */ ecma_value_t value, /**< rejected or resolved value */ bool is_resolve) /**< the operation is resolve */ { if (!ecma_is_value_object (this_arg)) { return ecma_raise_type_error (ECMA_ERR_MSG ("'this' is not an object.")); } if (is_resolve && ecma_is_value_object (value) && ecma_is_promise (ecma_get_object_from_value (value))) { ecma_object_t *object_p = ecma_get_object_from_value (value); ecma_value_t constructor = ecma_op_object_get_by_magic_id (object_p, LIT_MAGIC_STRING_CONSTRUCTOR); if (ECMA_IS_VALUE_ERROR (constructor)) { return constructor; } /* The this_arg must be an object. */ bool is_same_value = (constructor == this_arg); ecma_free_value (constructor); if (is_same_value) { return ecma_copy_value (value); } } ecma_object_t *capability_obj_p = ecma_promise_new_capability (this_arg); if (JERRY_UNLIKELY (capability_obj_p == NULL)) { return ECMA_VALUE_ERROR; } ecma_promise_capabality_t *capability_p = (ecma_promise_capabality_t *) capability_obj_p; ecma_value_t func = is_resolve ? capability_p->resolve : capability_p->reject; ecma_value_t call_ret = ecma_op_function_call (ecma_get_object_from_value (func), ECMA_VALUE_UNDEFINED, &value, 1); if (ECMA_IS_VALUE_ERROR (call_ret)) { ecma_deref_object (capability_obj_p); return call_ret; } ecma_free_value (call_ret); ecma_value_t promise = ecma_copy_value (capability_p->header.u.class_prop.u.promise); ecma_deref_object (capability_obj_p); return promise; } /* ecma_promise_reject_or_resolve */ /** * It performs the "then" operation on promiFulfilled * and onRejected as its settlement actions. * * See also: 25.4.5.3.1 * * @return ecma value of the new promise object * Returned value must be freed with ecma_free_value */ static ecma_value_t ecma_promise_do_then (ecma_value_t promise, /**< the promise which call 'then' */ ecma_value_t on_fulfilled, /**< on_fulfilled function */ ecma_value_t on_rejected, /**< on_rejected function */ ecma_object_t *result_capability_obj_p) /**< promise capability */ { JERRY_ASSERT (ecma_object_class_is (result_capability_obj_p, LIT_INTERNAL_MAGIC_PROMISE_CAPABILITY)); ecma_promise_capabality_t *capability_p = (ecma_promise_capabality_t *) result_capability_obj_p; /* 3. boolean true indicates "indentity" */ if (!ecma_op_is_callable (on_fulfilled)) { on_fulfilled = ECMA_VALUE_TRUE; } /* 4. boolean false indicates "thrower" */ if (!ecma_op_is_callable (on_rejected)) { on_rejected = ECMA_VALUE_FALSE; } ecma_object_t *promise_obj_p = ecma_get_object_from_value (promise); ecma_promise_object_t *promise_p = (ecma_promise_object_t *) promise_obj_p; uint16_t flags = ecma_promise_get_flags (promise_obj_p); if (flags & ECMA_PROMISE_IS_PENDING) { /* 7. */ /* [ capability, (on_fulfilled), (on_rejected) ] */ ecma_value_t reaction_values[3]; ecma_value_t *reactions_p = reaction_values + 1; uint8_t tag = 0; if (on_fulfilled != ECMA_VALUE_TRUE) { tag |= JMEM_FIRST_TAG_BIT_MASK; *reactions_p++ = on_fulfilled; } if (on_rejected != ECMA_VALUE_FALSE) { tag |= JMEM_SECOND_TAG_BIT_MASK; *reactions_p++ = on_rejected; } ECMA_SET_NON_NULL_POINTER_TAG (reaction_values[0], result_capability_obj_p, tag); uint32_t value_count = (uint32_t) (reactions_p - reaction_values); ecma_collection_append (promise_p->reactions, reaction_values, value_count); } else if (flags & ECMA_PROMISE_IS_FULFILLED) { /* 8. */ ecma_value_t value = ecma_promise_get_result (promise_obj_p); ecma_enqueue_promise_reaction_job (ecma_make_object_value (result_capability_obj_p), on_fulfilled, value); ecma_free_value (value); } else { /* 9. */ ecma_value_t reason = ecma_promise_get_result (promise_obj_p); ecma_enqueue_promise_reaction_job (ecma_make_object_value (result_capability_obj_p), on_rejected, reason); ecma_free_value (reason); } /* 10. */ return ecma_copy_value (capability_p->header.u.class_prop.u.promise); } /* ecma_promise_do_then */ /** * The common function for ecma_builtin_promise_prototype_then * and ecma_builtin_promise_prototype_catch. * * @return ecma value of a new promise object. * Returned value must be freed with ecma_free_value. */ ecma_value_t ecma_promise_then (ecma_value_t promise, /**< the promise which call 'then' */ ecma_value_t on_fulfilled, /**< on_fulfilled function */ ecma_value_t on_rejected) /**< on_rejected function */ { if (!ecma_is_value_object (promise)) { return ecma_raise_type_error (ECMA_ERR_MSG ("'this' is not an object.")); } ecma_object_t *obj = ecma_get_object_from_value (promise); if (!ecma_is_promise (obj)) { return ecma_raise_type_error (ECMA_ERR_MSG ("'this' is not a Promise.")); } ecma_value_t species = ecma_op_species_constructor (obj, ECMA_BUILTIN_ID_PROMISE); if (ECMA_IS_VALUE_ERROR (species)) { return species; } ecma_object_t *result_capability_obj_p = ecma_promise_new_capability (species); ecma_free_value (species); if (JERRY_UNLIKELY (result_capability_obj_p == NULL)) { return ECMA_VALUE_ERROR; } ecma_value_t ret = ecma_promise_do_then (promise, on_fulfilled, on_rejected, result_capability_obj_p); ecma_deref_object (result_capability_obj_p); return ret; } /* ecma_promise_then */ /** * Definition of valueThunk function * * See also: * ES2020 25.6.5.3.1 step 8. * * @return ecma value */ ecma_value_t ecma_value_thunk_helper_cb (const ecma_value_t function_obj, /**< the function itself */ const ecma_value_t this_val, /**< this_arg of the function */ const ecma_value_t args_p[], /**< argument list */ const uint32_t args_count) /**< argument number */ { JERRY_UNUSED_3 (this_val, args_p, args_count); ecma_object_t *func_obj_p = ecma_get_object_from_value (function_obj); ecma_promise_value_thunk_t *value_thunk_obj_p = (ecma_promise_value_thunk_t *) func_obj_p; return ecma_copy_value (value_thunk_obj_p->value); } /* ecma_value_thunk_helper_cb */ /** * Definition of thrower function * * See also: * ES2020 25.6.5.3.2 step 8. * * @return ecma value */ ecma_value_t ecma_value_thunk_thrower_cb (const ecma_value_t function_obj, /**< the function itself */ const ecma_value_t this_val, /**< this_arg of the function */ const ecma_value_t args_p[], /**< argument list */ const uint32_t args_count) /**< argument number */ { JERRY_UNUSED_3 (this_val, args_p, args_count); ecma_object_t *func_obj_p = ecma_get_object_from_value (function_obj); ecma_promise_value_thunk_t *value_thunk_obj_p = (ecma_promise_value_thunk_t *) func_obj_p; jcontext_raise_exception (ecma_copy_value (value_thunk_obj_p->value)); return ECMA_VALUE_ERROR; } /* ecma_value_thunk_thrower_cb */ /** * Helper function for Then Finally and Catch Finally common parts * * See also: * ES2020 25.6.5.3.1 * ES2020 25.6.5.3.2 * * @return ecma value */ static ecma_value_t ecma_promise_than_catch_finally_helper (ecma_value_t function_obj, /**< the function itself */ ecma_external_handler_t ext_func_obj, /**< external function object */ ecma_value_t arg) /**< callback function argument */ { /* 2. */ ecma_object_t *func_obj_p = ecma_get_object_from_value (function_obj); ecma_promise_finally_function_t *finally_func_obj = (ecma_promise_finally_function_t *) func_obj_p; /* 3. */ JERRY_ASSERT (ecma_op_is_callable (finally_func_obj->on_finally)); /* 4. */ ecma_value_t result = ecma_op_function_call (ecma_get_object_from_value (finally_func_obj->on_finally), ECMA_VALUE_UNDEFINED, NULL, 0); if (ECMA_IS_VALUE_ERROR (result)) { return result; } /* 6. */ JERRY_ASSERT (ecma_is_constructor (finally_func_obj->constructor)); /* 7. */ ecma_value_t promise = ecma_promise_reject_or_resolve (finally_func_obj->constructor, result, true); ecma_free_value (result); if (ECMA_IS_VALUE_ERROR (promise)) { return promise; } /* 8. */ ecma_object_t *value_thunk_func_p; value_thunk_func_p = ecma_create_object (ecma_builtin_get (ECMA_BUILTIN_ID_FUNCTION_PROTOTYPE), sizeof (ecma_promise_value_thunk_t), ECMA_OBJECT_TYPE_EXTERNAL_FUNCTION); ecma_promise_value_thunk_t *value_thunk_func_obj = (ecma_promise_value_thunk_t *) value_thunk_func_p; value_thunk_func_obj->header.u.external_handler_cb = ext_func_obj; value_thunk_func_obj->value = ecma_copy_value_if_not_object (arg); /* 9. */ ecma_value_t value_thunk = ecma_make_object_value (value_thunk_func_p); ecma_value_t ret_value = ecma_op_invoke_by_magic_id (promise, LIT_MAGIC_STRING_THEN, &value_thunk, 1); ecma_free_value (promise); ecma_deref_object (value_thunk_func_p); return ret_value; } /* ecma_promise_than_catch_finally_helper */ /** * Definition of Then Finally Function * * See also: * ES2020 25.6.5.3.1 * * @return ecma value */ ecma_value_t ecma_promise_then_finally_cb (const ecma_value_t function_obj, /**< the function itself */ const ecma_value_t this_val, /**< this_arg of the function */ const ecma_value_t args_p[], /**< argument list */ const uint32_t args_count) /**< argument number */ { JERRY_UNUSED_2 (this_val, args_count); JERRY_ASSERT (args_count > 0); return ecma_promise_than_catch_finally_helper (function_obj, ecma_value_thunk_helper_cb, args_p[0]); } /* ecma_promise_then_finally_cb */ /** * Definition of Catch Finally Function * * See also: * ES2020 25.6.5.3.2 * * @return ecma value */ ecma_value_t ecma_promise_catch_finally_cb (const ecma_value_t function_obj, /**< the function itself */ const ecma_value_t this_val, /**< this_arg of the function */ const ecma_value_t args_p[], /**< argument list */ const uint32_t args_count) /**< argument number */ { JERRY_UNUSED_2 (this_val, args_count); JERRY_ASSERT (args_count > 0); return ecma_promise_than_catch_finally_helper (function_obj, ecma_value_thunk_thrower_cb, args_p[0]); } /* ecma_promise_catch_finally_cb */ /** * The common function for ecma_builtin_promise_prototype_finally * * @return ecma value of a new promise object. * Returned value must be freed with ecma_free_value. */ ecma_value_t ecma_promise_finally (ecma_value_t promise, /**< the promise which call 'finally' */ ecma_value_t on_finally) /**< on_finally function */ { /* 2. */ if (!ecma_is_value_object (promise)) { return ecma_raise_type_error (ECMA_ERR_MSG ("'this' is not an object.")); } ecma_object_t *obj = ecma_get_object_from_value (promise); /* 3. */ ecma_value_t species = ecma_op_species_constructor (obj, ECMA_BUILTIN_ID_PROMISE); if (ECMA_IS_VALUE_ERROR (species)) { return species; } /* 4. */ JERRY_ASSERT (ecma_is_constructor (species)); /* 5. */ if (!ecma_op_is_callable (on_finally)) { ecma_free_value (species); ecma_value_t invoke_args[2] = {on_finally, on_finally}; return ecma_op_invoke_by_magic_id (promise, LIT_MAGIC_STRING_THEN, invoke_args, 2); } /* 6. a,b */ ecma_object_t *then_finally_obj_p; then_finally_obj_p = ecma_create_object (ecma_builtin_get (ECMA_BUILTIN_ID_FUNCTION_PROTOTYPE), sizeof (ecma_promise_finally_function_t), ECMA_OBJECT_TYPE_EXTERNAL_FUNCTION); ecma_promise_finally_function_t *then_finally_func_obj = (ecma_promise_finally_function_t *) then_finally_obj_p; then_finally_func_obj->header.u.external_handler_cb = ecma_promise_then_finally_cb; /* 6.c */ then_finally_func_obj->constructor = species; /* 6.d*/ then_finally_func_obj->on_finally = on_finally; /* 6. e,f */ ecma_object_t *catch_finally_obj_p; catch_finally_obj_p = ecma_create_object (ecma_builtin_get (ECMA_BUILTIN_ID_FUNCTION_PROTOTYPE), sizeof (ecma_promise_finally_function_t), ECMA_OBJECT_TYPE_EXTERNAL_FUNCTION); ecma_promise_finally_function_t *catch_finally_func_obj = (ecma_promise_finally_function_t *) catch_finally_obj_p; catch_finally_func_obj->header.u.external_handler_cb = ecma_promise_catch_finally_cb; /* 6.g */ catch_finally_func_obj->constructor = species; /* 6.h */ catch_finally_func_obj->on_finally = on_finally; ecma_free_value (species); /* 7. */ ecma_value_t invoke_args[2] = { ecma_make_object_value (then_finally_obj_p), ecma_make_object_value (catch_finally_obj_p) }; ecma_value_t ret_value = ecma_op_invoke_by_magic_id (promise, LIT_MAGIC_STRING_THEN, invoke_args, 2); ecma_deref_object (then_finally_obj_p); ecma_deref_object (catch_finally_obj_p); return ret_value; } /* ecma_promise_finally */ /** * Resume the execution of an async function after the promise is resolved */ void ecma_promise_async_then (ecma_value_t promise, /**< promise object */ ecma_value_t executable_object) /**< executable object of the async function */ { ecma_object_t *promise_obj_p = ecma_get_object_from_value (promise); uint16_t flags = ecma_promise_get_flags (promise_obj_p); if (flags & ECMA_PROMISE_IS_PENDING) { ecma_value_t executable_object_with_tag; ECMA_SET_NON_NULL_POINTER_TAG (executable_object_with_tag, ecma_get_object_from_value (executable_object), 0); ECMA_SET_THIRD_BIT_TO_POINTER_TAG (executable_object_with_tag); ecma_collection_push_back (((ecma_promise_object_t *) promise_obj_p)->reactions, executable_object_with_tag); return; } ecma_value_t value = ecma_promise_get_result (promise_obj_p); ecma_enqueue_promise_async_reaction_job (executable_object, value, !(flags & ECMA_PROMISE_IS_FULFILLED)); ecma_free_value (value); } /* ecma_promise_async_then */ /** * Resolves the value and resume the execution of an async function after the resolve is completed * * @return ECMA_VALUE_UNDEFINED if not error is occured, an error otherwise */ ecma_value_t ecma_promise_async_await (ecma_extended_object_t *async_generator_object_p, /**< async generator function */ ecma_value_t value) /**< value to be resolved (takes the reference) */ { ecma_value_t promise = ecma_make_object_value (ecma_builtin_get (ECMA_BUILTIN_ID_PROMISE)); ecma_value_t result = ecma_promise_reject_or_resolve (promise, value, true); ecma_free_value (value); if (ECMA_IS_VALUE_ERROR (result)) { return result; } ecma_promise_async_then (result, ecma_make_object_value ((ecma_object_t *) async_generator_object_p)); ecma_free_value (result); return ECMA_VALUE_UNDEFINED; } /* ecma_promise_async_await */ /** * @} * @} */ #endif /* ENABLED (JERRY_BUILTIN_PROMISE) */