Created
November 29, 2019 15:27
-
-
Save lexborisov/7ea0b5f21001d721c2e1536c4ee2b19f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# HG changeset patch | |
# User Alexander Borisov <alexander.borisov@nginx.com> | |
# Date 1575041240 -10800 | |
# Fri Nov 29 18:27:20 2019 +0300 | |
# Node ID ef2e7a062ba40ff0f0e307763ec4e602dbf57112 | |
# Parent 01c7375c9b5ca47e0eda9a7bddf6aacbefbebaff | |
[mq]: Great_Promise | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 auto/sources | |
--- a/auto/sources Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/auto/sources Fri Nov 29 18:27:20 2019 +0300 | |
@@ -51,6 +51,7 @@ NJS_LIB_SRCS=" \ | |
src/njs_parser_expression.c \ | |
src/njs_generator.c \ | |
src/njs_disassembler.c \ | |
+ src/njs_promise.c \ | |
" | |
NJS_LIB_TEST_SRCS=" \ | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 src/njs_builtin.c | |
--- a/src/njs_builtin.c Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_builtin.c Fri Nov 29 18:27:20 2019 +0300 | |
@@ -65,6 +65,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. */ | |
@@ -1162,6 +1163,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 01c7375c9b5c -r ef2e7a062ba4 src/njs_date.c | |
--- a/src/njs_date.c Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_date.c Fri Nov 29 18:27:20 2019 +0300 | |
@@ -1840,3 +1840,4 @@ const njs_object_type_init_t njs_date_t | |
.constructor_props = &njs_date_constructor_init, | |
.value = { .object = { .type = NJS_OBJECT } }, | |
}; | |
+ | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 src/njs_error.c | |
--- a/src/njs_error.c Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_error.c Fri Nov 29 18:27:20 2019 +0300 | |
@@ -647,6 +647,27 @@ njs_memory_error(njs_vm_t *vm) | |
} | |
+njs_int_t | |
+njs_is_memory_error(njs_vm_t *vm) | |
+{ | |
+ njs_object_t *object; | |
+ | |
+ if (!njs_is_object(&vm->retval)) { | |
+ return 0; | |
+ } | |
+ | |
+ object = njs_object(&vm->retval); | |
+ | |
+ if (object->__proto__->type == NJS_OBJ_TYPE_INTERNAL_ERROR | |
+ && object->extensible == 0) | |
+ { | |
+ return 1; | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+ | |
static njs_int_t | |
njs_memory_error_constructor(njs_vm_t *vm, njs_value_t *args, | |
njs_uint_t nargs, njs_index_t unused) | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 src/njs_error.h | |
--- a/src/njs_error.h Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_error.h Fri Nov 29 18:27:20 2019 +0300 | |
@@ -39,6 +39,7 @@ void njs_noinline njs_error_fmt_new(njs_ | |
void njs_memory_error(njs_vm_t *vm); | |
void njs_memory_error_set(njs_vm_t *vm, njs_value_t *value); | |
+njs_int_t njs_is_memory_error(njs_vm_t *vm); | |
njs_object_t *njs_error_alloc(njs_vm_t *vm, njs_object_type_t type, | |
const njs_value_t *name, const njs_value_t *message); | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 src/njs_event.h | |
--- a/src/njs_event.h Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_event.h Fri Nov 29 18:27:20 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 01c7375c9b5c -r ef2e7a062ba4 src/njs_function.c | |
--- a/src/njs_function.c Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_function.c Fri Nov 29 18:27:20 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 01c7375c9b5c -r ef2e7a062ba4 src/njs_function.h | |
--- a/src/njs_function.h Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_function.h Fri Nov 29 18:27:20 2019 +0300 | |
@@ -117,9 +117,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); | |
@@ -188,11 +188,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 01c7375c9b5c -r ef2e7a062ba4 src/njs_json.c | |
--- a/src/njs_json.c Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_json.c Fri Nov 29 18:27:20 2019 +0300 | |
@@ -2077,6 +2077,10 @@ njs_dump_value(njs_json_stringify_t *str | |
return njs_json_buf_append(stringify, (char *) str.start, str.length); | |
+ case NJS_PROMISE: | |
+ njs_dump("[object Promise]"); | |
+ break; | |
+ | |
default: | |
p = njs_sprintf(buf, buf + njs_length(buf), "[Unknown value type:%uD]", | |
value->type); | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 src/njs_main.h | |
--- a/src/njs_main.h Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_main.h Fri Nov 29 18:27:20 2019 +0300 | |
@@ -62,6 +62,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 01c7375c9b5c -r ef2e7a062ba4 src/njs_object.c | |
--- a/src/njs_object.c Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_object.c Fri Nov 29 18:27:20 2019 +0300 | |
@@ -2408,6 +2408,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, | |
}; | |
value = njs_argument(args, 0); | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 src/njs_object_hash.h | |
--- a/src/njs_object_hash.h Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_object_hash.h Fri Nov 29 18:27:20 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 01c7375c9b5c -r ef2e7a062ba4 src/njs_promise.c | |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 | |
+++ b/src/njs_promise.c Fri Nov 29 18:27:20 2019 +0300 | |
@@ -0,0 +1,1209 @@ | |
+ | |
+/* | |
+ * Copyright (C) Igor Sysoev | |
+ * Copyright (C) Nginx, Inc. | |
+ */ | |
+ | |
+#include <njs_main.h> | |
+ | |
+#include "njs_error.h" | |
+#include "njs_promise.h" | |
+ | |
+ | |
+typedef enum { | |
+ NJS_PROMISE_PENDING = 0x00, | |
+ 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_arr_t reactions; | |
+ 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; | |
+ | |
+ | |
+static njs_promise_t *njs_promise_constructor_call(njs_vm_t *vm, | |
+ njs_function_t *function); | |
+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_species_constructor(njs_vm_t *vm, njs_value_t *object, | |
+ njs_value_t *default_constructor, 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_function_t *njs_promise_create_resolve_function(njs_vm_t *vm, | |
+ njs_promise_t *promise); | |
+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_function_t *njs_promise_create_reject_function(njs_vm_t *vm, | |
+ njs_promise_t *promise); | |
+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)) { | |
+ njs_memory_error(vm); | |
+ return NULL; | |
+ } | |
+ | |
+ 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; | |
+ | |
+ (void) njs_arr_init(vm->mem_pool, &data->reactions, NULL, 12, | |
+ sizeof(njs_promise_reaction_t)); | |
+ if (njs_slow_path(data->reactions.start == NULL)) { | |
+ njs_memory_error(vm); | |
+ return NULL; | |
+ } | |
+ | |
+ 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; | |
+} | |
+ | |
+ | |
+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 (!vm->top_frame->ctor) { | |
+ njs_type_error(vm, "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; | |
+ njs_function_t *resolve, *reject; | |
+ | |
+ promise = njs_promise_alloc(vm); | |
+ if (njs_slow_path(promise == NULL)) { | |
+ return NULL; | |
+ } | |
+ | |
+ resolve = njs_promise_create_resolve_function(vm, promise); | |
+ if (njs_slow_path(resolve == NULL)) { | |
+ return NULL; | |
+ } | |
+ | |
+ reject = njs_promise_create_reject_function(vm, promise); | |
+ if (njs_slow_path(reject == NULL)) { | |
+ return NULL; | |
+ } | |
+ | |
+ reject->resolved_ref = &resolve->resolved; | |
+ | |
+ njs_set_function(&arguments[0], resolve); | |
+ njs_set_function(&arguments[1], reject); | |
+ | |
+ 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))) { | |
+ return NULL; | |
+ } | |
+ | |
+ ret = njs_function_call(vm, reject, &njs_value_undefined, &vm->retval, | |
+ 1, &retval); | |
+ if (njs_slow_path(ret != NJS_OK)) { | |
+ return NULL; | |
+ } | |
+ } | |
+ | |
+ return promise; | |
+} | |
+ | |
+ | |
+njs_inline njs_function_t * | |
+njs_promise_create_function(njs_vm_t *vm) | |
+{ | |
+ njs_function_t *function; | |
+ | |
+ function = njs_mp_zalloc(vm->mem_pool, sizeof(njs_function_t)); | |
+ if (njs_slow_path(function == NULL)) { | |
+ njs_memory_error(vm); | |
+ return NULL; | |
+ } | |
+ | |
+ 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; | |
+ | |
+ return function; | |
+} | |
+ | |
+ | |
+njs_inline njs_promise_capability_t * | |
+njs_promise_new_capability(njs_vm_t *vm, njs_value_t *constructor) | |
+{ | |
+ njs_int_t ret; | |
+ njs_value_t argument; | |
+ njs_function_t *function; | |
+ njs_promise_capability_t *capability; | |
+ | |
+ 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->capability = capability; | |
+ function->args_count = 2; | |
+ | |
+ njs_set_function(&argument, function); | |
+ | |
+ ret = njs_function_call2(vm, njs_function(constructor), constructor, | |
+ &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; | |
+} | |
+ | |
+ | |
+/* TODO: need move the function to good place. */ | |
+static njs_int_t | |
+njs_species_constructor(njs_vm_t *vm, njs_value_t *object, | |
+ njs_value_t *default_constructor, njs_value_t *dst) | |
+{ | |
+ njs_int_t ret; | |
+ njs_value_t constructor; | |
+ | |
+ static const njs_value_t string_constructor = njs_string("constructor"); | |
+ | |
+ ret = njs_value_property(vm, object, njs_value_arg(&string_constructor), | |
+ &constructor); | |
+ if (njs_slow_path(ret != NJS_OK)) { | |
+ goto done_default; | |
+ } | |
+ | |
+ if (!njs_is_function(&constructor)) { | |
+ njs_type_error(vm, "the object does not contain a constructor"); | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ /* TODO: Let S be ? Get(C, @@species). */ | |
+ goto done_default; | |
+ | |
+ njs_type_error(vm, "get species constructor"); | |
+ | |
+ return NJS_ERROR; | |
+ | |
+done_default: | |
+ | |
+ *dst = *default_constructor; | |
+ | |
+ 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_capability_t *capability; | |
+ | |
+ capability = vm->top_frame->function->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 (data->fulfill_queue.head.next == &data->fulfill_queue.head) { | |
+ 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 (data->reject_queue.head.next == &data->reject_queue.head) { | |
+ 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_function_t * | |
+njs_promise_create_resolve_function(njs_vm_t *vm, njs_promise_t *promise) | |
+{ | |
+ njs_function_t *function; | |
+ | |
+ function = njs_promise_create_function(vm); | |
+ if (njs_slow_path(function == NULL)) { | |
+ return NULL; | |
+ } | |
+ | |
+ function->u.native = njs_promise_resolve_function; | |
+ function->resolved_ref = &function->resolved; | |
+ function->args_count = 1; | |
+ | |
+ njs_set_promise(&function->promise, promise); | |
+ | |
+ return function; | |
+} | |
+ | |
+ | |
+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; | |
+ | |
+ static const njs_value_t string_then = njs_string("then"); | |
+ | |
+ active_frame = (njs_frame_t *) vm->top_frame; | |
+ function = active_frame->native.function; | |
+ promise = njs_promise(&function->promise); | |
+ | |
+ if (*function->resolved_ref) { | |
+ njs_vm_retval_set(vm, &njs_value_undefined); | |
+ return NJS_OK; | |
+ } | |
+ | |
+ *function->resolved_ref = 1; | |
+ | |
+ resolution = njs_arg(args, nargs, 1); | |
+ | |
+ if (njs_values_same(resolution, &function->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))) { | |
+ 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] = function->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_promise_capability_t *capability; | |
+ | |
+ static const njs_value_t string_constructor = njs_string("constructor"); | |
+ | |
+ if (njs_is_promise(x)) { | |
+ 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_function_t * | |
+njs_promise_create_reject_function(njs_vm_t *vm, njs_promise_t *promise) | |
+{ | |
+ njs_function_t *function; | |
+ | |
+ function = njs_promise_create_function(vm); | |
+ if (njs_slow_path(function == NULL)) { | |
+ return NULL; | |
+ } | |
+ | |
+ function->u.native = njs_promise_reject_function; | |
+ function->resolved_ref = &function->resolved; | |
+ function->args_count = 1; | |
+ | |
+ njs_set_promise(&function->promise, promise); | |
+ | |
+ return function; | |
+} | |
+ | |
+ | |
+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_function_t *function; | |
+ | |
+ active_frame = (njs_frame_t *) vm->top_frame; | |
+ function = active_frame->native.function; | |
+ | |
+ if (*function->resolved_ref) { | |
+ njs_vm_retval_set(vm, &njs_value_undefined); | |
+ return NJS_OK; | |
+ } | |
+ | |
+ *function->resolved_ref = 1; | |
+ | |
+ value = njs_promise_reject(vm, njs_promise(&function->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_function_t *function; | |
+ njs_promise_capability_t *capability; | |
+ | |
+ promise = njs_arg(args, nargs, 0); | |
+ | |
+ if (njs_slow_path(!njs_is_promise(promise))) { | |
+ njs_type_error(vm, "required a promise object"); | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ /* TODO: need optimization */ | |
+ function = njs_promise_create_function(vm); | |
+ function->u.native = njs_promise_constructor; | |
+ | |
+ njs_set_function(&constructor, function); | |
+ | |
+ ret = njs_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); | |
+} | |
+ | |
+ | |
+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_slow_path(!njs_is_promise(value))) { | |
+ njs_type_error(vm, "required a promise object"); | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ 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); | |
+ | |
+ fulfilled_reaction = njs_arr_add(&data->reactions); | |
+ if (njs_slow_path(fulfilled_reaction == NULL)) { | |
+ njs_memory_error(vm); | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ fulfilled_reaction->capability = capability; | |
+ fulfilled_reaction->handler = *fulfilled; | |
+ fulfilled_reaction->type = NJS_PROMISE_FULFILL; | |
+ | |
+ rejected_reaction = njs_arr_add(&data->reactions); | |
+ if (njs_slow_path(fulfilled_reaction == NULL)) { | |
+ njs_memory_error(vm); | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ rejected_reaction->capability = capability; | |
+ rejected_reaction->handler = *rejected; | |
+ rejected_reaction->type = NJS_PROMISE_REJECTED; | |
+ | |
+ if (data->state == NJS_PROMISE_PENDING) { | |
+ njs_queue_insert_tail(&data->fulfill_queue, &fulfilled_reaction->link); | |
+ njs_queue_insert_tail(&data->reject_queue, &rejected_reaction->link); | |
+ | |
+ } else { | |
+ function = njs_promise_create_function(vm); | |
+ function->u.native = njs_promise_reaction_job; | |
+ | |
+ if (data->state == NJS_PROMISE_REJECTED) { | |
+ njs_set_data(&arguments[0], rejected_reaction); | |
+ | |
+ /* TODO: HostPromiseRejectionTracker */ | |
+ | |
+ } else { | |
+ njs_set_data(&arguments[0], fulfilled_reaction); | |
+ } | |
+ | |
+ arguments[1] = data->result; | |
+ | |
+ ret = njs_promise_add_event(vm, function, arguments, 2); | |
+ if (njs_slow_path(ret != NJS_OK)) { | |
+ return ret; | |
+ } | |
+ } | |
+ | |
+ data->is_handled = 1; | |
+ | |
+ if (capability == NULL) { | |
+ njs_vm_retval_set(vm, &njs_value_undefined); | |
+ | |
+ } else { | |
+ njs_vm_retval_set(vm, &capability->promise); | |
+ } | |
+ | |
+ return NJS_OK; | |
+} | |
+ | |
+ | |
+static njs_int_t | |
+njs_promise_prototype_catch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, | |
+ njs_index_t unused) | |
+{ | |
+ njs_value_t arguments[2]; | |
+ | |
+ arguments[0] = njs_value_undefined; | |
+ arguments[1] = *njs_arg(args, nargs, 1); | |
+ | |
+ return njs_promise_invoke_then(vm, njs_arg(args, nargs, 0), arguments, 2); | |
+} | |
+ | |
+ | |
+static njs_int_t | |
+njs_promise_prototype_finally(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, | |
+ njs_index_t unused) | |
+{ | |
+ njs_int_t ret; | |
+ njs_value_t *promise, *finally, constructor, arguments[2]; | |
+ njs_function_t *function; | |
+ | |
+ promise = njs_arg(args, nargs, 0); | |
+ | |
+ if (njs_slow_path(!njs_is_promise(promise))) { | |
+ njs_type_error(vm, "required a promise object"); | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ finally = njs_arg(args, nargs, 1); | |
+ | |
+ function = njs_promise_create_function(vm); | |
+ function->u.native = njs_promise_constructor; | |
+ | |
+ njs_set_function(&constructor, function); | |
+ | |
+ ret = njs_species_constructor(vm, promise, &constructor, &constructor); | |
+ if (njs_slow_path(ret != NJS_OK)) { | |
+ return ret; | |
+ } | |
+ | |
+ if (!njs_is_function(finally)) { | |
+ arguments[0] = *finally; | |
+ arguments[1] = *finally; | |
+ | |
+ return njs_promise_invoke_then(vm, promise, arguments, 2); | |
+ } | |
+ | |
+ function = njs_promise_create_function(vm); | |
+ if (njs_slow_path(function == NULL)) { | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ function->u.native = njs_promise_then_finally_function; | |
+ function->constructor = constructor; | |
+ function->finally = *finally; | |
+ function->args_count = 1; | |
+ | |
+ njs_set_function(&arguments[0], function); | |
+ | |
+ function = njs_promise_create_function(vm); | |
+ if (njs_slow_path(function == NULL)) { | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ function->u.native = njs_promise_catch_finally_function; | |
+ function->constructor = constructor; | |
+ function->finally = *finally; | |
+ function->args_count = 1; | |
+ | |
+ njs_set_function(&arguments[1], function); | |
+ | |
+ return njs_promise_invoke_then(vm, promise, arguments, 2); | |
+} | |
+ | |
+ | |
+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) | |
+{ | |
+ njs_int_t ret; | |
+ njs_value_t value, retval; | |
+ njs_frame_t *frame; | |
+ njs_promise_t *promise; | |
+ njs_function_t *function; | |
+ | |
+ frame = (njs_frame_t *) vm->top_frame; | |
+ function = frame->native.function; | |
+ | |
+ ret = njs_function_call(vm, njs_function(&function->finally), | |
+ &njs_value_undefined, args, 0, &retval); | |
+ if (njs_slow_path(ret != NJS_OK)) { | |
+ return ret; | |
+ } | |
+ | |
+ promise = njs_promise_resolve(vm, &function->constructor, &retval); | |
+ if (njs_slow_path(promise == NULL)) { | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ njs_set_promise(&value, promise); | |
+ | |
+ return njs_promise_invoke_then(vm, &value, njs_arg(args, nargs, 1), 1); | |
+} | |
+ | |
+ | |
+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) | |
+{ | |
+ return njs_promise_then_finally_function(vm, args, nargs, 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) | |
+{ | |
+ njs_int_t ret; | |
+ njs_bool_t is_error; | |
+ njs_value_t *value, *argument, retval; | |
+ njs_promise_reaction_t *reaction; | |
+ njs_promise_capability_t *capability; | |
+ | |
+ value = njs_arg(args, nargs, 1); | |
+ argument = njs_arg(args, nargs, 2); | |
+ | |
+ reaction = njs_data(value); | |
+ capability = reaction->capability; | |
+ | |
+ is_error = 0; | |
+ | |
+ if (njs_is_undefined(&reaction->handler)) { | |
+ if (reaction->type == NJS_PROMISE_REJECTED) { | |
+ is_error = 1; | |
+ } | |
+ | |
+ retval = *argument; | |
+ | |
+ } else { | |
+ ret = njs_function_call(vm, njs_function(&reaction->handler), | |
+ &njs_value_undefined, argument, 1, &retval); | |
+ if (njs_slow_path(ret != NJS_OK)) { | |
+ if (njs_slow_path(njs_is_memory_error(vm))) { | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ retval = vm->retval; | |
+ is_error = 1; | |
+ } | |
+ } | |
+ | |
+ if (capability == NULL) { | |
+ njs_vm_retval_set(vm, &retval); | |
+ return NJS_OK; | |
+ } | |
+ | |
+ if (is_error) { | |
+ ret = njs_function_call(vm, njs_function(&capability->reject), | |
+ &njs_value_undefined, &retval, 1, &vm->retval); | |
+ | |
+ } else { | |
+ ret = njs_function_call(vm, njs_function(&capability->resolve), | |
+ &njs_value_undefined, &retval, 1, &vm->retval); | |
+ } | |
+ | |
+ if (njs_slow_path(ret != NJS_OK)) { | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ return NJS_OK; | |
+} | |
+ | |
+ | |
+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) | |
+{ | |
+ njs_int_t ret; | |
+ njs_value_t *promise, retval, arguments[2]; | |
+ njs_function_t *resolve, *reject; | |
+ | |
+ promise = njs_arg(args, nargs, 1); | |
+ | |
+ resolve = njs_promise_create_resolve_function(vm, njs_promise(promise)); | |
+ if (njs_slow_path(resolve == NULL)) { | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ reject = njs_promise_create_reject_function(vm, njs_promise(promise)); | |
+ if (njs_slow_path(reject == NULL)) { | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ reject->resolved_ref = &resolve->resolved; | |
+ | |
+ njs_set_function(&arguments[0], resolve); | |
+ njs_set_function(&arguments[1], reject); | |
+ | |
+ ret = njs_function_call(vm, njs_function(njs_arg(args, nargs, 3)), | |
+ njs_arg(args, nargs, 2), arguments, 2, &retval); | |
+ if (njs_slow_path(ret != NJS_OK)) { | |
+ | |
+ if (njs_slow_path(njs_is_memory_error(vm))) { | |
+ return NJS_ERROR; | |
+ } | |
+ | |
+ ret = njs_function_call(vm, reject, &njs_value_undefined, &vm->retval, | |
+ 1, &vm->retval); | |
+ if (njs_slow_path(ret != NJS_OK)) { | |
+ return NJS_ERROR; | |
+ } | |
+ } | |
+ | |
+ return NJS_OK; | |
+} | |
+ | |
+ | |
+static const njs_object_prop_t njs_promise_constructor_properties[] = | |
+{ | |
+ { | |
+ .type = NJS_PROPERTY, | |
+ .name = njs_string("name"), | |
+ .value = njs_string("Promise"), | |
+ .configurable = 1, | |
+ }, | |
+ | |
+ { | |
+ .type = NJS_PROPERTY, | |
+ .name = njs_string("length"), | |
+ .value = njs_value(NJS_NUMBER, 1, 1.0), | |
+ .configurable = 1, | |
+ }, | |
+ | |
+ { | |
+ .type = NJS_PROPERTY_HANDLER, | |
+ .name = njs_string("prototype"), | |
+ .value = njs_prop_handler(njs_object_prototype_create), | |
+ }, | |
+ | |
+ { | |
+ .type = NJS_PROPERTY, | |
+ .name = njs_string("resolve"), | |
+ .value = njs_native_function(njs_promise_object_resolve, 1), | |
+ .writable = 1, | |
+ .configurable = 1, | |
+ }, | |
+ | |
+ { | |
+ .type = NJS_PROPERTY, | |
+ .name = njs_string("reject"), | |
+ .value = njs_native_function(njs_promise_object_reject, 1), | |
+ .writable = 1, | |
+ .configurable = 1, | |
+ }, | |
+}; | |
+ | |
+ | |
+const njs_object_init_t njs_promise_constructor_init = { | |
+ njs_promise_constructor_properties, | |
+ njs_nitems(njs_promise_constructor_properties), | |
+}; | |
+ | |
+ | |
+static const njs_object_prop_t njs_promise_prototype_properties[] = | |
+{ | |
+ { | |
+ .type = NJS_PROPERTY_HANDLER, | |
+ .name = njs_string("constructor"), | |
+ .value = njs_prop_handler(njs_object_prototype_create_constructor), | |
+ .writable = 1, | |
+ .configurable = 1, | |
+ }, | |
+ | |
+ { | |
+ .type = NJS_PROPERTY, | |
+ .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG), | |
+ .value = njs_string("Promise"), | |
+ .configurable = 1, | |
+ }, | |
+ | |
+ { | |
+ .type = NJS_PROPERTY, | |
+ .name = njs_string("then"), | |
+ .value = njs_native_function(njs_promise_prototype_then, 2), | |
+ .writable = 1, | |
+ .configurable = 1, | |
+ }, | |
+ | |
+ { | |
+ .type = NJS_PROPERTY, | |
+ .name = njs_string("catch"), | |
+ .value = njs_native_function(njs_promise_prototype_catch, 1), | |
+ .writable = 1, | |
+ .configurable = 1, | |
+ }, | |
+ | |
+ { | |
+ .type = NJS_PROPERTY, | |
+ .name = njs_string("finally"), | |
+ .value = njs_native_function(njs_promise_prototype_finally, 1), | |
+ .writable = 1, | |
+ .configurable = 1, | |
+ }, | |
+}; | |
+ | |
+ | |
+const njs_object_init_t njs_promise_prototype_init = { | |
+ njs_promise_prototype_properties, | |
+ njs_nitems(njs_promise_prototype_properties), | |
+}; | |
+ | |
+const njs_object_type_init_t njs_promise_type_init = { | |
+ .constructor = njs_promise_constructor, | |
+ .prototype_props = &njs_promise_prototype_init, | |
+ .constructor_props = &njs_promise_constructor_init, | |
+ .value = { .object = { .type = NJS_OBJECT } }, | |
+}; | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 src/njs_promise.h | |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 | |
+++ b/src/njs_promise.h Fri Nov 29 18:27:20 2019 +0300 | |
@@ -0,0 +1,19 @@ | |
+ | |
+/* | |
+ * Copyright (C) Igor Sysoev | |
+ * Copyright (C) Nginx, Inc. | |
+ */ | |
+ | |
+#ifndef _NJS_PROMISE_H_INCLUDED_ | |
+#define _NJS_PROMISE_H_INCLUDED_ | |
+ | |
+ | |
+njs_int_t | |
+njs_promise_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, | |
+ njs_index_t unused); | |
+ | |
+ | |
+extern const njs_object_type_init_t njs_promise_type_init; | |
+ | |
+ | |
+#endif /* _NJS_PROMISE_H_INCLUDED_ */ | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 src/njs_shell.c | |
--- a/src/njs_shell.c Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_shell.c Fri Nov 29 18:27:20 2019 +0300 | |
@@ -108,6 +108,7 @@ static njs_int_t njs_ext_console_time_en | |
static njs_host_event_t njs_console_set_timer(njs_external_ptr_t external, | |
uint64_t delay, njs_vm_event_t vm_event); | |
+ | |
static void njs_console_clear_timer(njs_external_ptr_t external, | |
njs_host_event_t event); | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 src/njs_value.c | |
--- a/src/njs_value.c Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_value.c Fri Nov 29 18:27:20 2019 +0300 | |
@@ -359,6 +359,9 @@ njs_type_string(njs_value_type_t type) | |
case NJS_DATE: | |
return "date"; | |
+ case NJS_PROMISE: | |
+ return "promise"; | |
+ | |
default: | |
return NULL; | |
} | |
@@ -553,6 +556,7 @@ njs_property_query(njs_vm_t *vm, njs_pro | |
case NJS_OBJECT_STRING: | |
case NJS_REGEXP: | |
case NJS_DATE: | |
+ case NJS_PROMISE: | |
case NJS_OBJECT_VALUE: | |
obj = njs_object(value); | |
break; | |
@@ -1251,6 +1255,7 @@ njs_value_to_object(njs_vm_t *vm, njs_va | |
return NJS_ERROR; | |
} | |
+ | |
void | |
njs_symbol_conversion_failed(njs_vm_t *vm, njs_bool_t to_string) | |
{ | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 src/njs_value.h | |
--- a/src/njs_value.h Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_value.h Fri Nov 29 18:27:20 2019 +0300 | |
@@ -67,7 +67,8 @@ typedef enum { | |
NJS_FUNCTION = 0x16, | |
NJS_REGEXP = 0x17, | |
NJS_DATE = 0x18, | |
- NJS_OBJECT_VALUE = 0x19, | |
+ NJS_PROMISE = 0x19, | |
+ NJS_OBJECT_VALUE = 0x1A, | |
NJS_VALUE_TYPE_MAX | |
} njs_value_type_t; | |
@@ -81,6 +82,7 @@ typedef struct njs_regexp_pattern_s nj | |
typedef struct njs_array_s njs_array_t; | |
typedef struct njs_regexp_s njs_regexp_t; | |
typedef struct njs_date_s njs_date_t; | |
+typedef struct njs_object_value_s njs_promise_t; | |
typedef struct njs_property_next_s njs_property_next_t; | |
typedef struct njs_object_init_s njs_object_init_t; | |
@@ -143,6 +145,7 @@ union njs_value_s { | |
njs_function_lambda_t *lambda; | |
njs_regexp_t *regexp; | |
njs_date_t *date; | |
+ njs_promise_t *promise; | |
njs_prop_handler_t prop_handler; | |
njs_value_t *value; | |
njs_property_next_t *next; | |
@@ -251,6 +254,13 @@ struct njs_function_s { | |
njs_function_t *bound_target; | |
} u; | |
+ njs_value_t promise; | |
+ njs_value_t finally; | |
+ njs_value_t constructor; | |
+ njs_bool_t resolved; | |
+ njs_bool_t *resolved_ref; | |
+ void *capability; | |
+ | |
njs_value_t *bound; | |
#if (NJS_SUNC) | |
njs_closure_t *closures[1]; | |
@@ -285,6 +295,7 @@ typedef union { | |
njs_function_t function; | |
njs_regexp_t regexp; | |
njs_date_t date; | |
+ njs_promise_t promise; | |
} njs_object_prototype_t; | |
@@ -604,6 +615,10 @@ typedef struct { | |
((value)->type == NJS_DATE) | |
+#define njs_is_promise(value) \ | |
+ ((value)->type == NJS_PROMISE) | |
+ | |
+ | |
#define njs_is_error(value) \ | |
((value)->type == NJS_OBJECT && njs_object(value)->error_data) | |
@@ -664,6 +679,10 @@ typedef struct { | |
((value)->data.u.date) | |
+#define njs_promise(value) \ | |
+ ((value)->data.u.promise) | |
+ | |
+ | |
#define njs_regexp(value) \ | |
((value)->data.u.regexp) | |
@@ -822,6 +841,15 @@ njs_set_date(njs_value_t *value, njs_dat | |
njs_inline void | |
+njs_set_promise(njs_value_t *value, njs_promise_t *promise) | |
+{ | |
+ value->data.u.promise = promise; | |
+ value->type = NJS_PROMISE; | |
+ value->data.truth = 1; | |
+} | |
+ | |
+ | |
+njs_inline void | |
njs_set_regexp(njs_value_t *value, njs_regexp_t *regexp) | |
{ | |
value->data.u.regexp = regexp; | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 src/njs_vm.c | |
--- a/src/njs_vm.c Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_vm.c Fri Nov 29 18:27:20 2019 +0300 | |
@@ -304,6 +304,7 @@ njs_vm_init(njs_vm_t *vm) | |
njs_lvlhsh_init(&vm->events_hash); | |
njs_queue_init(&vm->posted_events); | |
+ njs_queue_init(&vm->promise_events); | |
return NJS_OK; | |
} | |
@@ -433,7 +434,7 @@ njs_vm_waiting(njs_vm_t *vm) | |
njs_int_t | |
njs_vm_posted(njs_vm_t *vm) | |
{ | |
- return njs_posted_events(vm); | |
+ return njs_posted_events(vm) || njs_promise_events(vm); | |
} | |
@@ -493,6 +494,26 @@ njs_vm_handle_events(njs_vm_t *vm) | |
njs_queue_t *events; | |
njs_queue_link_t *link; | |
+ events = &vm->promise_events; | |
+ | |
+ for ( ;; ) { | |
+ link = njs_queue_first(events); | |
+ | |
+ if (link == njs_queue_tail(events)) { | |
+ break; | |
+ } | |
+ | |
+ ev = njs_queue_link_data(link, njs_event_t, link); | |
+ | |
+ ev->posted = 0; | |
+ njs_queue_remove(&ev->link); | |
+ | |
+ ret = njs_vm_call(vm, ev->function, ev->args, ev->nargs); | |
+ if (njs_slow_path(ret == NJS_ERROR)) { | |
+ return ret; | |
+ } | |
+ } | |
+ | |
events = &vm->posted_events; | |
for ( ;; ) { | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 src/njs_vm.h | |
--- a/src/njs_vm.h Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_vm.h Fri Nov 29 18:27:20 2019 +0300 | |
@@ -89,6 +89,7 @@ typedef enum { | |
NJS_OBJ_TYPE_FUNCTION, | |
NJS_OBJ_TYPE_REGEXP, | |
NJS_OBJ_TYPE_DATE, | |
+ NJS_OBJ_TYPE_PROMISE, | |
NJS_OBJ_TYPE_CRYPTO_HASH, | |
#define NJS_OBJ_TYPE_HIDDEN_MIN (NJS_OBJ_TYPE_CRYPTO_HASH) | |
NJS_OBJ_TYPE_CRYPTO_HMAC, | |
@@ -186,6 +187,7 @@ struct njs_vm_s { | |
uint32_t event_id; | |
njs_lvlhsh_t events_hash; | |
njs_queue_t posted_events; | |
+ njs_queue_t promise_events; | |
njs_vm_opt_t options; | |
diff -r 01c7375c9b5c -r ef2e7a062ba4 src/njs_vmcode.c | |
--- a/src/njs_vmcode.c Fri Nov 29 12:53:33 2019 +0300 | |
+++ b/src/njs_vmcode.c Fri Nov 29 18:27:20 2019 +0300 | |
@@ -1415,6 +1415,7 @@ njs_vmcode_typeof(njs_vm_t *vm, njs_valu | |
&njs_string_object, | |
&njs_string_object, | |
&njs_string_object, | |
+ &njs_string_object, | |
}; | |
vm->retval = *types[value->type]; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment