/* * Copyright (C) Alexander Borisov * Copyright (C) Nginx, Inc. */ #include typedef enum { NJS_PROMISE_HANDLE = 0, NJS_PROMISE_REJECT } njs_promise_rejection_type_t; typedef enum { NJS_PROMISE_ALL = 0, NJS_PROMISE_ALL_SETTLED, NJS_PROMISE_ANY } njs_promise_function_type_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_function_native_t handler; } njs_promise_context_t; typedef struct { njs_bool_t already_called; uint32_t index; uint32_t *remaining_elements; njs_array_t *values; njs_promise_capability_t *capability; } njs_promise_all_context_t; typedef struct { njs_iterator_args_t args; uint32_t *remaining; njs_value_t *constructor; njs_function_t *function; njs_promise_capability_t *capability; } njs_promise_iterator_args_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 unused, njs_value_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 unused, njs_value_t *retval); 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_value_t *retval); 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_value_t *retval); static njs_int_t njs_promise_then_finally_return(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static njs_int_t njs_promise_catch_finally_return(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); 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_value_t *retval); 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_value_t *retval); static njs_int_t njs_promise_perform_all(njs_vm_t *vm, njs_value_t *iterator, njs_promise_iterator_args_t *pargs, njs_iterator_handler_t handler, njs_value_t *retval); static njs_int_t njs_promise_perform_all_handler(njs_vm_t *vm, njs_iterator_args_t *args, njs_value_t *value, int64_t index, njs_value_t *retval); static njs_int_t njs_promise_all_resolve_element_functions(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static njs_int_t njs_promise_perform_all_settled_handler(njs_vm_t *vm, njs_iterator_args_t *args, njs_value_t *value, int64_t index, njs_value_t *retval); static njs_int_t njs_promise_all_settled_element_functions(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t rejected, njs_value_t *retval); static njs_int_t njs_promise_perform_any_handler(njs_vm_t *vm, njs_iterator_args_t *args, njs_value_t *value, int64_t index, njs_value_t *retval); static njs_int_t njs_promise_any_reject_element_functions(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval); static njs_int_t njs_promise_perform_race_handler(njs_vm_t *vm, njs_iterator_args_t *args, njs_value_t *value, int64_t index, njs_value_t *retval); 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.error_data = 0; promise->object.fast_array = 0; promise->object.__proto__ = njs_vm_proto(vm, NJS_OBJ_TYPE_PROMISE); promise->object.slots = NULL; 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_data(&promise->value, data, 0); 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_value_t *retval) { 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(retval, promise); return NJS_OK; } njs_int_t njs_vm_promise_create(njs_vm_t *vm, njs_value_t *retval, njs_value_t *callbacks) { njs_int_t ret; njs_promise_t *promise; promise = njs_promise_alloc(vm); if (njs_slow_path(promise == NULL)) { return NJS_ERROR; } ret = njs_promise_create_resolving_functions(vm, promise, callbacks); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } njs_set_promise(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->exception))) { return NULL; } retval = njs_vm_exception(vm); ret = njs_function_call(vm, njs_function(&arguments[1]), &njs_value_undefined, &retval, 1, &retval); if (njs_slow_path(ret != NJS_OK)) { return NULL; } } return promise; } njs_function_t * njs_promise_create_function(njs_vm_t *vm, size_t context_size) { 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; } if (context_size > 0) { context = njs_mp_zalloc(vm->mem_pool, context_size); if (njs_slow_path(context == NULL)) { njs_mp_free(vm->mem_pool, function); goto memory_error; } } else { context = NULL; } function->object.__proto__ = njs_vm_proto(vm, NJS_OBJ_TYPE_FUNCTION); function->object.shared_hash = vm->shared->arrow_instance_hash; function->object.type = NJS_FUNCTION; function->object.extensible = 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, sizeof(njs_promise_context_t)); 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; } 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, sizeof(njs_promise_context_t)); 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; if (njs_is_function(value)) { *dst = *value; return NJS_OK; } ret = njs_value_property(vm, value, NJS_ATOM_STRING_constructor, dst); if (njs_slow_path(ret == NJS_ERROR)) { 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_value_t *retval) { 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_value_assign(retval, &njs_value_undefined); 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, sizeof(njs_promise_context_t)); function->u.native = njs_promise_reaction_job; njs_set_data(&arguments[0], reaction, 0); arguments[1] = *value; ret = njs_vm_enqueue_job(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_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_value_t promise_value; njs_promise_data_t *data; data = njs_data(&promise->value); data->result = *reason; data->state = NJS_PROMISE_REJECTED; if (!data->is_handled) { if (vm->rejection_tracker != NULL) { njs_set_promise(&promise_value, promise); vm->rejection_tracker(vm, vm->rejection_tracker_opaque, 0, &promise_value, reason); } } 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_value_t *retval) { njs_int_t ret; njs_value_t function; ret = njs_value_property(vm, promise, NJS_ATOM_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, 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_value_t *retval) { njs_int_t ret; njs_value_t *resolution, error, then, arguments[3]; njs_promise_t *promise; njs_function_t *function; njs_native_frame_t *active_frame; njs_promise_context_t *context; active_frame = vm->top_frame; context = active_frame->function->context; promise = njs_promise(&context->promise); if (*context->resolved_ref) { njs_value_assign(retval, &njs_value_undefined); return NJS_OK; } *context->resolved_ref = 1; resolution = njs_arg(args, nargs, 1); if (njs_values_same(vm, 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_value_assign(retval, njs_promise_reject(vm, promise, &error)); return NJS_OK; } if (!njs_is_object(resolution)) { goto fulfill; } ret = njs_value_property(vm, resolution, NJS_ATOM_STRING_then, &then); if (njs_slow_path(ret == NJS_ERROR)) { if (njs_slow_path(njs_is_memory_error(vm, &vm->exception))) { return NJS_ERROR; } error = njs_vm_exception(vm); njs_value_assign(retval, njs_promise_reject(vm, promise, &error)); if (njs_slow_path(njs_is_null(retval))) { 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, sizeof(njs_promise_context_t)); if (njs_slow_path(function == NULL)) { return NJS_ERROR; } function->u.native = njs_promise_resolve_thenable_job; ret = njs_vm_enqueue_job(vm, function, arguments, 3); if (njs_slow_path(ret != NJS_OK)) { return ret; } njs_value_assign(retval, &njs_value_undefined); return NJS_OK; fulfill: njs_value_assign(retval, njs_promise_fulfill(vm, promise, resolution)); if (njs_slow_path(njs_is_null(retval))) { 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_value_t *retval) { if (njs_slow_path(!njs_is_object(njs_argument(args, 0)))) { njs_type_error(vm, "this value is not an object"); return NJS_ERROR; } return njs_promise_resolve(vm, njs_argument(args, 0), njs_arg(args, nargs, 1), retval); } njs_int_t njs_promise_resolve(njs_vm_t *vm, njs_value_t *constructor, njs_value_t *x, njs_value_t *retval) { njs_int_t ret; njs_value_t value; njs_promise_capability_t *capability; if (njs_is_promise(x)) { ret = njs_value_property(vm, x, NJS_ATOM_STRING_constructor, &value); if (njs_slow_path(ret == NJS_ERROR)) { return NJS_ERROR; } if (njs_values_same(vm, &value, constructor)) { njs_value_assign(retval, x); return NJS_OK; } } capability = njs_promise_new_capability(vm, constructor); if (njs_slow_path(capability == NULL)) { return NJS_ERROR; } ret = njs_function_call(vm, njs_function(&capability->resolve), &njs_value_undefined, x, 1, &value); if (njs_slow_path(ret != NJS_OK)) { return ret; } njs_value_assign(retval, &capability->promise); return NJS_OK; } 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_value_t *retval) { njs_value_t *value; njs_native_frame_t *active_frame; njs_promise_context_t *context; active_frame = vm->top_frame; context = active_frame->function->context; if (*context->resolved_ref) { njs_value_assign(retval, &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_value_assign(retval, 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_value_t *retval) { njs_int_t ret; njs_value_t value; njs_promise_capability_t *capability; if (njs_slow_path(!njs_is_object(njs_argument(args, 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_value_assign(retval, &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_value_t *retval) { njs_int_t ret; njs_value_t *promise, *fulfilled, *rejected, constructor; njs_function_t *function; njs_promise_capability_t *capability; promise = njs_argument(args, 0); if (njs_slow_path(!njs_is_promise(promise))) { goto failed; } function = njs_promise_create_function(vm, sizeof(njs_promise_context_t)); 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, retval); failed: njs_type_error(vm, "required a promise object"); return NJS_ERROR; } 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_value_t *retval) { njs_int_t ret; njs_value_t arguments[2], promise_value; njs_promise_t *promise; njs_function_t *function; njs_promise_data_t *data; njs_promise_reaction_t *fulfilled_reaction, *rejected_reaction; njs_assert(njs_is_promise(value)); 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_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(rejected_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, sizeof(njs_promise_context_t)); function->u.native = njs_promise_reaction_job; if (data->state == NJS_PROMISE_REJECTED) { njs_set_data(&arguments[0], rejected_reaction, 0); if (vm->rejection_tracker != NULL) { njs_set_promise(&promise_value, promise); vm->rejection_tracker(vm, vm->rejection_tracker_opaque, 1, &promise_value, &data->result); } } else { njs_set_data(&arguments[0], fulfilled_reaction, 0); } arguments[1] = data->result; ret = njs_vm_enqueue_job(vm, function, arguments, 2); if (njs_slow_path(ret != NJS_OK)) { return ret; } } data->is_handled = 1; if (capability == NULL) { njs_set_undefined(retval); } else { njs_value_assign(retval, &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 *retval) { njs_value_t arguments[2]; arguments[0] = njs_value_undefined; arguments[1] = *njs_arg(args, nargs, 1); return njs_promise_invoke_then(vm, njs_argument(args, 0), arguments, 2, retval); } 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_value_t *retval) { njs_int_t ret; njs_value_t *promise, *finally, constructor, arguments[2]; njs_function_t *function; njs_promise_context_t *context; promise = njs_argument(args, 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, sizeof(njs_promise_context_t)); 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, retval); } function = njs_promise_create_function(vm, sizeof(njs_promise_context_t)); 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; context->handler = njs_promise_then_finally_return; njs_set_function(&arguments[0], function); function = njs_promise_create_function(vm, sizeof(njs_promise_context_t)); if (njs_slow_path(function == NULL)) { njs_mp_free(vm->mem_pool, njs_function(&arguments[0])); return NJS_ERROR; } function->u.native = njs_promise_then_finally_function; function->args_count = 1; context = function->context; context->constructor = constructor; context->finally = *finally; context->handler = njs_promise_catch_finally_return; njs_set_function(&arguments[1], function); return njs_promise_invoke_then(vm, promise, arguments, 2, retval); } 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_value_t *retval) { njs_int_t ret; njs_value_t value, argument; njs_function_t *function; njs_native_frame_t *frame; njs_promise_context_t *context; frame = vm->top_frame; context = frame->function->context; ret = njs_function_call(vm, njs_function(&context->finally), &njs_value_undefined, args, 0, &value); if (njs_slow_path(ret != NJS_OK)) { return ret; } ret = njs_promise_resolve(vm, &context->constructor, &value, &value); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } function = njs_promise_create_function(vm, sizeof(njs_value_t)); if (njs_slow_path(function == NULL)) { return NJS_ERROR; } function->u.native = context->handler; *((njs_value_t *) function->context) = *njs_arg(args, nargs, 1); njs_set_function(&argument, function); return njs_promise_invoke_then(vm, &value, &argument, 1, retval); } static njs_int_t njs_promise_then_finally_return(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { njs_value_assign(retval, vm->top_frame->function->context); return NJS_OK; } static njs_int_t njs_promise_catch_finally_return(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { njs_vm_throw(vm, vm->top_frame->function->context); return NJS_ERROR; } 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_value_t *retval) { njs_int_t ret; njs_bool_t is_error; njs_value_t value, *argument; njs_promise_reaction_t *reaction; njs_promise_capability_t *capability; reaction = njs_data(njs_arg(args, nargs, 1)); argument = njs_arg(args, nargs, 2); capability = reaction->capability; is_error = 0; if (njs_is_undefined(&reaction->handler)) { if (reaction->type == NJS_PROMISE_REJECTED) { is_error = 1; } njs_value_assign(&value, argument); } else { ret = njs_function_call(vm, njs_function(&reaction->handler), &njs_value_undefined, argument, 1, &value); if (njs_slow_path(ret != NJS_OK)) { if (njs_slow_path(njs_is_memory_error(vm, &vm->exception))) { return NJS_ERROR; } value = njs_vm_exception(vm); is_error = 1; } } if (capability == NULL) { njs_value_assign(retval, &value); return NJS_OK; } if (is_error) { ret = njs_function_call(vm, njs_function(&capability->reject), &njs_value_undefined, &value, 1, retval); } else { ret = njs_function_call(vm, njs_function(&capability->resolve), &njs_value_undefined, &value, 1, 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_value_t *retval) { njs_int_t ret; njs_value_t *promise, value, 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, &value); if (njs_slow_path(ret != NJS_OK)) { if (njs_slow_path(njs_is_memory_error(vm, &vm->exception))) { return NJS_ERROR; } value = njs_vm_exception(vm); ret = njs_function_call(vm, njs_function(&arguments[1]), &njs_value_undefined, &value, 1, retval); if (njs_slow_path(ret != NJS_OK)) { return NJS_ERROR; } } return NJS_OK; } static njs_int_t njs_promise_all(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t function_type, njs_value_t *retval) { njs_int_t ret; njs_value_t *promise_ctor, resolve; njs_iterator_handler_t handler; njs_promise_iterator_args_t pargs; promise_ctor = njs_argument(args, 0); pargs.capability = njs_promise_new_capability(vm, promise_ctor); if (njs_slow_path(pargs.capability == NULL)) { return NJS_ERROR; } ret = njs_value_property(vm, promise_ctor, NJS_ATOM_STRING_resolve, &resolve); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } if (njs_slow_path(!njs_is_function(&resolve))) { njs_type_error(vm, "resolve is not callable"); return NJS_ERROR; } pargs.function = njs_function(&resolve); pargs.constructor = promise_ctor; switch (function_type) { case NJS_PROMISE_ALL_SETTLED: handler = njs_promise_perform_all_settled_handler; break; case NJS_PROMISE_ANY: handler = njs_promise_perform_any_handler; break; default: handler = njs_promise_perform_all_handler; break; } return njs_promise_perform_all(vm, njs_arg(args, nargs, 1), &pargs, handler, retval); } static njs_int_t njs_promise_perform_all(njs_vm_t *vm, njs_value_t *iterator, njs_promise_iterator_args_t *pargs, njs_iterator_handler_t handler, njs_value_t *retval) { int64_t length; njs_int_t ret; njs_value_t argument, message; njs_object_t *error; if (njs_slow_path(!njs_is_object(pargs->constructor))) { njs_type_error(vm, "constructor is not object"); return NJS_ERROR; } njs_memzero(&pargs->args, sizeof(njs_iterator_args_t)); ret = njs_object_length(vm, iterator, &length); if (njs_slow_path(ret != NJS_OK)) { return ret; } pargs->args.data = njs_array_alloc(vm, 1, 0, NJS_ARRAY_SPARE); if (njs_slow_path(pargs->args.data == NULL)) { return NJS_ERROR; } pargs->remaining = njs_mp_alloc(vm->mem_pool, sizeof(uint32_t)); if (njs_slow_path(pargs->remaining == NULL)) { njs_memory_error(vm); return NJS_ERROR; } (*pargs->remaining) = 1; njs_value_assign(&pargs->args.value, iterator); pargs->args.to = length; ret = njs_object_iterate(vm, &pargs->args, handler, retval); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } if (--(*pargs->remaining) == 0) { njs_mp_free(vm->mem_pool, pargs->remaining); njs_set_array(&argument, pargs->args.data); njs_atom_to_value(vm, &message, NJS_ATOM_STRING_All_promises_were_rejected); if (handler == njs_promise_perform_any_handler) { error = njs_error_alloc(vm, njs_vm_proto(vm, NJS_OBJ_TYPE_AGGREGATE_ERROR), NULL, &message, &argument); if (njs_slow_path(error == NULL)) { return NJS_ERROR; } njs_set_object(&argument, error); } ret = njs_function_call(vm, njs_function(&pargs->capability->resolve), &njs_value_undefined, &argument, 1, retval); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } } njs_value_assign(retval, &pargs->capability->promise); return NJS_OK; } static njs_int_t njs_promise_perform_all_handler(njs_vm_t *vm, njs_iterator_args_t *args, njs_value_t *value, int64_t index, njs_value_t *retval) { njs_int_t ret; njs_array_t *array; njs_value_t arguments[2], next, arr_value; njs_function_t *on_fulfilled; njs_promise_capability_t *capability; njs_promise_all_context_t *context; njs_promise_iterator_args_t *pargs; if (!njs_is_valid(value)) { value = njs_value_arg(&njs_value_undefined); } pargs = (njs_promise_iterator_args_t *) args; capability = pargs->capability; array = args->data; njs_set_array(&arr_value, array); ret = njs_value_property_i64_set(vm, &arr_value, index, njs_value_arg(&njs_value_undefined)); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } ret = njs_function_call(vm, pargs->function, pargs->constructor, value, 1, &next); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } on_fulfilled = njs_promise_create_function(vm, sizeof(njs_promise_all_context_t)); if (njs_slow_path(on_fulfilled == NULL)) { return NJS_ERROR; } on_fulfilled->u.native = njs_promise_all_resolve_element_functions; on_fulfilled->args_count = 1; context = on_fulfilled->context; context->already_called = 0; context->index = (uint32_t) index; context->values = pargs->args.data; context->capability = capability; context->remaining_elements = pargs->remaining; (*pargs->remaining)++; njs_set_function(&arguments[0], on_fulfilled); arguments[1] = capability->reject; ret = njs_promise_invoke_then(vm, &next, arguments, 2, retval); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } return NJS_OK; } static njs_int_t njs_promise_all_resolve_element_functions(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { njs_int_t ret; njs_value_t arr_value; njs_promise_all_context_t *context; context = vm->top_frame->function->context; if (context->already_called) { njs_value_assign(retval, &njs_value_undefined); return NJS_OK; } context->already_called = 1; njs_set_array(&arr_value, context->values); ret = njs_value_property_i64_set(vm, &arr_value, context->index, njs_arg(args, nargs, 1)); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } if (--(*context->remaining_elements) == 0) { njs_mp_free(vm->mem_pool, context->remaining_elements); return njs_function_call(vm, njs_function(&context->capability->resolve), &njs_value_undefined, &arr_value, 1, retval); } njs_value_assign(retval, &njs_value_undefined); return NJS_OK; } static njs_int_t njs_promise_perform_all_settled_handler(njs_vm_t *vm, njs_iterator_args_t *args, njs_value_t *value, int64_t index, njs_value_t *retval) { njs_int_t ret; njs_array_t *array; njs_value_t arguments[2], next, arr_value; njs_function_t *on_fulfilled, *on_rejected; njs_promise_capability_t *capability; njs_promise_all_context_t *context; njs_promise_iterator_args_t *pargs; if (!njs_is_valid(value)) { value = njs_value_arg(&njs_value_undefined); } pargs = (njs_promise_iterator_args_t *) args; capability = pargs->capability; array = args->data; njs_set_array(&arr_value, array); ret = njs_value_property_i64_set(vm, &arr_value, index, njs_value_arg(&njs_value_undefined)); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } ret = njs_function_call(vm, pargs->function, pargs->constructor, value, 1, &next); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } on_fulfilled = njs_promise_create_function(vm, sizeof(njs_promise_all_context_t)); if (njs_slow_path(on_fulfilled == NULL)) { return NJS_ERROR; } context = on_fulfilled->context; context->already_called = 0; context->index = (uint32_t) index; context->values = pargs->args.data; context->capability = capability; context->remaining_elements = pargs->remaining; on_rejected = njs_promise_create_function(vm, 0); if (njs_slow_path(on_rejected == NULL)) { return NJS_ERROR; } on_fulfilled->u.native = njs_promise_all_settled_element_functions; on_rejected->u.native = njs_promise_all_settled_element_functions; on_rejected->magic8 = 1; /* rejected. */ on_fulfilled->args_count = 1; on_rejected->args_count = 1; on_rejected->context = context; (*pargs->remaining)++; njs_set_function(&arguments[0], on_fulfilled); njs_set_function(&arguments[1], on_rejected); ret = njs_promise_invoke_then(vm, &next, arguments, 2, retval); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } return NJS_OK; } static njs_int_t njs_promise_all_settled_element_functions(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t rejected, njs_value_t *retval) { njs_int_t ret; uint32_t set_atom_id; njs_value_t status; njs_value_t obj_value, arr_value; njs_object_t *obj; njs_promise_all_context_t *context; context = vm->top_frame->function->context; if (context->already_called) { njs_value_assign(retval, &njs_value_undefined); return NJS_OK; } context->already_called = 1; obj = njs_object_alloc(vm); if (njs_slow_path(obj == NULL)) { return NJS_ERROR; } njs_set_object(&obj_value, obj); if (rejected) { njs_atom_to_value(vm, &status, NJS_ATOM_STRING_rejected); set_atom_id = NJS_ATOM_STRING_reason; } else { njs_atom_to_value(vm, &status, NJS_ATOM_STRING_fulfilled); set_atom_id = NJS_ATOM_STRING_value; } ret = njs_value_property_set(vm, &obj_value, NJS_ATOM_STRING_status, njs_value_arg(&status)); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } ret = njs_value_property_set(vm, &obj_value, set_atom_id, njs_arg(args, nargs, 1)); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } njs_set_array(&arr_value, context->values); ret = njs_value_property_i64_set(vm, &arr_value, context->index, &obj_value); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } if (--(*context->remaining_elements) == 0) { njs_mp_free(vm->mem_pool, context->remaining_elements); return njs_function_call(vm, njs_function(&context->capability->resolve), &njs_value_undefined, &arr_value, 1, retval); } njs_value_assign(retval, &njs_value_undefined); return NJS_OK; } static njs_int_t njs_promise_perform_any_handler(njs_vm_t *vm, njs_iterator_args_t *args, njs_value_t *value, int64_t index, njs_value_t *retval) { njs_int_t ret; njs_array_t *array; njs_value_t arguments[2], next, arr_value; njs_function_t *on_rejected; njs_promise_capability_t *capability; njs_promise_all_context_t *context; njs_promise_iterator_args_t *pargs; if (!njs_is_valid(value)) { value = njs_value_arg(&njs_value_undefined); } pargs = (njs_promise_iterator_args_t *) args; capability = pargs->capability; array = args->data; njs_set_array(&arr_value, array); ret = njs_value_property_i64_set(vm, &arr_value, index, njs_value_arg(&njs_value_undefined)); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } ret = njs_function_call(vm, pargs->function, pargs->constructor, value, 1, &next); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } on_rejected = njs_promise_create_function(vm, sizeof(njs_promise_all_context_t)); if (njs_slow_path(on_rejected == NULL)) { return NJS_ERROR; } on_rejected->u.native = njs_promise_any_reject_element_functions; on_rejected->args_count = 1; context = on_rejected->context; context->already_called = 0; context->index = (uint32_t) index; context->values = pargs->args.data; context->capability = capability; context->remaining_elements = pargs->remaining; (*pargs->remaining)++; arguments[0] = capability->resolve; njs_set_function(&arguments[1], on_rejected); ret = njs_promise_invoke_then(vm, &next, arguments, 2, retval); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } return NJS_OK; } static njs_int_t njs_promise_any_reject_element_functions(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { njs_int_t ret; njs_value_t argument, message, arr_value; njs_object_t *error; njs_promise_all_context_t *context; context = vm->top_frame->function->context; if (context->already_called) { njs_value_assign(retval, &njs_value_undefined); return NJS_OK; } context->already_called = 1; njs_set_array(&arr_value, context->values); ret = njs_value_property_i64_set(vm, &arr_value, context->index, njs_arg(args, nargs, 1)); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } if (--(*context->remaining_elements) == 0) { njs_mp_free(vm->mem_pool, context->remaining_elements); njs_atom_to_value(vm, &message, NJS_ATOM_STRING_All_promises_were_rejected); error = njs_error_alloc(vm, njs_vm_proto(vm, NJS_OBJ_TYPE_AGGREGATE_ERROR), NULL, &message, &arr_value); if (njs_slow_path(error == NULL)) { return NJS_ERROR; } njs_set_object(&argument, error); return njs_function_call(vm, njs_function(&context->capability->reject), &njs_value_undefined, &argument, 1, retval); } njs_value_assign(retval, &njs_value_undefined); return NJS_OK; } static njs_int_t njs_promise_race(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs, njs_index_t unused, njs_value_t *retval) { int64_t length; njs_int_t ret; njs_value_t *promise_ctor, *iterator, resolve; njs_promise_iterator_args_t pargs; promise_ctor = njs_argument(args, 0); iterator = njs_arg(args, nargs, 1); pargs.capability = njs_promise_new_capability(vm, promise_ctor); if (njs_slow_path(pargs.capability == NULL)) { return NJS_ERROR; } ret = njs_value_property(vm, promise_ctor, NJS_ATOM_STRING_resolve, &resolve); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } if (njs_slow_path(!njs_is_function(&resolve))) { njs_type_error(vm, "resolve is not callable"); return NJS_ERROR; } ret = njs_object_length(vm, iterator, &length); if (njs_slow_path(ret != NJS_OK)) { return ret; } njs_memzero(&pargs.args, sizeof(njs_iterator_args_t)); pargs.function = njs_function(&resolve); pargs.constructor = promise_ctor; njs_value_assign(&pargs.args.value, iterator); pargs.args.to = length; ret = njs_object_iterate(vm, &pargs.args, njs_promise_perform_race_handler, retval); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } njs_value_assign(retval, &pargs.capability->promise); return NJS_OK; } static njs_int_t njs_promise_perform_race_handler(njs_vm_t *vm, njs_iterator_args_t *args, njs_value_t *value, int64_t index, njs_value_t *retval) { njs_int_t ret; njs_value_t arguments[2], next; njs_promise_capability_t *capability; njs_promise_iterator_args_t *pargs; if (!njs_is_valid(value)) { value = njs_value_arg(&njs_value_undefined); } pargs = (njs_promise_iterator_args_t *) args; ret = njs_function_call(vm, pargs->function, pargs->constructor, value, 1, &next); if (njs_slow_path(ret == NJS_ERROR)) { return ret; } capability = pargs->capability; arguments[0] = capability->resolve; arguments[1] = capability->reject; (void) njs_promise_invoke_then(vm, &next, arguments, 2, retval); 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_value_t *retval) { njs_value_assign(retval, njs_argument(args, 0)); return NJS_OK; } static const njs_object_prop_init_t njs_promise_constructor_properties[] = { NJS_DECLARE_PROP_LENGTH(1), NJS_DECLARE_PROP_NAME("Promise"), NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create, 0, 0), NJS_DECLARE_PROP_NATIVE(STRING_resolve, njs_promise_object_resolve, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_reject, njs_promise_object_reject, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_all, njs_promise_all, 1, NJS_PROMISE_ALL), NJS_DECLARE_PROP_NATIVE(STRING_allSettled, njs_promise_all, 1, NJS_PROMISE_ALL_SETTLED), NJS_DECLARE_PROP_NATIVE(STRING_any, njs_promise_all, 1, NJS_PROMISE_ANY), NJS_DECLARE_PROP_NATIVE(STRING_race, njs_promise_race, 1, 0), NJS_DECLARE_PROP_GETTER(SYMBOL_species, njs_promise_species, 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_init_t njs_promise_prototype_properties[] = { NJS_DECLARE_PROP_HANDLER(STRING_constructor, njs_object_prototype_create_constructor, 0, NJS_OBJECT_PROP_VALUE_CW), NJS_DECLARE_PROP_VALUE(SYMBOL_toStringTag, njs_ascii_strval("Promise"), NJS_OBJECT_PROP_VALUE_C), NJS_DECLARE_PROP_NATIVE(STRING_then, njs_promise_prototype_then, 2, 0), NJS_DECLARE_PROP_NATIVE(STRING_catch, njs_promise_prototype_catch, 1, 0), NJS_DECLARE_PROP_NATIVE(STRING_finally, njs_promise_prototype_finally, 1, 0), }; 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 } }, };