--- /dev/null
+
+/*
+ * 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 } },
+};