]> git.kaiwu.me - njs.git/commitdiff
Introduced the Promise object.
authorAlexander Borisov <alexander.borisov@nginx.com>
Tue, 17 Dec 2019 07:35:11 +0000 (10:35 +0300)
committerAlexander Borisov <alexander.borisov@nginx.com>
Tue, 17 Dec 2019 07:35:11 +0000 (10:35 +0300)
Implemented according to the specification without: Promise.all(),
Promise.allSettled(), Promise.race().

This closes #39 issue on GitHub.

47 files changed:
auto/sources
src/njs_builtin.c
src/njs_date.c
src/njs_event.h
src/njs_function.c
src/njs_function.h
src/njs_json.c
src/njs_main.h
src/njs_object.c
src/njs_object_hash.h
src/njs_promise.c [new file with mode: 0644]
src/njs_promise.h [new file with mode: 0644]
src/njs_shell.c
src/njs_value.c
src/njs_value.h
src/njs_vm.c
src/njs_vm.h
src/njs_vmcode.c
src/njs_vmcode.h
test/js/promise_s1.js [new file with mode: 0644]
test/js/promise_s10.js [new file with mode: 0644]
test/js/promise_s11.js [new file with mode: 0644]
test/js/promise_s12.js [new file with mode: 0644]
test/js/promise_s13.js [new file with mode: 0644]
test/js/promise_s14.js [new file with mode: 0644]
test/js/promise_s15.js [new file with mode: 0644]
test/js/promise_s16.js [new file with mode: 0644]
test/js/promise_s17.js [new file with mode: 0644]
test/js/promise_s18.js [new file with mode: 0644]
test/js/promise_s19.js [new file with mode: 0644]
test/js/promise_s2.js [new file with mode: 0644]
test/js/promise_s20.js [new file with mode: 0644]
test/js/promise_s21.js [new file with mode: 0644]
test/js/promise_s22.js [new file with mode: 0644]
test/js/promise_s23.js [new file with mode: 0644]
test/js/promise_s24.js [new file with mode: 0644]
test/js/promise_s25.js [new file with mode: 0644]
test/js/promise_s26.js [new file with mode: 0644]
test/js/promise_s3.js [new file with mode: 0644]
test/js/promise_s4.js [new file with mode: 0644]
test/js/promise_s5.js [new file with mode: 0644]
test/js/promise_s6.js [new file with mode: 0644]
test/js/promise_s7.js [new file with mode: 0644]
test/js/promise_s8.js [new file with mode: 0644]
test/js/promise_s9.js [new file with mode: 0644]
test/js/promise_set_timeout.js [new file with mode: 0644]
test/njs_expect_test.exp

index c289fe394ead2bd0d385841b67d4dd0c2d742ea6..d7b39ece8e8c9e7d398609bbc7ea59f90ddb8f66 100644 (file)
@@ -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=" \
index 9c6b808e1139d98cd754697bae2944e631fd15c4..4825a5810beea846348384035ad4a2461d086a25 100644 (file)
@@ -66,6 +66,7 @@ static const njs_object_type_init_t *const
     &njs_function_type_init,
     &njs_regexp_type_init,
     &njs_date_type_init,
+    &njs_promise_type_init,
 
     /* Hidden types. */
 
@@ -1171,6 +1172,15 @@ static const njs_object_prop_t  njs_global_this_object_properties[] =
         .configurable = 1,
     },
 
+    {
+        .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"),
index cefe755586bef06ccf91a85f1f5a7e1a324f3b70..41c84bd7ebd3482c0ea7ef9c8699d86557f13719 100644 (file)
@@ -1840,3 +1840,4 @@ const njs_object_type_init_t  njs_date_type_init = {
    .prototype_props = &njs_date_prototype_init,
    .prototype_value = { .object = { .type = NJS_OBJECT } },
 };
+
index 64baad8654103bde17164ad8253ab25c4d5d39a2..9ca3ac08702949bc2df128a6f6783db556e99b2f 100644 (file)
@@ -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;
index 46850e25a3b6cbd0011daba153ffdb59c4e52b1b..9e6cfba7b17ca27448c7968a1f7c577fa80aa8d7 100644 (file)
@@ -539,14 +539,14 @@ njs_function_frame_alloc(njs_vm_t *vm, size_t size)
 
 
 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;
     }
index a659a4528f476e88bfc3e03d1af3dbb9cb9d7123..d736508b4a788b6b29b48bbe40a596002591fcaa 100644 (file)
@@ -115,9 +115,9 @@ njs_int_t njs_function_native_frame(njs_vm_t *vm, njs_function_t *function,
 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);
@@ -185,12 +185,21 @@ njs_function_frame_invoke(njs_vm_t *vm, njs_index_t retval)
 }
 
 
+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);
 }
 
 
index e740070fb555c374d4567aec6cd08f38ab353f70..f8e10eeb9c8634a56ce211505515f93e24d8446f 100644 (file)
@@ -1904,6 +1904,7 @@ njs_dump_is_object(const njs_value_t *value)
     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);
 }
index 5fc40c74436497717fea87aea8917fc26bd03180..6bc39614006ecc24bd573e7685ebb031b9478e00 100644 (file)
@@ -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>
index c42bc1526f93d08dc907188a15046e20b1416ca1..41fa88f8a7c7e9d35f0c4e9e4ca631d646aba077 100644 (file)
@@ -2410,6 +2410,7 @@ njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
         &njs_object_regexp_string,
         &njs_object_date_string,
         &njs_object_object_string,
+        &njs_object_object_string,
         &njs_object_array_buffer_string,
     };
 
index 9049ba3fd693280b5705d96b1efa6abfc0dd86a7..1d845dfae9159293c17863d5addf2207f86a36dc 100644 (file)
         '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 --git a/src/njs_promise.c b/src/njs_promise.c
new file mode 100644 (file)
index 0000000..6c88889
--- /dev/null
@@ -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);
+
+    fulfilled_reaction = njs_mp_alloc(vm->mem_pool,
+                                      sizeof(njs_promise_reaction_t));
+    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_mp_alloc(vm->mem_pool,
+                                     sizeof(njs_promise_reaction_t));
+    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;
+    njs_promise_context_t  *context;
+
+    promise = njs_arg(args, nargs, 0);
+
+    if (njs_slow_path(!njs_is_object(promise))) {
+        njs_type_error(vm, "required a 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_value_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->args_count = 1;
+
+    context = function->context;
+    context->constructor = constructor;
+    context->finally = *finally;
+
+    njs_set_function(&arguments[0], function);
+
+    function = njs_promise_create_function(vm);
+    if (njs_slow_path(function == NULL)) {
+        njs_mp_free(vm->mem_pool, njs_function(&arguments[0]));
+        return NJS_ERROR;
+    }
+
+    function->u.native = njs_promise_catch_finally_function;
+    function->args_count = 1;
+
+    context = function->context;
+    context->constructor = constructor;
+    context->finally = *finally;
+
+    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_promise_context_t  *context;
+
+    frame = (njs_frame_t *) vm->top_frame;
+    context = frame->native.function->context;
+
+    ret = njs_function_call(vm, njs_function(&context->finally),
+                            &njs_value_undefined, args, 0, &retval);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    promise = njs_promise_resolve(vm, &context->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, &vm->retval))) {
+                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];
+
+    promise = njs_arg(args, nargs, 1);
+
+    ret = njs_promise_create_resolving_functions(vm, njs_promise(promise),
+                                                 arguments);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    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, &vm->retval))) {
+            return NJS_ERROR;
+        }
+
+        ret = njs_function_call(vm, njs_function(&arguments[1]),
+                                &njs_value_undefined, &vm->retval, 1,
+                                &vm->retval);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+    }
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
+njs_promise_species(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
+    njs_index_t unused)
+{
+    njs_vm_retval_set(vm, njs_arg(args, nargs, 0));
+
+    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,
+    },
+
+    {
+        .type = NJS_PROPERTY,
+        .name = njs_wellknown_symbol(NJS_SYMBOL_SPECIES),
+        .value = njs_value(NJS_INVALID, 1, NAN),
+        .getter = njs_native_function(njs_promise_species, 0),
+        .setter = njs_value(NJS_UNDEFINED, 0, NAN),
+        .writable = NJS_ATTRIBUTE_UNSET,
+        .configurable = 1,
+        .enumerable = 0,
+    },
+};
+
+
+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_native_ctor(njs_promise_constructor, 1, 0),
+    .prototype_props = &njs_promise_prototype_init,
+    .constructor_props = &njs_promise_constructor_init,
+    .prototype_value = { .object = { .type = NJS_OBJECT } },
+};
diff --git a/src/njs_promise.h b/src/njs_promise.h
new file mode 100644 (file)
index 0000000..bb645f3
--- /dev/null
@@ -0,0 +1,18 @@
+
+/*
+ * 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_ */
index d4378990b91a0647e04b65b173a5699267d56834..f509ce3dec4792830ebcc980c0381a4d327a8ad1 100644 (file)
@@ -109,6 +109,7 @@ static njs_int_t njs_ext_console_time_end(njs_vm_t *vm, njs_value_t *args,
 
 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);
 
index a861889d7fc0e26fc4842a9fb8a9d00557551c8e..e015c213e7e97b33692862c57a193efcbed73cf3 100644 (file)
@@ -362,6 +362,9 @@ njs_type_string(njs_value_type_t type)
     case NJS_DATE:
         return "date";
 
+    case NJS_PROMISE:
+        return "promise";
+
     default:
         return NULL;
     }
@@ -557,6 +560,7 @@ njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *value,
     case NJS_OBJECT_STRING:
     case NJS_REGEXP:
     case NJS_DATE:
+    case NJS_PROMISE:
     case NJS_OBJECT_VALUE:
         obj = njs_object(value);
         break;
index 0af5dae9c27dbec35f5348711d9e4554c307d838..28ed7640059e10d53fd71529aa595ce0c5597a22 100644 (file)
@@ -67,8 +67,9 @@ typedef enum {
     NJS_FUNCTION              = 0x16,
     NJS_REGEXP                = 0x17,
     NJS_DATE                  = 0x18,
-    NJS_OBJECT_VALUE          = 0x19,
-    NJS_ARRAY_BUFFER          = 0x1A,
+    NJS_PROMISE               = 0x19,
+    NJS_OBJECT_VALUE          = 0x1A,
+    NJS_ARRAY_BUFFER          = 0x1B,
     NJS_VALUE_TYPE_MAX
 } njs_value_type_t;
 
@@ -83,6 +84,7 @@ typedef struct njs_array_s            njs_array_t;
 typedef struct njs_array_buffer_s     njs_array_buffer_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;
 
@@ -146,6 +148,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;
@@ -283,6 +286,8 @@ struct njs_function_s {
         njs_function_t                *bound_target;
     } u;
 
+    void                              *context;
+
     njs_value_t                       *bound;
 };
 
@@ -312,6 +317,7 @@ typedef union {
     njs_function_t                    function;
     njs_regexp_t                      regexp;
     njs_date_t                        date;
+    njs_promise_t                     promise;
 } njs_object_prototype_t;
 
 
@@ -644,6 +650,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)
 
@@ -708,6 +718,10 @@ typedef struct {
     ((value)->data.u.date)
 
 
+#define njs_promise(value)                                                    \
+    ((value)->data.u.promise)
+
+
 #define njs_regexp(value)                                                     \
     ((value)->data.u.regexp)
 
@@ -874,6 +888,15 @@ njs_set_date(njs_value_t *value, njs_date_t *date)
 }
 
 
+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)
 {
index cd2953d609eda4ae8bb2e4dd2042a99d455eb49d..13b3ae7678bf02c2245708afb79fdefb76e7fe5a 100644 (file)
@@ -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;
 }
@@ -435,7 +436,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);
 }
 
 
@@ -495,31 +496,53 @@ njs_vm_handle_events(njs_vm_t *vm)
     njs_queue_t       *events;
     njs_queue_link_t  *link;
 
-    events = &vm->posted_events;
+    do {
+        events = &vm->promise_events;
 
-    for ( ;; ) {
-        link = njs_queue_first(events);
-
-        if (link == njs_queue_tail(events)) {
-            break;
-        }
+        for ( ;; ) {
+            link = njs_queue_first(events);
 
-        ev = njs_queue_link_data(link, njs_event_t, link);
+            if (link == njs_queue_tail(events)) {
+                break;
+            }
 
-        if (ev->once) {
-            njs_del_event(vm, ev, NJS_EVENT_RELEASE | NJS_EVENT_DELETE);
+            ev = njs_queue_link_data(link, njs_event_t, link);
 
-        } else {
-            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;
+            }
         }
 
-        ret = njs_vm_call(vm, ev->function, ev->args, ev->nargs);
+        events = &vm->posted_events;
 
-        if (ret == NJS_ERROR) {
-            return ret;
+        for ( ;; ) {
+            link = njs_queue_first(events);
+
+            if (link == njs_queue_tail(events)) {
+                break;
+            }
+
+            ev = njs_queue_link_data(link, njs_event_t, link);
+
+            if (ev->once) {
+                njs_del_event(vm, ev, NJS_EVENT_RELEASE | NJS_EVENT_DELETE);
+
+            } else {
+                ev->posted = 0;
+                njs_queue_remove(&ev->link);
+            }
+
+            ret = njs_vm_call(vm, ev->function, ev->args, ev->nargs);
+
+            if (ret == NJS_ERROR) {
+                return ret;
+            }
         }
-    }
+
+    } while (!njs_queue_is_empty(events));
 
     return njs_posted_events(vm) ? NJS_AGAIN : NJS_OK;
 }
index 612924d230083d812750e5957b7387776c8344b4..5167ff805f5a2818dac1efa26422150719002e96 100644 (file)
@@ -90,6 +90,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,
@@ -187,6 +188,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;
 
index 5f6e36e4e3d77be578d70a55e19ca04466584f16..b5de9966fab1e4ce871b799ff5f6bb3e97ca3ca6 100644 (file)
@@ -66,8 +66,6 @@ static njs_jump_off_t njs_primitive_values_compare(njs_vm_t *vm,
 static njs_jump_off_t njs_function_frame_create(njs_vm_t *vm,
     njs_value_t *value, const njs_value_t *this, uintptr_t nargs,
     njs_bool_t ctor);
-static njs_object_t *njs_function_new_object(njs_vm_t *vm,
-    njs_value_t *constructor);
 
 /*
  * The nJSVM is optimized for an ABIs where the first several arguments
@@ -1421,6 +1419,7 @@ njs_vmcode_typeof(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
         &njs_string_object,
         &njs_string_object,
         &njs_string_object,
+        &njs_string_object,
     };
 
     vm->retval = *types[value->type];
@@ -1624,7 +1623,7 @@ njs_function_frame_create(njs_vm_t *vm, njs_value_t *value,
 }
 
 
-static njs_object_t *
+njs_object_t *
 njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor)
 {
     njs_value_t     proto, bound;
index b6e4062bed846067a37976e7d90db6a6cc28c1bf..1c90112a4b8727272c3dceaf164f4878c2cddc37 100644 (file)
@@ -394,5 +394,7 @@ typedef struct {
 
 njs_int_t njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc);
 
+njs_object_t *njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor);
+
 
 #endif /* _NJS_VMCODE_H_INCLUDED_ */
diff --git a/test/js/promise_s1.js b/test/js/promise_s1.js
new file mode 100644 (file)
index 0000000..308dbe2
--- /dev/null
@@ -0,0 +1,15 @@
+var promise = new Promise(function(resolve, reject) {
+    console.log('One');
+
+    reject(123);
+}).catch((v) => {console.log(v)});
+
+console.log('Two');
+
+promise.then(() => {console.log('Four'); return {num: 'Five'}})
+.then((obj) => {console.log(obj.num);  return {num: 'Six'}})
+.then((obj) => {console.log(obj.num);  return {num: 'Seven'}})
+.then((obj) => {console.log(obj.num);  return {num: 'Eight'}})
+.then((obj) => {console.log(obj.num)});
+
+console.log('Three');
\ No newline at end of file
diff --git a/test/js/promise_s10.js b/test/js/promise_s10.js
new file mode 100644 (file)
index 0000000..6fd1d6b
--- /dev/null
@@ -0,0 +1,11 @@
+var promise = new Promise(function(resolve, reject) {
+    console.log('One');
+    reject('Oh no');
+});
+
+console.log('Two');
+
+promise.then(() => {console.log('Three')})
+.catch((v) => {console.log(v)});
+
+console.log('Three');
\ No newline at end of file
diff --git a/test/js/promise_s11.js b/test/js/promise_s11.js
new file mode 100644 (file)
index 0000000..ecffccc
--- /dev/null
@@ -0,0 +1,13 @@
+var promise = new Promise((resolve, reject) => resolve('all'));
+
+promise.then( function f1(result) {
+    console.log('S: ' + result);
+    return 'f1';
+});
+
+promise.then( function f2(result) {
+    console.log('R: ' + result);
+    return 'f2';
+});
+
+console.log('end')
\ No newline at end of file
diff --git a/test/js/promise_s12.js b/test/js/promise_s12.js
new file mode 100644 (file)
index 0000000..e157b47
--- /dev/null
@@ -0,0 +1,10 @@
+var thenable = new Promise(function() {});
+
+var p = new Promise(function(resolve) {
+    resolve();
+    throw thenable;
+});
+
+p.then(function() {
+    console.log('Done');
+});
\ No newline at end of file
diff --git a/test/js/promise_s13.js b/test/js/promise_s13.js
new file mode 100644 (file)
index 0000000..4ace323
--- /dev/null
@@ -0,0 +1,21 @@
+var thenable = Promise.resolve();
+var p = new Promise(function(a,b) {
+    throw thenable;
+});
+
+p.then(function() {
+    console.log('The promise should not be fulfilled.');
+})
+.then(
+    function() {
+        console.log('The promise should not be fulfilled.');
+    },
+    function(x) {
+        if (x !== thenable) {
+            console.log('The promise should be rejected with the resolution value.');
+            return;
+        }
+
+        console.log('Done');
+    }
+);
\ No newline at end of file
diff --git a/test/js/promise_s14.js b/test/js/promise_s14.js
new file mode 100644 (file)
index 0000000..63494ee
--- /dev/null
@@ -0,0 +1,9 @@
+var isLoading = true;
+var promise = new Promise((a, b) => {a()} );
+
+promise.then(function(response) {
+    throw new TypeError('oops');
+})
+.then(function(json) { })
+.catch(function(error) { console.log(error); })
+.finally(function() { console.log('Done') });
\ No newline at end of file
diff --git a/test/js/promise_s15.js b/test/js/promise_s15.js
new file mode 100644 (file)
index 0000000..5b134bd
--- /dev/null
@@ -0,0 +1,10 @@
+var obj = {};
+var p = Promise.resolve(obj);
+
+p.then(undefined, undefined)
+.then(function(arg) {
+    if (arg !== obj) {
+        console.log('Expected resolution object to be passed through, got ' + arg);
+    }
+})
+.then(() => {console.log('Done')}, () => {console.log('Error')});
\ No newline at end of file
diff --git a/test/js/promise_s16.js b/test/js/promise_s16.js
new file mode 100644 (file)
index 0000000..769d38a
--- /dev/null
@@ -0,0 +1,10 @@
+var obj = {};
+var p = Promise.reject(obj);
+
+p.then(undefined, undefined).then(function() {
+    console.log('Should not be called -- promise was rejected.');
+}, function(arg) {
+    if (arg !== obj) {
+        console.log('Expected resolution object to be passed through, got ' + arg);
+    }
+}).then(() => {console.log('Done')}, () => {console.log('Error')});
diff --git a/test/js/promise_s17.js b/test/js/promise_s17.js
new file mode 100644 (file)
index 0000000..769d38a
--- /dev/null
@@ -0,0 +1,10 @@
+var obj = {};
+var p = Promise.reject(obj);
+
+p.then(undefined, undefined).then(function() {
+    console.log('Should not be called -- promise was rejected.');
+}, function(arg) {
+    if (arg !== obj) {
+        console.log('Expected resolution object to be passed through, got ' + arg);
+    }
+}).then(() => {console.log('Done')}, () => {console.log('Error')});
diff --git a/test/js/promise_s18.js b/test/js/promise_s18.js
new file mode 100644 (file)
index 0000000..10d1064
--- /dev/null
@@ -0,0 +1,23 @@
+var thenable = {
+    then: function(resolve) {
+        resolve();
+        console.log('State 5');
+    }
+};
+
+var thenableWithError = {
+    then: function(resolve) {
+        console.log('State 3');
+        resolve(thenable);
+        console.log('State 4');
+        throw new Error('ignored exception');
+    }
+};
+
+function executor(resolve, reject) {
+    console.log('State 1');
+    resolve(thenableWithError);
+    console.log('State 2');
+}
+
+new Promise(executor).then(() => {console.log('Done')}, () => {console.log('Error')});
\ No newline at end of file
diff --git a/test/js/promise_s19.js b/test/js/promise_s19.js
new file mode 100644 (file)
index 0000000..451f710
--- /dev/null
@@ -0,0 +1,33 @@
+var returnValue = null;
+var value = {};
+var resolve;
+
+var poisonedThen = Object.defineProperty({}, 'then', {
+    get: function() {
+        console.log('Throw!');
+        throw value;
+    }
+});
+
+var promise = new Promise(function(_resolve) {
+    resolve = _resolve;
+});
+
+promise.then(
+function() {
+    console.log('Resolve!');
+    console.log('The promise should not be fulfilled.');
+},
+function(val) {
+    console.log('Reject!');
+    if (val !== value) {
+        console.log('The promise should be fulfilled with the provided value.');
+        return;
+    }
+
+    console.log('Done');
+});
+
+returnValue = resolve(poisonedThen);
+
+console.log(returnValue);
\ No newline at end of file
diff --git a/test/js/promise_s2.js b/test/js/promise_s2.js
new file mode 100644 (file)
index 0000000..92d92dd
--- /dev/null
@@ -0,0 +1,14 @@
+var promise = new Promise(function(resolve, reject) {
+    console.log('One');
+    reject(123);
+});
+
+console.log('Two');
+
+promise.then(() => {console.log('Four'); return {num: 'Five'}})
+.then((obj) => {console.log(obj.num);  return {num: 'Six'}})
+.then((obj) => {console.log(obj.num);  return {num: 'Seven'}})
+.then((obj) => {console.log(obj.num);  return {num: 'Eight'}})
+.then((obj) => {console.log(obj.num)});
+
+console.log('Three');
\ No newline at end of file
diff --git a/test/js/promise_s20.js b/test/js/promise_s20.js
new file mode 100644 (file)
index 0000000..0934bba
--- /dev/null
@@ -0,0 +1,23 @@
+var returnValue = null;
+var value = {};
+var poisonedThen = Object.defineProperty({}, 'then', {
+    get: function() {
+        throw value;
+    }
+});
+var promise = new Promise(function(resolve) {
+    returnValue = resolve(poisonedThen);
+});
+
+console.log(returnValue == undefined);
+
+promise.then(function() {
+    console.log('The promise should not be fulfilled.');
+}, function(val) {
+    if (val !== value) {
+        console.log('The promise should be fulfilled with the provided value.');
+        return;
+    }
+
+    console.log('Done');
+});
\ No newline at end of file
diff --git a/test/js/promise_s21.js b/test/js/promise_s21.js
new file mode 100644 (file)
index 0000000..aad00cc
--- /dev/null
@@ -0,0 +1,30 @@
+var value = {};
+var resolve;
+var poisonedThen = Object.defineProperty({}, 'then', {
+    get: function() {
+        throw value;
+    }
+});
+
+var p1 = new Promise(function(_resolve) {
+    resolve = _resolve;
+});
+
+var p2;
+
+p2 = p1.then(function() {
+    return poisonedThen;
+});
+
+p2.then(function(x) {
+    console.log('The promise should not be fulfilled.');
+}, function(x) {
+    if (x !== value) {
+        console.log('The promise should be rejected with the thrown exception.');
+        return;
+    }
+
+    console.log('Done');
+});
+
+resolve();
\ No newline at end of file
diff --git a/test/js/promise_s22.js b/test/js/promise_s22.js
new file mode 100644 (file)
index 0000000..9dee7c1
--- /dev/null
@@ -0,0 +1,32 @@
+var returnValue = null;
+var value = {};
+var resolve;
+
+var thenable = new Promise(function(resolve) {
+    resolve();
+});
+
+var promise = new Promise(function(_resolve) {
+    resolve = _resolve;
+});
+
+thenable.then = function(resolve) {
+    resolve(value);
+};
+
+promise.then(
+function(val) {
+    if (val !== value) {
+        console.log('The promise should be fulfilled with the provided value.');
+        return;
+    }
+
+    console.log('Done');
+},
+function() {
+    console.log('The promise should not be rejected.');
+});
+
+returnValue = resolve(thenable);
+
+console.log(returnValue == undefined);
\ No newline at end of file
diff --git a/test/js/promise_s23.js b/test/js/promise_s23.js
new file mode 100644 (file)
index 0000000..8525cde
--- /dev/null
@@ -0,0 +1,28 @@
+var returnValue = null;
+var resolve;
+
+var promise = new Promise(function(_resolve) {
+    resolve = _resolve;
+});
+
+promise.then(
+function() {
+    console.log('The promise should not be fulfilled.');
+},
+function(reason) {
+    if (!reason) {
+        console.log('The promise should be rejected with a value.');
+        return;
+    }
+
+    if (reason.constructor !== TypeError) {
+        console.log('The promise should be rejected with a TypeError instance.');
+        return;
+    }
+
+    console.log('Done');
+});
+
+returnValue = resolve(promise);
+
+console.log(returnValue == undefined)
\ No newline at end of file
diff --git a/test/js/promise_s24.js b/test/js/promise_s24.js
new file mode 100644 (file)
index 0000000..bb3dddf
--- /dev/null
@@ -0,0 +1,13 @@
+var checkPoint = '';
+
+Promise.reject.call(function(executor) {
+    checkPoint += 'a';
+    executor();
+
+    checkPoint += 'b';
+    executor(function() {}, function() {});
+
+    checkPoint += 'c';
+}, {});
+
+console.log(checkPoint == 'abc');
\ No newline at end of file
diff --git a/test/js/promise_s25.js b/test/js/promise_s25.js
new file mode 100644 (file)
index 0000000..3b70e46
--- /dev/null
@@ -0,0 +1,29 @@
+var resolve, reject;
+var promise = new Promise(function(_resolve, _reject) {
+    resolve = _resolve;
+    reject = _reject;
+});
+
+var P = function(executor) {
+    executor(resolve, reject);
+    return promise;
+};
+
+Promise.resolve.call(P, promise)
+.then(
+function() {
+    console.log('The promise should not be fulfilled.');
+},
+function(value) {
+    if (!value) {
+        console.log('The promise should be rejected with a value.');
+        return;
+    }
+
+    if (value.constructor !== TypeError) {
+        console.log('The promise should be rejected with a TypeError instance.');
+        return;
+    }
+
+    console.log('Done');
+});
\ No newline at end of file
diff --git a/test/js/promise_s26.js b/test/js/promise_s26.js
new file mode 100644 (file)
index 0000000..f5244d3
--- /dev/null
@@ -0,0 +1,144 @@
+var inherits = (child, parent) => {
+    child.prototype = Object.create(parent.prototype, {
+        constructor: {
+            value: child,
+            enumerable: false,
+            writable: true,
+            configurable: true
+        }
+    });
+    Object.setPrototypeOf(child, parent);
+};
+
+
+var BoxedPromise = (() => {
+    function BoxedPromise(executor) {
+        console.log('BoxedPromise.constructor');
+
+        if (!(this instanceof BoxedPromise)) {
+            return Promise(executor);
+        }
+
+        if (typeof executor !== 'function') {
+            return new Promise(executor);
+        }
+
+        var context, args;
+        var promise = new Promise(wrappedExecutor);
+        this.boxed = promise;
+
+        try {
+            executor.apply(context, args);
+
+        } catch (e) {
+            args[1](e);
+        }
+
+        function wrappedExecutor(resolve, reject) {
+            context = this;
+            args = [wrappedResolve, wrappedReject];
+            function wrappedResolve(val) {
+                return resolve(val);
+            }
+            function wrappedReject(val) {
+                return reject(val);
+            }
+        }
+    }
+
+    inherits(BoxedPromise, Promise);
+
+    BoxedPromise.prototype.then = function(res, rej) {
+        console.log('BoxedPromise.prototype.then');
+        var rs = Object.create(Object.getPrototypeOf(this));
+        rs.boxed = this.boxed.then(res, rej);
+        return rs;
+    };
+
+    return BoxedPromise;
+})();
+
+
+var PatchedPromise = (() => {
+    function PatchedPromise(executor) {
+        console.log('PatchedPromise.constructor');
+
+        if (!(this instanceof PatchedPromise)) {
+            return Promise(executor);
+        }
+
+        if (typeof executor !== 'function') {
+            return new Promise(executor);
+        }
+
+        var context, args;
+        var promise = new Promise(wrappedExecutor);
+        Object.setPrototypeOf(promise, PatchedPromise.prototype);
+
+        try {
+            executor.apply(context, args);
+
+        } catch (e) {
+            args[1](e);
+        }
+
+        return promise;
+
+        function wrappedExecutor(resolve, reject) {
+            context = this;
+            args = [wrappedResolve, wrappedReject];
+            function wrappedResolve(val) {
+                return resolve(val);
+            }
+            function wrappedReject(val) {
+                return reject(val);
+            }
+        }
+    }
+
+    inherits(PatchedPromise, Promise);
+
+    return PatchedPromise;
+})();
+
+
+var testSubclass = (Class, name) => {
+    return new Promise((resolve) => {
+        var resolved = Class.resolve(name)
+            .then((x) => console.log('resolved', name));
+
+        console.log(name, 'resolve', resolved instanceof Class ? 'OK' : 'failed');
+
+
+        var rejected = Class.reject(name)
+            .catch((x) => console.log('rejected', name));
+
+        console.log(name, 'reject', rejected instanceof Class ? 'OK' : 'failed');
+
+
+        var instance = new Class((resolve) => {
+            setImmediate(() => resolve(name));
+        });
+
+        var chain = instance
+            .then((x) => { console.log('then', x); return x; })
+            .then((x) => { console.log('then', x); return x; });
+
+        console.log(name, 'chain', chain instanceof Class ? 'OK' : 'failed');
+
+        var fin = chain
+            .finally(() => console.log('finally', name));
+
+        console.log(name, 'finally', fin instanceof Class ? 'OK' : 'failed');
+
+        console.log(name, 'sync done\n');
+
+        fin
+            .then(() => console.log(name, 'async done\n'))
+            .then(resolve);
+    });
+};
+
+Promise.resolve()
+    .then(() => testSubclass(BoxedPromise, 'BoxedPromise'))
+    .then(() => testSubclass(PatchedPromise, 'PatchedPromise'));
diff --git a/test/js/promise_s3.js b/test/js/promise_s3.js
new file mode 100644 (file)
index 0000000..8e71fe3
--- /dev/null
@@ -0,0 +1,11 @@
+
+var promise = new Promise(function(resolve, reject) {
+    console.log('One');
+    reject(new Error('Blah'));
+});
+
+console.log('Two');
+
+promise.then((response) => console.log(`Fulfilled: ${response}`), (error) => console.log(`Rejected: ${error}`));
+
+console.log('Three');
\ No newline at end of file
diff --git a/test/js/promise_s4.js b/test/js/promise_s4.js
new file mode 100644 (file)
index 0000000..ce52e6b
--- /dev/null
@@ -0,0 +1,6 @@
+Promise.reject(new Error('Oh my')).then(function(success) {
+},
+function(error) {
+    console.log(error);
+    throw error;
+});
\ No newline at end of file
diff --git a/test/js/promise_s5.js b/test/js/promise_s5.js
new file mode 100644 (file)
index 0000000..91995f9
--- /dev/null
@@ -0,0 +1,7 @@
+
+Promise.resolve('Success').then(function(value) {
+    console.log(value);
+},
+function(value) {
+    console.log('ignored');
+});
\ No newline at end of file
diff --git a/test/js/promise_s6.js b/test/js/promise_s6.js
new file mode 100644 (file)
index 0000000..9451c6e
--- /dev/null
@@ -0,0 +1,4 @@
+var p = Promise.resolve([1,2,3]);
+p.then(function(v) {
+    console.log(v[0]);
+});
\ No newline at end of file
diff --git a/test/js/promise_s7.js b/test/js/promise_s7.js
new file mode 100644 (file)
index 0000000..5ecc378
--- /dev/null
@@ -0,0 +1,12 @@
+var p1 = Promise.resolve({
+    then: function(onFulfill, onReject) { onFulfill('fulfilled!'); }
+});
+
+console.log(p1 instanceof Promise);
+
+p1.then(function(v) {
+    console.log(v);
+},
+function(e) {
+    console.log('ignored');
+});
\ No newline at end of file
diff --git a/test/js/promise_s8.js b/test/js/promise_s8.js
new file mode 100644 (file)
index 0000000..da87ff7
--- /dev/null
@@ -0,0 +1,13 @@
+var thenable = {
+    then: function(resolve) {
+        console.log('Ok')
+        resolve();
+    }
+};
+
+function executor(resolve, reject) {
+    resolve(thenable);
+    throw new Error('ignored');
+}
+
+new Promise(executor).then(() => {});
\ No newline at end of file
diff --git a/test/js/promise_s9.js b/test/js/promise_s9.js
new file mode 100644 (file)
index 0000000..4c014bb
--- /dev/null
@@ -0,0 +1,10 @@
+var executorFunction;
+
+function NotPromise(executor) {
+  executorFunction = executor;
+  executor(function() {console.log('S')}, function() {console.log('R')});
+}
+
+Promise.resolve.call(NotPromise);
+
+console.log(Object.isExtensible(executorFunction));
\ No newline at end of file
diff --git a/test/js/promise_set_timeout.js b/test/js/promise_set_timeout.js
new file mode 100644 (file)
index 0000000..c0d365b
--- /dev/null
@@ -0,0 +1,17 @@
+var res = [];
+function abc() {
+    var promise = new Promise(function(resolve, reject) {
+        res.push('One');
+        resolve();
+    });
+    res.push('Two');
+    promise.then(() => {res.push('Four'); return {num: 'Five'}})
+    .then((obj) => {res.push(obj.num);  return {num: 'Six'}})
+    .then((obj) => {res.push(obj.num);  return {num: 'Seven'}})
+    .then((obj) => {res.push(obj.num);  return {num: 'Eight'}})
+    .then((obj) => {res.push(obj.num)});
+    res.push('Three');
+}
+abc();
+console.log(res.join(','));
+setTimeout(() => console.log(res.join(',')), 0);
index c5b9e59e1e4219cf31765a86399a6d0091c0e0cd..b3eb186f5e80db7f09e8c0965cbbcdcd347ce4df 100644 (file)
@@ -860,3 +860,173 @@ njs_test {
 
 njs_run {"-v"} "\\d+\.\\d+\.\\d+"
 
+
+# Promise
+
+njs_run {"./test/js/promise_set_timeout.js"} \
+"One,Two,Three
+One,Two,Three,Four,Five,Six,Seven,Eight"
+
+njs_run {"./test/js/promise_s1.js"} \
+"One
+Two
+Three
+123
+Four
+Five
+Six
+Seven
+Eight"
+
+njs_run {"./test/js/promise_s2.js"} \
+"One
+Two
+Three"
+
+njs_run {"./test/js/promise_s3.js"} \
+"One
+Two
+Three
+Rejected: Error: Blah"
+
+njs_run {"./test/js/promise_s4.js"} \
+"Error: Oh my"
+
+njs_run {"./test/js/promise_s5.js"} \
+"Success"
+
+njs_run {"./test/js/promise_s6.js"} \
+"1"
+
+njs_run {"./test/js/promise_s7.js"} \
+"true
+fulfilled!"
+
+njs_run {"./test/js/promise_s8.js"} \
+"Ok"
+
+njs_run {"./test/js/promise_s9.js"} \
+"S
+true"
+
+njs_run {"./test/js/promise_s10.js"} \
+"One
+Two
+Three
+Oh no"
+
+njs_run {"./test/js/promise_s11.js"} \
+"end
+S: all
+R: all"
+
+njs_run {"./test/js/promise_s12.js"} \
+"Done"
+
+njs_run {"./test/js/promise_s13.js"} \
+"Done"
+
+njs_run {"./test/js/promise_s14.js"} \
+"TypeError: oops
+    at anonymous \\\(promise_s14.js:4\\\)
+    at native \\\(native\\\)
+    at main \\\(native\\\)
+
+Done"
+
+njs_run {"./test/js/promise_s15.js"} \
+"Done"
+
+njs_run {"./test/js/promise_s16.js"} \
+"Done"
+
+njs_run {"./test/js/promise_s17.js"} \
+"Done"
+
+njs_run {"./test/js/promise_s18.js"} \
+"State 1
+State 2
+State 3
+State 4
+State 5
+Done"
+
+njs_run {"./test/js/promise_s19.js"} \
+"Throw!
+undefined
+Reject!
+Done"
+
+njs_run {"./test/js/promise_s20.js"} \
+"true
+Done"
+
+njs_run {"./test/js/promise_s21.js"} \
+"Done"
+
+njs_run {"./test/js/promise_s22.js"} \
+"true
+Done"
+
+njs_run {"./test/js/promise_s23.js"} \
+"true
+Done"
+
+njs_run {"./test/js/promise_s24.js"} \
+"true"
+
+njs_run {"./test/js/promise_s25.js"} \
+"Done"
+
+njs_run {"./test/js/promise_s26.js"} \
+"BoxedPromise.constructor
+BoxedPromise.prototype.then
+BoxedPromise resolve OK
+BoxedPromise.constructor
+BoxedPromise.prototype.then
+BoxedPromise reject OK
+BoxedPromise.constructor
+BoxedPromise.prototype.then
+BoxedPromise.prototype.then
+BoxedPromise chain OK
+BoxedPromise.prototype.then
+BoxedPromise finally OK
+BoxedPromise sync done
+
+BoxedPromise.prototype.then
+BoxedPromise.prototype.then
+resolved BoxedPromise
+rejected BoxedPromise
+then BoxedPromise
+then BoxedPromise
+finally BoxedPromise
+BoxedPromise.constructor
+BoxedPromise.prototype.then
+BoxedPromise.prototype.then
+BoxedPromise async done
+
+PatchedPromise.constructor
+PatchedPromise.constructor
+PatchedPromise resolve OK
+PatchedPromise.constructor
+PatchedPromise.constructor
+PatchedPromise reject OK
+PatchedPromise.constructor
+PatchedPromise.constructor
+PatchedPromise.constructor
+PatchedPromise chain OK
+PatchedPromise.constructor
+PatchedPromise finally OK
+PatchedPromise sync done
+
+PatchedPromise.constructor
+PatchedPromise.constructor
+resolved PatchedPromise
+rejected PatchedPromise
+then PatchedPromise
+then PatchedPromise
+finally PatchedPromise
+PatchedPromise.constructor
+PatchedPromise.constructor
+PatchedPromise.constructor
+PatchedPromise async done"