[njs] Introduced the Promise object.

Alexander Borisov alexander.borisov at nginx.com
Mon Dec 23 15:53:30 UTC 2019


details:   https://hg.nginx.org/njs/rev/61bf7a31e685
branches:  
changeset: 1287:61bf7a31e685
user:      Alexander Borisov <alexander.borisov at 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);


More information about the nginx-devel mailing list