From eebbed143d74fa59c51ca483ac342405266cd945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20G=C3=A1l?= Date: Mon, 28 Oct 2019 14:07:37 +0100 Subject: [PATCH] Introduce new Promise API methods (#3186) The new API methods make it possible to get a Promise object's result and it's state. JerryScript-DCO-1.0-Signed-off-by: Peter Gal pgal.u-szeged@partner.samsung.com --- docs/02.API-REFERENCE.md | 154 +++++++++++++ jerry-core/api/jerry.c | 62 ++++++ .../ecma/operations/ecma-promise-object.c | 4 +- .../ecma/operations/ecma-promise-object.h | 2 + jerry-core/include/jerryscript-core.h | 16 +- tests/unit-core/test-api-promise.c | 205 ++++++++++++++++++ 6 files changed, 440 insertions(+), 3 deletions(-) create mode 100644 tests/unit-core/test-api-promise.c diff --git a/docs/02.API-REFERENCE.md b/docs/02.API-REFERENCE.md index c9fa310ae..01f04aafb 100644 --- a/docs/02.API-REFERENCE.md +++ b/docs/02.API-REFERENCE.md @@ -595,6 +595,22 @@ typedef jerry_value_t (*jerry_vm_exec_stop_callback_t) (void *user_p); - [jerry_set_vm_exec_stop_callback](#jerry_set_vm_exec_stop_callback) +## jerry_promise_state_t + +Enum which describes the state of a Promise. + +Possible values: + + - JERRY_PROMISE_STATE_NONE - Invalid/Unknown state (possibly called on a non-promise object). + - JERRY_PROMISE_STATE_PENDING - Promise is in "Pending" state. + - JERRY_PROMISE_STATE_FULFILLED - Promise is in "Fulfilled" state. + - JERRY_PROMISE_STATE_REJECTED - Promise is in "Rejected" state. + +*New in version [NEXT VERSION]*. + +**See also** + +- [jerry_get_promise_result](#jerry_get_promise_result) ## jerry_typedarray_type_t @@ -3231,6 +3247,144 @@ jerry_value_to_string (const jerry_value_t value); These APIs all depend on the ES2015-subset profile (or on some build options). +## jerry_get_promise_result + +**Summary** + +The function returns the result of a Promise object. + +*Notes*: +- Returned value must be freed with [jerry_release_value](#jerry_release_value) when it + is no longer needed. +- This API depends on a build option (`JERRY_ES2015_BUILTIN_PROMISE`) and can be checked + in runtime with the `JERRY_FEATURE_PROMISE` feature enum value, + see: [jerry_is_feature_enabled](#jerry_is_feature_enabled). +- The ES2015-subset profile enables this by default. + + +**Prototype** + +```c +jerry_value_t +jerry_get_promise_result (const jerry_value_t promise); +``` + +- `promise` - the input Promise object. +- return + - The result of the Promise. + - If the Promise is not resolved yet the result is the 'undefined' value. + - A TypeError is returned if the input argument was not a Promise object or + the Promise support was not built into the library. + +*New in version [NEXT VERSION]*. + +**Example** + +[doctest]: # (test="compile") + +```c +#include + +static void +example (void) +{ + // acquire/create a promise object. + jerry_value_t promise = jerry_create_promise (); + { + // prepare the argumnent for the resolve or reject. + jerry_value_t argument = jerry_create_number (33); + + jerry_value_t is_ok = jerry_resolve_or_reject_promise (promise, + argument, + true); + // 'is_ok' should be checked if it is an error or not. + // skipped in this example + jerry_release_value (is_ok); + jerry_release_value (argument); + } + + jerry_value_t promise_result = jerry_get_promise_result (promise); + // 'promise_result' is now the number 33. + + jerry_release_value (promise_result); + jerry_release_value (promise); +} +``` + +**See also** + +- [jerry_create_promise](#jerry_create_promise) +- [jerry_promise_state_t](#jerry_promise_state_t) + +## jerry_get_promise_state + +**Summary** + +*Notes*: +- Returned value must be freed with [jerry_release_value](#jerry_release_value) when it + is no longer needed. +- This API depends on a build option (`JERRY_ES2015_BUILTIN_PROMISE`) and can be checked + in runtime with the `JERRY_FEATURE_PROMISE` feature enum value, + see: [jerry_is_feature_enabled](#jerry_is_feature_enabled). +- The ES2015-subset profile enables this by default. + + +**Prototype** + +```c +jerry_promise_state_t +jerry_get_promise_state (const jerry_value_t promise); +``` + +- `promise` - the input promise object. +- return + - [jerry_promise_state_t](#jerry_promise_state_t) + - `JERRY_PROMISE_STATE_NONE` is returned if the input argument was not a promise object or + the Promise support was not built into the library. + +*New in version [NEXT VERSION]*. + +**Example** + +[doctest]: # (test="compile") + +```c +#include + +static void +example (void) +{ + // acquire/create a promise object. + jerry_value_t promise = jerry_create_promise (); + + jerry_promise_state_t start_state = jerry_get_promise_state (promise); + // a Promise have a default state of JERRY_PROMISE_STATE_PENDING + + { + // prepare the argumnent for the resolve or reject. + jerry_value_t argument = jerry_create_number (33); + + jerry_value_t is_ok = jerry_resolve_or_reject_promise (promise, + argument, + true); + // 'is_ok' should be checked if it is an error or not. + // skipped in this example + jerry_release_value (is_ok); + jerry_release_value (argument); + } + + jerry_promise_state_t current_state = jerry_get_promise_state (promise); + // at this point the Promise should be in the JERRY_PROMISE_STATE_FULFILLED state. + + jerry_release_value (promise); +} +``` + +**See also** + +- [jerry_create_promise](#jerry_create_promise) +- [jerry_promise_state_t](#jerry_promise_state_t) + ## jerry_resolve_or_reject_promise **Summary** diff --git a/jerry-core/api/jerry.c b/jerry-core/api/jerry.c index 5257edb64..c7e28e39c 100644 --- a/jerry-core/api/jerry.c +++ b/jerry-core/api/jerry.c @@ -71,6 +71,14 @@ JERRY_STATIC_ASSERT ((int) RE_FLAG_GLOBAL == (int) JERRY_REGEXP_FLAG_GLOBAL re_flags_t_must_be_equal_to_jerry_regexp_flags_t); #endif /* ENABLED (JERRY_BUILTIN_REGEXP) */ +#if ENABLED (JERRY_ES2015_BUILTIN_PROMISE) +/* The internal ECMA_PROMISE_STATE_* values are "one byte away" from the API values */ +JERRY_STATIC_ASSERT (((ECMA_PROMISE_STATE_PENDING + 1) == JERRY_PROMISE_STATE_PENDING) + && ((ECMA_PROMISE_STATE_FULFILLED + 1) == JERRY_PROMISE_STATE_FULFILLED) + && ((ECMA_PROMISE_STATE_REJECTED + 1) == JERRY_PROMISE_STATE_REJECTED), + promise_internal_state_matches_external); +#endif /* ENABLED (JERRY_ES2015_BUILTIN_PROMISE) */ + #if !ENABLED (JERRY_PARSER) && !ENABLED (JERRY_SNAPSHOT_EXEC) #error "JERRY_SNAPSHOT_EXEC must be enabled if JERRY_PARSER is disabled!" #endif /* !ENABLED (JERRY_PARSER) && !ENABLED (JERRY_SNAPSHOT_EXEC) */ @@ -3084,6 +3092,60 @@ jerry_resolve_or_reject_promise (jerry_value_t promise, /**< the promise value * #endif /* ENABLED (JERRY_ES2015_BUILTIN_PROMISE) */ } /* jerry_resolve_or_reject_promise */ +/** + * Get the result of a promise. + * + * @return - Promise result + * - Type error if the promise support was not enabled or the input was not a promise object + */ +jerry_value_t +jerry_get_promise_result (const jerry_value_t promise) /**< promise object to get the result from */ +{ + jerry_assert_api_available (); + +#if ENABLED (JERRY_ES2015_BUILTIN_PROMISE) + if (!jerry_value_is_promise (promise)) + { + return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG (wrong_args_msg_p))); + } + + return ecma_promise_get_result (ecma_get_object_from_value (promise)); +#else /* !ENABLED (JERRY_ES2015_BUILTIN_PROMISE) */ + JERRY_UNUSED (promise); + return jerry_throw (ecma_raise_type_error (ECMA_ERR_MSG ("Promise not supported."))); +#endif /* ENABLED (JERRY_ES2015_BUILTIN_PROMISE) */ +} /* jerry_get_promise_result */ + +/** + * Get the state of a promise object. + * + * @return - the state of the promise (one of the jerry_promise_state_t enum values) + * - JERRY_PROMISE_STATE_NONE is only returned if the input is not a promise object + * or the promise support was not enabled. + */ +jerry_promise_state_t +jerry_get_promise_state (const jerry_value_t promise) /**< promise object to get the state from */ +{ + jerry_assert_api_available (); + +#if ENABLED (JERRY_ES2015_BUILTIN_PROMISE) + if (!jerry_value_is_promise (promise)) + { + return JERRY_PROMISE_STATE_NONE; + } + + uint8_t state = ecma_promise_get_state (ecma_get_object_from_value (promise)); + + JERRY_ASSERT (state < ECMA_PROMISE_STATE__COUNT); + + /* Static assert above guarantees the mapping from internal type to external type. */ + return (jerry_promise_state_t) (state + 1); +#else /* !ENABLED (JERRY_ES2015_BUILTIN_PROMISE) */ + JERRY_UNUSED (promise); + return JERRY_PROMISE_STATE_NONE; +#endif /* ENABLED (JERRY_ES2015_BUILTIN_PROMISE) */ +} /* jerry_get_promise_state */ + /** * Call the SymbolDescriptiveString ecma builtin operation on the symbol value. * diff --git a/jerry-core/ecma/operations/ecma-promise-object.c b/jerry-core/ecma/operations/ecma-promise-object.c index d60a94872..3a799b6a1 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.c +++ b/jerry-core/ecma/operations/ecma-promise-object.c @@ -53,7 +53,7 @@ ecma_is_promise (ecma_object_t *obj_p) /**< points to object */ * @return ecma value of the promise result. * Returned value must be freed with ecma_free_value */ -static inline ecma_value_t +ecma_value_t ecma_promise_get_result (ecma_object_t *obj_p) /**< points to promise object */ { JERRY_ASSERT (ecma_is_promise (obj_p)); @@ -84,7 +84,7 @@ ecma_promise_set_result (ecma_object_t *obj_p, /**< points to promise object */ * * @return the state's enum value */ -static inline uint8_t JERRY_ATTR_ALWAYS_INLINE +uint8_t ecma_promise_get_state (ecma_object_t *obj_p) /**< points to promise object */ { JERRY_ASSERT (ecma_is_promise (obj_p)); diff --git a/jerry-core/ecma/operations/ecma-promise-object.h b/jerry-core/ecma/operations/ecma-promise-object.h index 2e71b6225..ad019f330 100644 --- a/jerry-core/ecma/operations/ecma-promise-object.h +++ b/jerry-core/ecma/operations/ecma-promise-object.h @@ -71,6 +71,8 @@ typedef struct bool ecma_is_promise (ecma_object_t *obj_p); ecma_value_t ecma_op_create_promise_object (ecma_value_t executor, ecma_promise_executor_type_t type); +uint8_t ecma_promise_get_state (ecma_object_t *promise_p); +ecma_value_t ecma_promise_get_result (ecma_object_t *promise_p); ecma_value_t ecma_promise_new_capability (void); ecma_value_t ecma_promise_then (ecma_value_t promise, diff --git a/jerry-core/include/jerryscript-core.h b/jerry-core/include/jerryscript-core.h index 8feaed429..218baf284 100644 --- a/jerry-core/include/jerryscript-core.h +++ b/jerry-core/include/jerryscript-core.h @@ -555,10 +555,24 @@ bool jerry_foreach_object_property (const jerry_value_t obj_val, jerry_object_pr void *user_data_p); /** - * Promise resolve/reject functions. + * Promise functions. */ jerry_value_t jerry_resolve_or_reject_promise (jerry_value_t promise, jerry_value_t argument, bool is_resolve); +/** + * Enum values representing various Promise states. + */ +typedef enum +{ + JERRY_PROMISE_STATE_NONE = 0u, /**< Invalid/Unknown state (possibly called on a non-promise object). */ + JERRY_PROMISE_STATE_PENDING, /**< Promise is in "Pending" state. */ + JERRY_PROMISE_STATE_FULFILLED, /**< Promise is in "Fulfilled" state. */ + JERRY_PROMISE_STATE_REJECTED, /**< Promise is in "Rejected" state. */ +} jerry_promise_state_t; + +jerry_value_t jerry_get_promise_result (const jerry_value_t promise); +jerry_promise_state_t jerry_get_promise_state (const jerry_value_t promise); + /** * Symbol functions. */ diff --git a/tests/unit-core/test-api-promise.c b/tests/unit-core/test-api-promise.c new file mode 100644 index 000000000..9dd247d0b --- /dev/null +++ b/tests/unit-core/test-api-promise.c @@ -0,0 +1,205 @@ +/* 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 "jerryscript.h" +#include "jerryscript-port.h" +#include "jerryscript-port-default.h" +#include "test-common.h" + +static void +test_promise_resolve_success (void) +{ + jerry_value_t my_promise = jerry_create_promise (); + + // A created promise has an undefined promise result by default and a pending state + { + jerry_value_t promise_result = jerry_get_promise_result (my_promise); + TEST_ASSERT (jerry_value_is_undefined (promise_result)); + + jerry_promise_state_t promise_state = jerry_get_promise_state (my_promise); + TEST_ASSERT (promise_state == JERRY_PROMISE_STATE_PENDING); + + jerry_release_value (promise_result); + } + + jerry_value_t resolve_value = jerry_create_object (); + { + jerry_value_t obj_key = jerry_create_string ((const jerry_char_t *) "key_one"); + jerry_value_t set_result = jerry_set_property (resolve_value, obj_key, jerry_create_number (3)); + TEST_ASSERT (jerry_value_is_boolean (set_result) && (jerry_get_boolean_value (set_result) == true)); + jerry_release_value (set_result); + jerry_release_value (obj_key); + } + + // A resolved promise should have the result of from the resolve call and a fulfilled state + { + jerry_value_t resolve_result = jerry_resolve_or_reject_promise (my_promise, resolve_value, true); + + // Release "old" value of resolve. + jerry_release_value (resolve_value); + + jerry_value_t promise_result = jerry_get_promise_result (my_promise); + { + TEST_ASSERT (jerry_value_is_object (promise_result)); + jerry_value_t obj_key = jerry_create_string ((const jerry_char_t *) "key_one"); + jerry_value_t get_result = jerry_get_property (promise_result, obj_key); + TEST_ASSERT (jerry_value_is_number (get_result)); + TEST_ASSERT (jerry_get_number_value (get_result) == 3.0); + + jerry_release_value (get_result); + jerry_release_value (obj_key); + } + + jerry_promise_state_t promise_state = jerry_get_promise_state (my_promise); + TEST_ASSERT (promise_state == JERRY_PROMISE_STATE_FULFILLED); + + jerry_release_value (promise_result); + + jerry_release_value (resolve_result); + } + + // Resolvind a promise again does not change the result/state + { + jerry_value_t resolve_result = jerry_resolve_or_reject_promise (my_promise, jerry_create_number (50), false); + + jerry_value_t promise_result = jerry_get_promise_result (my_promise); + { + TEST_ASSERT (jerry_value_is_object (promise_result)); + jerry_value_t obj_key = jerry_create_string ((const jerry_char_t *) "key_one"); + jerry_value_t get_result = jerry_get_property (promise_result, obj_key); + TEST_ASSERT (jerry_value_is_number (get_result)); + TEST_ASSERT (jerry_get_number_value (get_result) == 3.0); + + jerry_release_value (get_result); + jerry_release_value (obj_key); + } + + jerry_promise_state_t promise_state = jerry_get_promise_state (my_promise); + TEST_ASSERT (promise_state == JERRY_PROMISE_STATE_FULFILLED); + + jerry_release_value (promise_result); + + jerry_release_value (resolve_result); + } + + jerry_release_value (my_promise); +} /* test_promise_resolve_success */ + +static void +test_promise_resolve_fail (void) +{ + jerry_value_t my_promise = jerry_create_promise (); + + // A created promise has an undefined promise result by default and a pending state + { + jerry_value_t promise_result = jerry_get_promise_result (my_promise); + TEST_ASSERT (jerry_value_is_undefined (promise_result)); + + jerry_promise_state_t promise_state = jerry_get_promise_state (my_promise); + TEST_ASSERT (promise_state == JERRY_PROMISE_STATE_PENDING); + + jerry_release_value (promise_result); + } + + // A resolved promise should have the result of from the resolve call and a fulfilled state + { + jerry_value_t error_value = jerry_create_error (JERRY_ERROR_TYPE, (const jerry_char_t *) "resolve_fail"); + jerry_value_t error_obj = jerry_get_value_from_error (error_value, true); + jerry_value_t resolve_result = jerry_resolve_or_reject_promise (my_promise, error_obj, false); + jerry_release_value (error_obj); + + jerry_value_t promise_result = jerry_get_promise_result (my_promise); + // The error is not throw that's why it is only an error object. + TEST_ASSERT (jerry_value_is_object (promise_result)); + TEST_ASSERT (jerry_get_error_type (promise_result) == JERRY_ERROR_TYPE); + + jerry_promise_state_t promise_state = jerry_get_promise_state (my_promise); + TEST_ASSERT (promise_state == JERRY_PROMISE_STATE_REJECTED); + + jerry_release_value (promise_result); + + jerry_release_value (resolve_result); + } + + // Resolvind a promise again does not change the result/state + { + jerry_value_t resolve_result = jerry_resolve_or_reject_promise (my_promise, jerry_create_number (50), true); + + jerry_value_t promise_result = jerry_get_promise_result (my_promise); + TEST_ASSERT (jerry_value_is_object (promise_result)); + TEST_ASSERT (jerry_get_error_type (promise_result) == JERRY_ERROR_TYPE); + + jerry_promise_state_t promise_state = jerry_get_promise_state (my_promise); + TEST_ASSERT (promise_state == JERRY_PROMISE_STATE_REJECTED); + + jerry_release_value (promise_result); + + jerry_release_value (resolve_result); + } + + jerry_release_value (my_promise); +} /* test_promise_resolve_fail */ + +static void +test_promise_from_js (void) +{ + const jerry_char_t test_source[] = "(new Promise(function(rs, rj) { rs(30); })).then(function(v) { return v + 1; })"; + + jerry_value_t parsed_code_val = jerry_parse (NULL, + 0, + test_source, + sizeof (test_source) - 1, + JERRY_PARSE_NO_OPTS); + TEST_ASSERT (!jerry_value_is_error (parsed_code_val)); + + jerry_value_t res = jerry_run (parsed_code_val); + TEST_ASSERT (jerry_value_is_promise (res)); + + TEST_ASSERT (jerry_get_promise_state (res) == JERRY_PROMISE_STATE_PENDING); + + jerry_value_t run_result = jerry_run_all_enqueued_jobs (); + TEST_ASSERT (jerry_value_is_undefined (run_result)); + jerry_release_value (run_result); + + TEST_ASSERT (jerry_get_promise_state (res) == JERRY_PROMISE_STATE_FULFILLED); + jerry_value_t promise_result = jerry_get_promise_result (res); + TEST_ASSERT (jerry_value_is_number (promise_result)); + TEST_ASSERT (jerry_get_number_value (promise_result) == 31.0); + + jerry_release_value (promise_result); + jerry_release_value (res); + jerry_release_value (parsed_code_val); +} /* test_promise_from_js */ + +int +main (void) +{ + if (!jerry_is_feature_enabled (JERRY_FEATURE_PROMISE)) + { + jerry_port_log (JERRY_LOG_LEVEL_ERROR, "Promise is disabled!\n"); + return 0; + } + + jerry_init (JERRY_INIT_EMPTY); + + test_promise_resolve_fail (); + test_promise_resolve_success (); + + test_promise_from_js (); + + jerry_cleanup (); + + return 0; +} /* main */