
/*
 * Copyright (C) Alexander Borisov
 * Copyright (C) Nginx, Inc.
 */

#include <njs_main.h>


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_flathsh_init(&promise->object.hash);
    njs_flathsh_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));
        if (njs_slow_path(function == NULL)) {
            return njs_value_arg(&njs_value_null);
        }

        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));
    if (njs_slow_path(function == NULL)) {
        /* vm error is already set by njs_promise_create_function */
        return NJS_ERROR;
    }

    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));
        if (njs_slow_path(function == NULL)) {
            return NJS_ERROR;
        }

        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;
}


njs_promise_type_t
njs_promise_state(const njs_value_t *value)
{
    njs_promise_data_t  *promise_data;

    promise_data = njs_data(&njs_promise(value)->value);

    return promise_data->state;
}


njs_value_t *
njs_promise_result(const njs_value_t *value)
{
    njs_promise_data_t  *promise_data;

    promise_data = njs_data(&njs_promise(value)->value);

    return &promise_data->result;
}


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));
    if (njs_slow_path(function == NULL)) {
        return NJS_ERROR;
    }

    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);

            njs_error_stack_attach(vm, argument, 1);
        }

        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);

        njs_error_stack_attach(vm, argument, 1);

        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 } },
};
