details: https://hg.nginx.org/njs/rev/61bf7a31e685 branches: changeset: 1287:61bf7a31e685 user: Alexander Borisov <alexander.bori...@nginx.com> date: Tue Dec 17 10:35:11 2019 +0300 description: Introduced the Promise object.
Implemented according to the specification without: Promise.all(), Promise.allSettled(), Promise.race(). This closes #39 issue on GitHub. diffstat: auto/sources | 1 + src/njs_builtin.c | 10 + src/njs_date.c | 1 + src/njs_event.h | 2 + src/njs_function.c | 6 +- src/njs_function.h | 19 +- src/njs_json.c | 1 + src/njs_main.h | 1 + src/njs_object.c | 1 + src/njs_object_hash.h | 11 + src/njs_promise.c | 1221 ++++++++++++++++++++++++++++++++++++++++ src/njs_promise.h | 18 + src/njs_shell.c | 1 + src/njs_value.c | 4 + src/njs_value.h | 27 +- src/njs_vm.c | 59 +- src/njs_vm.h | 2 + src/njs_vmcode.c | 5 +- src/njs_vmcode.h | 2 + test/js/promise_s1.js | 15 + test/js/promise_s10.js | 11 + test/js/promise_s11.js | 13 + test/js/promise_s12.js | 10 + test/js/promise_s13.js | 21 + test/js/promise_s14.js | 9 + test/js/promise_s15.js | 10 + test/js/promise_s16.js | 10 + test/js/promise_s17.js | 10 + test/js/promise_s18.js | 23 + test/js/promise_s19.js | 33 + test/js/promise_s2.js | 14 + test/js/promise_s20.js | 23 + test/js/promise_s21.js | 30 + test/js/promise_s22.js | 32 + test/js/promise_s23.js | 28 + test/js/promise_s24.js | 13 + test/js/promise_s25.js | 29 + test/js/promise_s26.js | 144 ++++ test/js/promise_s3.js | 11 + test/js/promise_s4.js | 6 + test/js/promise_s5.js | 7 + test/js/promise_s6.js | 4 + test/js/promise_s7.js | 12 + test/js/promise_s8.js | 13 + test/js/promise_s9.js | 10 + test/js/promise_set_timeout.js | 17 + test/njs_expect_test.exp | 170 +++++ 47 files changed, 2089 insertions(+), 31 deletions(-) diffs (truncated from 2553 to 1000 lines): diff -r 1023383de2d6 -r 61bf7a31e685 auto/sources --- a/auto/sources Mon Dec 16 15:18:51 2019 +0300 +++ b/auto/sources Tue Dec 17 10:35:11 2019 +0300 @@ -53,6 +53,7 @@ NJS_LIB_SRCS=" \ src/njs_generator.c \ src/njs_disassembler.c \ src/njs_array_buffer.c \ + src/njs_promise.c \ " NJS_LIB_TEST_SRCS=" \ diff -r 1023383de2d6 -r 61bf7a31e685 src/njs_builtin.c --- a/src/njs_builtin.c Mon Dec 16 15:18:51 2019 +0300 +++ b/src/njs_builtin.c Tue Dec 17 10:35:11 2019 +0300 @@ -66,6 +66,7 @@ static const njs_object_type_init_t *con &njs_function_type_init, &njs_regexp_type_init, &njs_date_type_init, + &njs_promise_type_init, /* Hidden types. */ @@ -1173,6 +1174,15 @@ static const njs_object_prop_t njs_glob { .type = NJS_PROPERTY_HANDLER, + .name = njs_string("Promise"), + .value = njs_prop_handler2(njs_top_level_constructor, + NJS_OBJ_TYPE_PROMISE, NJS_PROMISE_HASH), + .writable = 1, + .configurable = 1, + }, + + { + .type = NJS_PROPERTY_HANDLER, .name = njs_string("Error"), .value = njs_prop_handler2(njs_top_level_constructor, NJS_OBJ_TYPE_ERROR, NJS_ERROR_HASH), diff -r 1023383de2d6 -r 61bf7a31e685 src/njs_date.c --- a/src/njs_date.c Mon Dec 16 15:18:51 2019 +0300 +++ b/src/njs_date.c Tue Dec 17 10:35:11 2019 +0300 @@ -1840,3 +1840,4 @@ const njs_object_type_init_t njs_date_t .prototype_props = &njs_date_prototype_init, .prototype_value = { .object = { .type = NJS_OBJECT } }, }; + diff -r 1023383de2d6 -r 61bf7a31e685 src/njs_event.h --- a/src/njs_event.h Mon Dec 16 15:18:51 2019 +0300 +++ b/src/njs_event.h Tue Dec 17 10:35:11 2019 +0300 @@ -16,6 +16,8 @@ #define njs_posted_events(vm) (!njs_queue_is_empty(&(vm)->posted_events)) +#define njs_promise_events(vm) (!njs_queue_is_empty(&(vm)->promise_events)) + typedef struct { njs_function_t *function; diff -r 1023383de2d6 -r 61bf7a31e685 src/njs_function.c --- a/src/njs_function.c Mon Dec 16 15:18:51 2019 +0300 +++ b/src/njs_function.c Tue Dec 17 10:35:11 2019 +0300 @@ -539,14 +539,14 @@ njs_function_frame_alloc(njs_vm_t *vm, s njs_int_t -njs_function_call(njs_vm_t *vm, njs_function_t *function, +njs_function_call2(njs_vm_t *vm, njs_function_t *function, const njs_value_t *this, const njs_value_t *args, - njs_uint_t nargs, njs_value_t *retval) + njs_uint_t nargs, njs_value_t *retval, njs_bool_t ctor) { njs_int_t ret; njs_value_t dst njs_aligned(16); - ret = njs_function_frame(vm, function, this, args, nargs, 0); + ret = njs_function_frame(vm, function, this, args, nargs, ctor); if (njs_slow_path(ret != NJS_OK)) { return ret; } diff -r 1023383de2d6 -r 61bf7a31e685 src/njs_function.h --- a/src/njs_function.h Mon Dec 16 15:18:51 2019 +0300 +++ b/src/njs_function.h Tue Dec 17 10:35:11 2019 +0300 @@ -115,9 +115,9 @@ njs_int_t njs_function_native_frame(njs_ njs_int_t njs_function_lambda_frame(njs_vm_t *vm, njs_function_t *function, const njs_value_t *this, const njs_value_t *args, njs_uint_t nargs, njs_bool_t ctor); -njs_int_t njs_function_call(njs_vm_t *vm, njs_function_t *function, - const njs_value_t *this, const njs_value_t *args, njs_uint_t nargs, - njs_value_t *retval); +njs_int_t njs_function_call2(njs_vm_t *vm, njs_function_t *function, + const njs_value_t *this, const njs_value_t *args, + njs_uint_t nargs, njs_value_t *retval, njs_bool_t ctor); njs_int_t njs_function_lambda_call(njs_vm_t *vm); njs_int_t njs_function_native_call(njs_vm_t *vm); void njs_function_frame_free(njs_vm_t *vm, njs_native_frame_t *frame); @@ -186,11 +186,20 @@ njs_function_frame_invoke(njs_vm_t *vm, njs_inline njs_int_t +njs_function_call(njs_vm_t *vm, njs_function_t *function, + const njs_value_t *this, const njs_value_t *args, + njs_uint_t nargs, njs_value_t *retval) +{ + return njs_function_call2(vm, function, this, args, nargs, retval, 0); +} + + +njs_inline njs_int_t njs_function_apply(njs_vm_t *vm, njs_function_t *function, const njs_value_t *args, njs_uint_t nargs, njs_value_t *retval) { - return njs_function_call(vm, function, &args[0], &args[1], nargs - 1, - retval); + return njs_function_call2(vm, function, &args[0], &args[1], nargs - 1, + retval, 0); } diff -r 1023383de2d6 -r 61bf7a31e685 src/njs_json.c --- a/src/njs_json.c Mon Dec 16 15:18:51 2019 +0300 +++ b/src/njs_json.c Tue Dec 17 10:35:11 2019 +0300 @@ -1904,6 +1904,7 @@ njs_dump_is_object(const njs_value_t *va return (value->type == NJS_OBJECT && !njs_object(value)->error_data) || (value->type == NJS_ARRAY) || (value->type == NJS_ARRAY_BUFFER) + || (value->type == NJS_PROMISE) || (value->type == NJS_OBJECT_VALUE) || njs_dump_is_external_object(value); } diff -r 1023383de2d6 -r 61bf7a31e685 src/njs_main.h --- a/src/njs_main.h Mon Dec 16 15:18:51 2019 +0300 +++ b/src/njs_main.h Tue Dec 17 10:35:11 2019 +0300 @@ -65,6 +65,7 @@ #include <njs_regexp.h> #include <njs_regexp_pattern.h> #include <njs_date.h> +#include <njs_promise.h> #include <njs_math.h> #include <njs_json.h> diff -r 1023383de2d6 -r 61bf7a31e685 src/njs_object.c --- a/src/njs_object.c Mon Dec 16 15:18:51 2019 +0300 +++ b/src/njs_object.c Tue Dec 17 10:35:11 2019 +0300 @@ -2410,6 +2410,7 @@ njs_object_prototype_to_string(njs_vm_t &njs_object_regexp_string, &njs_object_date_string, &njs_object_object_string, + &njs_object_object_string, &njs_object_array_buffer_string, }; diff -r 1023383de2d6 -r 61bf7a31e685 src/njs_object_hash.h --- a/src/njs_object_hash.h Mon Dec 16 15:18:51 2019 +0300 +++ b/src/njs_object_hash.h Tue Dec 17 10:35:11 2019 +0300 @@ -88,6 +88,17 @@ 'D'), 'a'), 't'), 'e') +#define NJS_PROMISE_HASH \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add( \ + njs_djb_hash_add(NJS_DJB_HASH_INIT, \ + 'P'), 'r'), 'o'), 'm'), 'i'), 's'), 'e') + + #define NJS_ENUMERABLE_HASH \ njs_djb_hash_add( \ njs_djb_hash_add( \ diff -r 1023383de2d6 -r 61bf7a31e685 src/njs_promise.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/njs_promise.c Tue Dec 17 10:35:11 2019 +0300 @@ -0,0 +1,1221 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + +#include <njs_main.h> + + +typedef enum { + NJS_PROMISE_PENDING = 0, + NJS_PROMISE_FULFILL, + NJS_PROMISE_REJECTED +} njs_promise_type_t; + +typedef struct { + njs_promise_type_t state; + njs_value_t result; + njs_queue_t fulfill_queue; + njs_queue_t reject_queue; + njs_bool_t is_handled; +} njs_promise_data_t; + +typedef struct { + njs_value_t promise; + njs_value_t resolve; + njs_value_t reject; +} njs_promise_capability_t; + +typedef struct { + njs_promise_capability_t *capability; + njs_promise_type_t type; + njs_queue_link_t link; + njs_value_t handler; +} njs_promise_reaction_t; + +typedef struct { + njs_value_t promise; + njs_value_t finally; + njs_value_t constructor; + njs_bool_t resolved; + njs_bool_t *resolved_ref; + njs_promise_capability_t *capability; +} njs_promise_context_t; + + +static njs_promise_t *njs_promise_constructor_call(njs_vm_t *vm, + njs_function_t *function); +static njs_int_t njs_promise_create_resolving_functions(njs_vm_t *vm, + njs_promise_t *promise, njs_value_t *dst); +static njs_int_t njs_promise_value_constructor(njs_vm_t *vm, njs_value_t *value, + njs_value_t *dst); +static njs_int_t njs_promise_capability_executor(njs_vm_t *vm, + njs_value_t *args, njs_uint_t nargs, njs_index_t retval); +static njs_int_t njs_promise_resolve_function(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t retval); +static njs_promise_t *njs_promise_resolve(njs_vm_t *vm, + njs_value_t *constructor, njs_value_t *x); +static njs_int_t njs_promise_reject_function(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t retval); +static njs_int_t njs_promise_perform_then(njs_vm_t *vm, njs_value_t *value, + njs_value_t *fulfilled, njs_value_t *rejected, + njs_promise_capability_t *capability); +static njs_int_t njs_promise_then_finally_function(njs_vm_t *vm, + njs_value_t *args, njs_uint_t nargs, njs_index_t unused); +static njs_int_t njs_promise_catch_finally_function(njs_vm_t *vm, + njs_value_t *args, njs_uint_t nargs, njs_index_t unused); +static njs_int_t njs_promise_reaction_job(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused); +static njs_int_t njs_promise_resolve_thenable_job(njs_vm_t *vm, + njs_value_t *args, njs_uint_t nargs, njs_index_t unused); + + +static njs_promise_t * +njs_promise_alloc(njs_vm_t *vm) +{ + njs_promise_t *promise; + njs_promise_data_t *data; + + promise = njs_mp_alloc(vm->mem_pool, sizeof(njs_promise_t) + + sizeof(njs_promise_data_t)); + if (njs_slow_path(promise == NULL)) { + goto memory_error; + } + + njs_lvlhsh_init(&promise->object.hash); + njs_lvlhsh_init(&promise->object.shared_hash); + promise->object.type = NJS_PROMISE; + promise->object.shared = 0; + promise->object.extensible = 1; + promise->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_PROMISE].object; + + data = (njs_promise_data_t *) ((uint8_t *) promise + sizeof(njs_promise_t)); + + data->state = NJS_PROMISE_PENDING; + data->is_handled = 0; + + njs_queue_init(&data->fulfill_queue); + njs_queue_init(&data->reject_queue); + + njs_set_promise(&vm->retval, promise); + njs_value_data_set(&promise->value, data); + + return promise; + +memory_error: + + njs_memory_error(vm); + + return NULL; +} + + +njs_int_t +njs_promise_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_promise_t *promise; + njs_function_t *function; + + if (njs_slow_path(!vm->top_frame->ctor)) { + njs_type_error(vm, "the Promise constructor must be called with new"); + return NJS_ERROR; + } + + if (njs_slow_path(!njs_is_function(njs_arg(args, nargs, 1)))) { + njs_type_error(vm, "unexpected arguments"); + return NJS_ERROR; + } + + function = njs_function(njs_argument(args, 1)); + + promise = njs_promise_constructor_call(vm, function); + if (njs_slow_path(promise == NULL)) { + return NJS_ERROR; + } + + njs_set_promise(&vm->retval, promise); + + return NJS_OK; +} + + +static njs_promise_t * +njs_promise_constructor_call(njs_vm_t *vm, njs_function_t *function) +{ + njs_int_t ret; + njs_value_t retval, arguments[2]; + njs_promise_t *promise; + + promise = njs_promise_alloc(vm); + if (njs_slow_path(promise == NULL)) { + return NULL; + } + + ret = njs_promise_create_resolving_functions(vm, promise, arguments); + if (njs_slow_path(ret != NJS_OK)) { + return NULL; + } + + ret = njs_function_call(vm, function, &njs_value_undefined, arguments, 2, + &retval); + if (njs_slow_path(ret != NJS_OK)) { + if (njs_slow_path(njs_is_memory_error(vm, &vm->retval))) { + return NULL; + } + + ret = njs_function_call(vm, njs_function(&arguments[1]), + &njs_value_undefined, &vm->retval, 1, &retval); + if (njs_slow_path(ret != NJS_OK)) { + return NULL; + } + } + + return promise; +} + + +static njs_function_t * +njs_promise_create_function(njs_vm_t *vm) +{ + njs_function_t *function; + njs_promise_context_t *context; + + function = njs_mp_zalloc(vm->mem_pool, sizeof(njs_function_t)); + if (njs_slow_path(function == NULL)) { + goto memory_error; + } + + context = njs_mp_zalloc(vm->mem_pool, sizeof(njs_promise_context_t)); + if (njs_slow_path(context == NULL)) { + njs_mp_free(vm->mem_pool, function); + goto memory_error; + } + + function->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_FUNCTION].object; + function->object.shared_hash = vm->shared->arrow_instance_hash; + function->object.type = NJS_FUNCTION; + function->object.shared = 0; + function->object.extensible = 1; + function->args_offset = 1; + function->native = 1; + function->context = context; + + return function; + +memory_error: + + njs_memory_error(vm); + + return NULL; +} + + +static njs_int_t +njs_promise_create_resolving_functions(njs_vm_t *vm, njs_promise_t *promise, + njs_value_t *dst) +{ + unsigned i; + njs_function_t *function; + njs_promise_context_t *context, *resolve_context; + + i = 0; + + /* Some compilers give at error an uninitialized context if using for. */ + do { + function = njs_promise_create_function(vm); + if (njs_slow_path(function == NULL)) { + return NJS_ERROR; + } + + function->args_count = 1; + + context = function->context; + context->resolved_ref = &context->resolved; + + njs_set_promise(&context->promise, promise); + njs_set_function(&dst[i], function); + + } while (++i < 2); + + njs_function(&dst[0])->u.native = njs_promise_resolve_function; + njs_function(&dst[1])->u.native = njs_promise_reject_function; + + resolve_context = njs_function(&dst[0])->context; + resolve_context->resolved_ref = &context->resolved; + + return NJS_OK; +} + + +static njs_promise_capability_t * +njs_promise_new_capability(njs_vm_t *vm, njs_value_t *constructor) +{ + njs_int_t ret; + njs_value_t argument, this; + njs_object_t *object; + njs_function_t *function; + njs_promise_context_t *context; + njs_promise_capability_t *capability; + + object = NULL; + function = NULL; + + ret = njs_promise_value_constructor(vm, constructor, constructor); + if (njs_slow_path(ret != NJS_OK)) { + return NULL; + } + + capability = njs_mp_zalloc(vm->mem_pool, sizeof(njs_promise_capability_t)); + if (njs_slow_path(capability == NULL)) { + njs_memory_error(vm); + return NULL; + } + + function = njs_promise_create_function(vm); + if (njs_slow_path(function == NULL)) { + return NULL; + } + + njs_set_undefined(&capability->resolve); + njs_set_undefined(&capability->reject); + + function->u.native = njs_promise_capability_executor; + function->args_count = 2; + + context = function->context; + context->capability = capability; + + njs_set_function(&argument, function); + + object = njs_function_new_object(vm, constructor); + if (njs_slow_path(object == NULL)) { + return NULL; + } + + njs_set_object(&this, object); + + ret = njs_function_call2(vm, njs_function(constructor), &this, + &argument, 1, &capability->promise, 1); + if (njs_slow_path(ret != NJS_OK)) { + return NULL; + } + + if (njs_slow_path(!njs_is_function(&capability->resolve))) { + njs_type_error(vm, "capability resolve slot is not callable"); + return NULL; + } + + if (njs_slow_path(!njs_is_function(&capability->reject))) { + njs_type_error(vm, "capability reject slot is not callable"); + return NULL; + } + + return capability; +} + + +static njs_int_t +njs_promise_value_constructor(njs_vm_t *vm, njs_value_t *value, + njs_value_t *dst) +{ + njs_int_t ret; + + static const njs_value_t string_constructor = njs_string("constructor"); + + if (njs_is_function(value)) { + *dst = *value; + return NJS_OK; + } + + ret = njs_value_property(vm, value, njs_value_arg(&string_constructor), + dst); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + if (!njs_is_function(dst)) { + njs_type_error(vm, "the object does not contain a constructor"); + return NJS_ERROR; + } + + return NJS_OK; +} + + +static njs_int_t +njs_promise_capability_executor(njs_vm_t *vm, njs_value_t *args, + njs_uint_t nargs, njs_index_t unused) +{ + njs_promise_context_t *context; + njs_promise_capability_t *capability; + + context = vm->top_frame->function->context; + capability = context->capability; + + if (njs_slow_path(capability == NULL)) { + njs_type_error(vm, "failed to get function capability"); + return NJS_ERROR; + } + + if (!njs_is_undefined(&capability->resolve)) { + njs_type_error(vm, "capability resolve slot is not undefined"); + return NJS_ERROR; + } + + if (!njs_is_undefined(&capability->reject)) { + njs_type_error(vm, "capability reject slot is not undefined"); + return NJS_ERROR; + } + + capability->resolve = *njs_arg(args, nargs, 1); + capability->reject = *njs_arg(args, nargs, 2); + + njs_vm_retval_set(vm, &njs_value_undefined); + + return NJS_OK; +} + + +njs_inline njs_int_t +njs_promise_add_event(njs_vm_t *vm, njs_function_t *function, njs_value_t *args, + njs_uint_t nargs) +{ + njs_event_t *event; + + event = njs_mp_zalloc(vm->mem_pool, sizeof(njs_event_t)); + if (njs_slow_path(event == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + event->function = function; + event->once = 1; + + if (nargs != 0) { + event->args = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t) * nargs); + if (njs_slow_path(event->args == NULL)) { + njs_memory_error(vm); + return NJS_ERROR; + } + + memcpy(event->args, args, sizeof(njs_value_t) * nargs); + + event->nargs = nargs; + } + + njs_queue_insert_tail(&vm->promise_events, &event->link); + + return NJS_OK; +} + + +njs_inline njs_value_t * +njs_promise_trigger_reactions(njs_vm_t *vm, njs_value_t *value, + njs_queue_t *queue) +{ + njs_int_t ret; + njs_value_t arguments[2]; + njs_function_t *function; + njs_queue_link_t *link; + njs_promise_reaction_t *reaction; + + for (link = njs_queue_first(queue); + link != njs_queue_tail(queue); + link = njs_queue_next(link)) + { + reaction = njs_queue_link_data(link, njs_promise_reaction_t, link); + + function = njs_promise_create_function(vm); + function->u.native = njs_promise_reaction_job; + + njs_set_data(&arguments[0], reaction); + arguments[1] = *value; + + ret = njs_promise_add_event(vm, function, arguments, 2); + if (njs_slow_path(ret != NJS_OK)) { + return njs_value_arg(&njs_value_null); + } + } + + return njs_value_arg(&njs_value_undefined); +} + + +njs_inline njs_value_t * +njs_promise_fulfill(njs_vm_t *vm, njs_promise_t *promise, njs_value_t *value) +{ + njs_queue_t queue; + njs_promise_data_t *data; + + data = njs_value_data(&promise->value); + + data->result = *value; + data->state = NJS_PROMISE_FULFILL; + + if (njs_queue_is_empty(&data->fulfill_queue)) { + return njs_value_arg(&njs_value_undefined); + + } else { + queue = data->fulfill_queue; + + queue.head.prev->next = &queue.head; + queue.head.next->prev = &queue.head; + } + + njs_queue_init(&data->fulfill_queue); + njs_queue_init(&data->reject_queue); + + return njs_promise_trigger_reactions(vm, value, &queue); +} + + +njs_inline njs_value_t * +njs_promise_reject(njs_vm_t *vm, njs_promise_t *promise, njs_value_t *reason) +{ + njs_queue_t queue; + njs_promise_data_t *data; + + data = njs_value_data(&promise->value); + + data->result = *reason; + data->state = NJS_PROMISE_REJECTED; + + if (njs_queue_is_empty(&data->reject_queue)) { + return njs_value_arg(&njs_value_undefined); + + } else { + queue = data->reject_queue; + + queue.head.prev->next = &queue.head; + queue.head.next->prev = &queue.head; + } + + njs_queue_init(&data->fulfill_queue); + njs_queue_init(&data->reject_queue); + + return njs_promise_trigger_reactions(vm, reason, &queue); +} + + +static njs_int_t +njs_promise_invoke_then(njs_vm_t *vm, njs_value_t *promise, njs_value_t *args, + njs_int_t nargs) +{ + njs_int_t ret; + njs_value_t function; + + static const njs_value_t string_then = njs_string("then"); + + ret = njs_value_property(vm, promise, njs_value_arg(&string_then), + &function); + if (njs_slow_path(ret != NJS_OK)) { + if (ret == NJS_DECLINED) { + goto failed; + } + + return NJS_ERROR; + } + + if (njs_fast_path(njs_is_function(&function))) { + return njs_function_call(vm, njs_function(&function), promise, args, + nargs, &vm->retval); + } + +failed: + + njs_type_error(vm, "is not a function"); + + return NJS_ERROR; +} + + +static njs_int_t +njs_promise_resolve_function(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_int_t ret; + njs_frame_t *active_frame; + njs_value_t *resolution, error, then, arguments[3]; + njs_promise_t *promise; + njs_function_t *function; + njs_promise_context_t *context; + + static const njs_value_t string_then = njs_string("then"); + + active_frame = (njs_frame_t *) vm->top_frame; + context = active_frame->native.function->context; + promise = njs_promise(&context->promise); + + if (*context->resolved_ref) { + njs_vm_retval_set(vm, &njs_value_undefined); + return NJS_OK; + } + + *context->resolved_ref = 1; + + resolution = njs_arg(args, nargs, 1); + + if (njs_values_same(resolution, &context->promise)) { + njs_error_fmt_new(vm, &error, NJS_OBJ_TYPE_TYPE_ERROR, + "promise self resolution"); + if (njs_slow_path(!njs_is_error(&error))) { + return NJS_ERROR; + } + + njs_vm_retval_set(vm, njs_promise_reject(vm, promise, &error)); + + return NJS_OK; + } + + if (!njs_is_object(resolution)) { + goto fulfill; + } + + ret = njs_value_property(vm, resolution, njs_value_arg(&string_then), + &then); + if (njs_slow_path(ret == NJS_ERROR)) { + if (njs_slow_path(njs_is_memory_error(vm, &vm->retval))) { + return NJS_ERROR; + } + + njs_vm_retval_set(vm, njs_promise_reject(vm, promise, &vm->retval)); + if (njs_slow_path(njs_vm_retval(vm)->type == NJS_NULL)) { + return NJS_ERROR; + } + + return NJS_OK; + } + + if (!njs_is_function(&then)) { + goto fulfill; + } + + arguments[0] = context->promise; + arguments[1] = *resolution; + arguments[2] = then; + + function = njs_promise_create_function(vm); + if (njs_slow_path(function == NULL)) { + return NJS_ERROR; + } + + function->u.native = njs_promise_resolve_thenable_job; + + ret = njs_promise_add_event(vm, function, arguments, 3); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + njs_vm_retval_set(vm, &njs_value_undefined); + + return NJS_OK; + +fulfill: + + njs_vm_retval_set(vm, njs_promise_fulfill(vm, promise, resolution)); + if (njs_slow_path(njs_vm_retval(vm)->type == NJS_NULL)) { + return NJS_ERROR; + } + + return NJS_OK; +} + + +static njs_int_t +njs_promise_object_resolve(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_promise_t *promise; + + if (njs_slow_path(!njs_is_object(njs_arg(args, nargs, 0)))) { + njs_type_error(vm, "this value is not an object"); + return NJS_ERROR; + } + + promise = njs_promise_resolve(vm, njs_argument(args, 0), + njs_arg(args, nargs, 1)); + if (njs_slow_path(promise == NULL)) { + return NJS_ERROR; + } + + njs_set_promise(&vm->retval, promise); + + return NJS_OK; +} + + +static njs_promise_t * +njs_promise_resolve(njs_vm_t *vm, njs_value_t *constructor, njs_value_t *x) +{ + njs_int_t ret; + njs_value_t value; + njs_object_t *object; + njs_promise_capability_t *capability; + + static const njs_value_t string_constructor = njs_string("constructor"); + + if (njs_is_object(x)) { + object = njs_object_proto_lookup(njs_object(x), NJS_PROMISE, + njs_object_t); + + if (object != NULL) { + ret = njs_value_property(vm, x, njs_value_arg(&string_constructor), + &value); + if (njs_slow_path(ret == NJS_ERROR)) { + return NULL; + } + + if (njs_values_same(&value, constructor)) { + return njs_promise(x); + } + } + } + + capability = njs_promise_new_capability(vm, constructor); + if (njs_slow_path(capability == NULL)) { + return NULL; + } + + ret = njs_function_call(vm, njs_function(&capability->resolve), + &njs_value_undefined, x, 1, &value); + if (njs_slow_path(ret != NJS_OK)) { + return NULL; + } + + return njs_promise(&capability->promise); +} + + +static njs_int_t +njs_promise_reject_function(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_frame_t *active_frame; + njs_value_t *value; + njs_promise_context_t *context; + + active_frame = (njs_frame_t *) vm->top_frame; + context = active_frame->native.function->context; + + if (*context->resolved_ref) { + njs_vm_retval_set(vm, &njs_value_undefined); + return NJS_OK; + } + + *context->resolved_ref = 1; + + value = njs_promise_reject(vm, njs_promise(&context->promise), + njs_arg(args, nargs, 1)); + if (njs_slow_path(value->type == NJS_NULL)) { + return NJS_ERROR; + } + + njs_vm_retval_set(vm, value); + + return NJS_OK; +} + + +static njs_int_t +njs_promise_object_reject(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_int_t ret; + njs_value_t value; + njs_promise_capability_t *capability; + + if (njs_slow_path(!njs_is_object(njs_arg(args, nargs, 0)))) { + njs_type_error(vm, "this value is not an object"); + return NJS_ERROR; + } + + capability = njs_promise_new_capability(vm, njs_argument(args, 0)); + if (njs_slow_path(capability == NULL)) { + return NJS_ERROR; + } + + ret = njs_function_call(vm, njs_function(&capability->reject), + &njs_value_undefined, njs_arg(args, nargs, 1), + 1, &value); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + njs_vm_retval_set(vm, &capability->promise); + + return NJS_OK; +} + + +static njs_int_t +njs_promise_prototype_then(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, + njs_index_t unused) +{ + njs_int_t ret; + njs_value_t *promise, *fulfilled, *rejected, constructor; + njs_object_t *object; + njs_function_t *function; + njs_promise_capability_t *capability; + + promise = njs_arg(args, nargs, 0); + + if (njs_slow_path(!njs_is_object(promise))) { + goto failed; + } + + object = njs_object_proto_lookup(njs_object(promise), NJS_PROMISE, + njs_object_t); + if (njs_slow_path(object == NULL)) { + goto failed; + } + + function = njs_promise_create_function(vm); + function->u.native = njs_promise_constructor; + + njs_set_function(&constructor, function); + + ret = njs_value_species_constructor(vm, promise, &constructor, + &constructor); + if (njs_slow_path(ret != NJS_OK)) { + return ret; + } + + capability = njs_promise_new_capability(vm, &constructor); + if (njs_slow_path(capability == NULL)) { + return NJS_ERROR; + } + + fulfilled = njs_arg(args, nargs, 1); + rejected = njs_arg(args, nargs, 2); + + return njs_promise_perform_then(vm, promise, fulfilled, rejected, + capability); + +failed: + + njs_type_error(vm, "required a promise object"); + + return NJS_ERROR; +} + + +static njs_int_t +njs_promise_perform_then(njs_vm_t *vm, njs_value_t *value, + njs_value_t *fulfilled, njs_value_t *rejected, + njs_promise_capability_t *capability) +{ + njs_int_t ret; + njs_value_t arguments[2]; + njs_promise_t *promise; + njs_function_t *function; + njs_promise_data_t *data; + njs_promise_reaction_t *fulfilled_reaction, *rejected_reaction; + + if (!njs_is_function(fulfilled)) { + fulfilled = njs_value_arg(&njs_value_undefined); + } + + if (!njs_is_function(rejected)) { + rejected = njs_value_arg(&njs_value_undefined); + } + + promise = njs_promise(value); + data = njs_value_data(&promise->value); _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org http://mailman.nginx.org/mailman/listinfo/nginx-devel