1 
2 /*
3  * Copyright (C) Alexander Borisov
4  * Copyright (C) Nginx, Inc.
5  */
6 
7 #include <njs_main.h>
8 
9 
10 typedef enum {
11     NJS_PROMISE_HANDLE = 0,
12     NJS_PROMISE_REJECT
13 } njs_promise_rejection_type_t;
14 
15 typedef enum {
16     NJS_PROMISE_ALL = 0,
17     NJS_PROMISE_ALL_SETTLED,
18     NJS_PROMISE_ANY
19 } njs_promise_function_type_t;
20 
21 typedef struct {
22     njs_promise_capability_t  *capability;
23     njs_promise_type_t        type;
24     njs_queue_link_t          link;
25     njs_value_t               handler;
26 } njs_promise_reaction_t;
27 
28 typedef struct {
29     njs_value_t               promise;
30     njs_value_t               finally;
31     njs_value_t               constructor;
32     njs_bool_t                resolved;
33     njs_bool_t                *resolved_ref;
34     njs_promise_capability_t  *capability;
35     njs_function_native_t     handler;
36 } njs_promise_context_t;
37 
38 typedef struct {
39     njs_bool_t                already_called;
40     uint32_t                  index;
41     uint32_t                  *remaining_elements;
42     njs_array_t               *values;
43     njs_promise_capability_t  *capability;
44 } njs_promise_all_context_t;
45 
46 typedef struct {
47     njs_iterator_args_t       args;
48     uint32_t                  *remaining;
49     njs_value_t               *constructor;
50     njs_function_t            *function;
51     njs_promise_capability_t  *capability;
52 } njs_promise_iterator_args_t;
53 
54 
55 static njs_promise_t *njs_promise_constructor_call(njs_vm_t *vm,
56     njs_function_t *function);
57 static njs_int_t njs_promise_create_resolving_functions(njs_vm_t *vm,
58     njs_promise_t *promise, njs_value_t *dst);
59 static njs_int_t njs_promise_value_constructor(njs_vm_t *vm, njs_value_t *value,
60     njs_value_t *dst);
61 static njs_int_t njs_promise_capability_executor(njs_vm_t *vm,
62     njs_value_t *args, njs_uint_t nargs, njs_index_t retval);
63 static njs_int_t njs_promise_host_rejection_tracker(njs_vm_t *vm,
64     njs_promise_t *promise, njs_promise_rejection_type_t operation);
65 static njs_int_t njs_promise_resolve_function(njs_vm_t *vm, njs_value_t *args,
66     njs_uint_t nargs, njs_index_t retval);
67 static njs_int_t njs_promise_reject_function(njs_vm_t *vm, njs_value_t *args,
68     njs_uint_t nargs, njs_index_t retval);
69 static njs_int_t njs_promise_then_finally_function(njs_vm_t *vm,
70     njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
71 static njs_int_t njs_promise_then_finally_return(njs_vm_t *vm,
72     njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
73 static njs_int_t njs_promise_catch_finally_return(njs_vm_t *vm,
74     njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
75 static njs_int_t njs_promise_reaction_job(njs_vm_t *vm, njs_value_t *args,
76     njs_uint_t nargs, njs_index_t unused);
77 static njs_int_t njs_promise_resolve_thenable_job(njs_vm_t *vm,
78     njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
79 static njs_int_t njs_promise_perform_all(njs_vm_t *vm, njs_value_t *iterator,
80     njs_promise_iterator_args_t *pargs, njs_iterator_handler_t handler,
81     njs_value_t *retval);
82 static njs_int_t njs_promise_perform_all_handler(njs_vm_t *vm,
83     njs_iterator_args_t *args, njs_value_t *value, int64_t index);
84 static njs_int_t njs_promise_all_resolve_element_functions(njs_vm_t *vm,
85     njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
86 static njs_int_t njs_promise_perform_all_settled_handler(njs_vm_t *vm,
87     njs_iterator_args_t *args, njs_value_t *value, int64_t index);
88 static njs_int_t njs_promise_all_settled_element_functions(njs_vm_t *vm,
89     njs_value_t *args, njs_uint_t nargs, njs_index_t rejected);
90 static njs_int_t njs_promise_perform_any_handler(njs_vm_t *vm,
91     njs_iterator_args_t *args, njs_value_t *value, int64_t index);
92 static njs_int_t njs_promise_any_reject_element_functions(njs_vm_t *vm,
93     njs_value_t *args, njs_uint_t nargs, njs_index_t unused);
94 static njs_int_t njs_promise_perform_race_handler(njs_vm_t *vm,
95     njs_iterator_args_t *args, njs_value_t *value, int64_t index);
96 
97 
98 static const njs_value_t  string_resolve = njs_string("resolve");
99 static const njs_value_t  string_any_rejected =
100                                  njs_long_string("All promises were rejected");
101 
102 
103 static njs_promise_t *
njs_promise_alloc(njs_vm_t * vm)104 njs_promise_alloc(njs_vm_t *vm)
105 {
106     njs_promise_t       *promise;
107     njs_promise_data_t  *data;
108 
109     promise = njs_mp_alloc(vm->mem_pool, sizeof(njs_promise_t)
110                            + sizeof(njs_promise_data_t));
111     if (njs_slow_path(promise == NULL)) {
112         goto memory_error;
113     }
114 
115     njs_lvlhsh_init(&promise->object.hash);
116     njs_lvlhsh_init(&promise->object.shared_hash);
117     promise->object.type = NJS_PROMISE;
118     promise->object.shared = 0;
119     promise->object.extensible = 1;
120     promise->object.error_data = 0;
121     promise->object.fast_array = 0;
122     promise->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_PROMISE].object;
123     promise->object.slots = NULL;
124 
125     data = (njs_promise_data_t *) ((uint8_t *) promise + sizeof(njs_promise_t));
126 
127     data->state = NJS_PROMISE_PENDING;
128     data->is_handled = 0;
129 
130     njs_queue_init(&data->fulfill_queue);
131     njs_queue_init(&data->reject_queue);
132 
133     njs_set_data(&promise->value, data, 0);
134 
135     return promise;
136 
137 memory_error:
138 
139     njs_memory_error(vm);
140 
141     return NULL;
142 }
143 
144 
145 njs_int_t
njs_promise_constructor(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)146 njs_promise_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
147     njs_index_t unused)
148 {
149     njs_promise_t   *promise;
150     njs_function_t  *function;
151 
152     if (njs_slow_path(!vm->top_frame->ctor)) {
153         njs_type_error(vm, "the Promise constructor must be called with new");
154         return NJS_ERROR;
155     }
156 
157     if (njs_slow_path(!njs_is_function(njs_arg(args, nargs, 1)))) {
158         njs_type_error(vm, "unexpected arguments");
159         return NJS_ERROR;
160     }
161 
162     function = njs_function(njs_argument(args, 1));
163 
164     promise = njs_promise_constructor_call(vm, function);
165     if (njs_slow_path(promise == NULL)) {
166         return NJS_ERROR;
167     }
168 
169     njs_set_promise(&vm->retval, promise);
170 
171     return NJS_OK;
172 }
173 
174 
175 njs_int_t
njs_vm_promise_create(njs_vm_t * vm,njs_value_t * retval,njs_value_t * callbacks)176 njs_vm_promise_create(njs_vm_t *vm, njs_value_t *retval, njs_value_t *callbacks)
177 {
178     njs_int_t      ret;
179     njs_promise_t  *promise;
180 
181     promise = njs_promise_alloc(vm);
182     if (njs_slow_path(promise == NULL)) {
183         return NJS_ERROR;
184     }
185 
186     ret = njs_promise_create_resolving_functions(vm, promise, callbacks);
187     if (njs_slow_path(ret != NJS_OK)) {
188         return NJS_ERROR;
189     }
190 
191     njs_set_promise(retval, promise);
192 
193     return NJS_OK;
194 }
195 
196 
197 static njs_promise_t *
njs_promise_constructor_call(njs_vm_t * vm,njs_function_t * function)198 njs_promise_constructor_call(njs_vm_t *vm, njs_function_t *function)
199 {
200     njs_int_t      ret;
201     njs_value_t    retval, arguments[2];
202     njs_promise_t  *promise;
203 
204     promise = njs_promise_alloc(vm);
205     if (njs_slow_path(promise == NULL)) {
206         return NULL;
207     }
208 
209     ret = njs_promise_create_resolving_functions(vm, promise, arguments);
210     if (njs_slow_path(ret != NJS_OK)) {
211         return NULL;
212     }
213 
214     ret = njs_function_call(vm, function, &njs_value_undefined, arguments, 2,
215                             &retval);
216     if (njs_slow_path(ret != NJS_OK)) {
217         if (njs_slow_path(njs_is_memory_error(vm, &vm->retval))) {
218             return NULL;
219         }
220 
221         ret = njs_function_call(vm, njs_function(&arguments[1]),
222                                 &njs_value_undefined, &vm->retval, 1, &retval);
223         if (njs_slow_path(ret != NJS_OK)) {
224             return NULL;
225         }
226     }
227 
228     return promise;
229 }
230 
231 
232 njs_function_t *
njs_promise_create_function(njs_vm_t * vm,size_t context_size)233 njs_promise_create_function(njs_vm_t *vm, size_t context_size)
234 {
235     njs_function_t         *function;
236     njs_promise_context_t  *context;
237 
238     function = njs_mp_zalloc(vm->mem_pool, sizeof(njs_function_t));
239     if (njs_slow_path(function == NULL)) {
240         goto memory_error;
241     }
242 
243     if (context_size > 0) {
244         context = njs_mp_zalloc(vm->mem_pool, context_size);
245         if (njs_slow_path(context == NULL)) {
246             njs_mp_free(vm->mem_pool, function);
247             goto memory_error;
248         }
249 
250     } else {
251         context = NULL;
252     }
253 
254     function->object.__proto__ = &vm->prototypes[NJS_OBJ_TYPE_FUNCTION].object;
255     function->object.shared_hash = vm->shared->arrow_instance_hash;
256     function->object.type = NJS_FUNCTION;
257     function->object.extensible = 1;
258     function->args_offset = 1;
259     function->native = 1;
260     function->context = context;
261 
262     return function;
263 
264 memory_error:
265 
266     njs_memory_error(vm);
267 
268     return NULL;
269 }
270 
271 
272 static njs_int_t
njs_promise_create_resolving_functions(njs_vm_t * vm,njs_promise_t * promise,njs_value_t * dst)273 njs_promise_create_resolving_functions(njs_vm_t *vm, njs_promise_t *promise,
274     njs_value_t *dst)
275 {
276     unsigned               i;
277     njs_function_t         *function;
278     njs_promise_context_t  *context, *resolve_context;
279 
280     i = 0;
281 
282     /* Some compilers give at error an uninitialized context if using for. */
283     do {
284         function = njs_promise_create_function(vm,
285                                                sizeof(njs_promise_context_t));
286         if (njs_slow_path(function == NULL)) {
287             return NJS_ERROR;
288         }
289 
290         function->args_count = 1;
291 
292         context = function->context;
293         context->resolved_ref = &context->resolved;
294 
295         njs_set_promise(&context->promise, promise);
296         njs_set_function(&dst[i], function);
297 
298     } while (++i < 2);
299 
300     njs_function(&dst[0])->u.native = njs_promise_resolve_function;
301     njs_function(&dst[1])->u.native = njs_promise_reject_function;
302 
303     resolve_context = njs_function(&dst[0])->context;
304     resolve_context->resolved_ref = &context->resolved;
305 
306     return NJS_OK;
307 }
308 
309 
310 njs_promise_capability_t *
njs_promise_new_capability(njs_vm_t * vm,njs_value_t * constructor)311 njs_promise_new_capability(njs_vm_t *vm, njs_value_t *constructor)
312 {
313     njs_int_t                 ret;
314     njs_value_t               argument, this;
315     njs_object_t              *object;
316     njs_function_t            *function;
317     njs_promise_context_t     *context;
318     njs_promise_capability_t  *capability;
319 
320     object = NULL;
321     function = NULL;
322 
323     ret = njs_promise_value_constructor(vm, constructor, constructor);
324     if (njs_slow_path(ret != NJS_OK)) {
325         return NULL;
326     }
327 
328     capability = njs_mp_zalloc(vm->mem_pool, sizeof(njs_promise_capability_t));
329     if (njs_slow_path(capability == NULL)) {
330         njs_memory_error(vm);
331         return NULL;
332     }
333 
334     function = njs_promise_create_function(vm, sizeof(njs_promise_context_t));
335     if (njs_slow_path(function == NULL)) {
336         return NULL;
337     }
338 
339     njs_set_undefined(&capability->resolve);
340     njs_set_undefined(&capability->reject);
341 
342     function->u.native = njs_promise_capability_executor;
343     function->args_count = 2;
344 
345     context = function->context;
346     context->capability = capability;
347 
348     njs_set_function(&argument, function);
349 
350     object = njs_function_new_object(vm, constructor);
351     if (njs_slow_path(object == NULL)) {
352         return NULL;
353     }
354 
355     njs_set_object(&this, object);
356 
357     ret = njs_function_call2(vm, njs_function(constructor), &this,
358                              &argument, 1, &capability->promise, 1);
359     if (njs_slow_path(ret != NJS_OK)) {
360         return NULL;
361     }
362 
363     if (njs_slow_path(!njs_is_function(&capability->resolve))) {
364         njs_type_error(vm, "capability resolve slot is not callable");
365         return NULL;
366     }
367 
368     if (njs_slow_path(!njs_is_function(&capability->reject))) {
369         njs_type_error(vm, "capability reject slot is not callable");
370         return NULL;
371     }
372 
373     return capability;
374 }
375 
376 
377 static njs_int_t
njs_promise_value_constructor(njs_vm_t * vm,njs_value_t * value,njs_value_t * dst)378 njs_promise_value_constructor(njs_vm_t *vm, njs_value_t *value,
379     njs_value_t *dst)
380 {
381     njs_int_t  ret;
382 
383     static const njs_value_t  string_constructor = njs_string("constructor");
384 
385     if (njs_is_function(value)) {
386         *dst = *value;
387         return NJS_OK;
388     }
389 
390     ret = njs_value_property(vm, value, njs_value_arg(&string_constructor),
391                              dst);
392     if (njs_slow_path(ret != NJS_OK)) {
393         return ret;
394     }
395 
396     if (!njs_is_function(dst)) {
397         njs_type_error(vm, "the object does not contain a constructor");
398         return NJS_ERROR;
399     }
400 
401     return NJS_OK;
402 }
403 
404 
405 static njs_int_t
njs_promise_capability_executor(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)406 njs_promise_capability_executor(njs_vm_t *vm, njs_value_t *args,
407     njs_uint_t nargs, njs_index_t unused)
408 {
409     njs_promise_context_t     *context;
410     njs_promise_capability_t  *capability;
411 
412     context = vm->top_frame->function->context;
413     capability = context->capability;
414 
415     if (njs_slow_path(capability == NULL)) {
416         njs_type_error(vm, "failed to get function capability");
417         return NJS_ERROR;
418     }
419 
420     if (!njs_is_undefined(&capability->resolve)) {
421         njs_type_error(vm, "capability resolve slot is not undefined");
422         return NJS_ERROR;
423     }
424 
425     if (!njs_is_undefined(&capability->reject)) {
426         njs_type_error(vm, "capability reject slot is not undefined");
427         return NJS_ERROR;
428     }
429 
430     capability->resolve = *njs_arg(args, nargs, 1);
431     capability->reject = *njs_arg(args, nargs, 2);
432 
433     njs_vm_retval_set(vm, &njs_value_undefined);
434 
435     return NJS_OK;
436 }
437 
438 
439 njs_inline njs_int_t
njs_promise_add_event(njs_vm_t * vm,njs_function_t * function,njs_value_t * args,njs_uint_t nargs)440 njs_promise_add_event(njs_vm_t *vm, njs_function_t *function, njs_value_t *args,
441     njs_uint_t nargs)
442 {
443     njs_event_t  *event;
444 
445     event = njs_mp_zalloc(vm->mem_pool, sizeof(njs_event_t));
446     if (njs_slow_path(event == NULL)) {
447         njs_memory_error(vm);
448         return NJS_ERROR;
449     }
450 
451     event->function = function;
452     event->once = 1;
453 
454     if (nargs != 0) {
455         event->args = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t) * nargs);
456         if (njs_slow_path(event->args == NULL)) {
457             njs_memory_error(vm);
458             return NJS_ERROR;
459         }
460 
461         memcpy(event->args, args, sizeof(njs_value_t) * nargs);
462 
463         event->nargs = nargs;
464     }
465 
466     njs_queue_insert_tail(&vm->promise_events, &event->link);
467 
468     return NJS_OK;
469 }
470 
471 
472 njs_inline njs_value_t *
njs_promise_trigger_reactions(njs_vm_t * vm,njs_value_t * value,njs_queue_t * queue)473 njs_promise_trigger_reactions(njs_vm_t *vm, njs_value_t *value,
474     njs_queue_t *queue)
475 {
476     njs_int_t               ret;
477     njs_value_t             arguments[2];
478     njs_function_t          *function;
479     njs_queue_link_t        *link;
480     njs_promise_reaction_t  *reaction;
481 
482     for (link = njs_queue_first(queue);
483          link != njs_queue_tail(queue);
484          link = njs_queue_next(link))
485     {
486         reaction = njs_queue_link_data(link, njs_promise_reaction_t, link);
487 
488         function = njs_promise_create_function(vm,
489                                                sizeof(njs_promise_context_t));
490         function->u.native = njs_promise_reaction_job;
491 
492         njs_set_data(&arguments[0], reaction, 0);
493         arguments[1] = *value;
494 
495         ret = njs_promise_add_event(vm, function, arguments, 2);
496         if (njs_slow_path(ret != NJS_OK)) {
497             return njs_value_arg(&njs_value_null);
498         }
499     }
500 
501     return njs_value_arg(&njs_value_undefined);
502 }
503 
504 
505 njs_inline njs_value_t *
njs_promise_fulfill(njs_vm_t * vm,njs_promise_t * promise,njs_value_t * value)506 njs_promise_fulfill(njs_vm_t *vm, njs_promise_t *promise, njs_value_t *value)
507 {
508     njs_queue_t         queue;
509     njs_promise_data_t  *data;
510 
511     data = njs_data(&promise->value);
512 
513     data->result = *value;
514     data->state = NJS_PROMISE_FULFILL;
515 
516     if (njs_queue_is_empty(&data->fulfill_queue)) {
517         return njs_value_arg(&njs_value_undefined);
518 
519     } else {
520         queue = data->fulfill_queue;
521 
522         queue.head.prev->next = &queue.head;
523         queue.head.next->prev = &queue.head;
524     }
525 
526     njs_queue_init(&data->fulfill_queue);
527     njs_queue_init(&data->reject_queue);
528 
529     return njs_promise_trigger_reactions(vm, value, &queue);
530 }
531 
532 
533 njs_inline njs_value_t *
njs_promise_reject(njs_vm_t * vm,njs_promise_t * promise,njs_value_t * reason)534 njs_promise_reject(njs_vm_t *vm, njs_promise_t *promise, njs_value_t *reason)
535 {
536     njs_int_t           ret;
537     njs_queue_t         queue;
538     njs_promise_data_t  *data;
539 
540     data = njs_data(&promise->value);
541 
542     data->result = *reason;
543     data->state = NJS_PROMISE_REJECTED;
544 
545     if (!data->is_handled) {
546         ret = njs_promise_host_rejection_tracker(vm, promise,
547                                                  NJS_PROMISE_REJECT);
548         if (njs_slow_path(ret != NJS_OK)) {
549             return njs_value_arg(&njs_value_null);
550         }
551     }
552 
553     if (njs_queue_is_empty(&data->reject_queue)) {
554         return njs_value_arg(&njs_value_undefined);
555 
556     } else {
557         queue = data->reject_queue;
558 
559         queue.head.prev->next = &queue.head;
560         queue.head.next->prev = &queue.head;
561     }
562 
563     njs_queue_init(&data->fulfill_queue);
564     njs_queue_init(&data->reject_queue);
565 
566     return njs_promise_trigger_reactions(vm, reason, &queue);
567 }
568 
569 
570 static njs_int_t
njs_promise_host_rejection_tracker(njs_vm_t * vm,njs_promise_t * promise,njs_promise_rejection_type_t operation)571 njs_promise_host_rejection_tracker(njs_vm_t *vm, njs_promise_t *promise,
572     njs_promise_rejection_type_t operation)
573 {
574     uint32_t            i, length;
575     njs_value_t         *value;
576     njs_promise_data_t  *data;
577 
578     if (vm->options.unhandled_rejection
579         == NJS_VM_OPT_UNHANDLED_REJECTION_IGNORE)
580     {
581         return NJS_OK;
582     }
583 
584     if (vm->promise_reason == NULL) {
585         vm->promise_reason = njs_array_alloc(vm, 1, 0, NJS_ARRAY_SPARE);
586         if (njs_slow_path(vm->promise_reason == NULL)) {
587             return NJS_ERROR;
588         }
589     }
590 
591     data = njs_data(&promise->value);
592 
593     if (operation == NJS_PROMISE_REJECT) {
594         if (vm->promise_reason != NULL) {
595             return njs_array_add(vm, vm->promise_reason, &data->result);
596         }
597 
598     } else {
599         value = vm->promise_reason->start;
600         length = vm->promise_reason->length;
601 
602         for (i = 0; i < length; i++) {
603             if (njs_values_same(&value[i], &data->result)) {
604                 length--;
605 
606                 if (i < length) {
607                     memmove(&value[i], &value[i + 1],
608                             sizeof(njs_value_t) * (length - i));
609                 }
610 
611                 break;
612             }
613         }
614 
615         vm->promise_reason->length = length;
616     }
617 
618     return NJS_OK;
619 }
620 
621 
622 static njs_int_t
njs_promise_invoke_then(njs_vm_t * vm,njs_value_t * promise,njs_value_t * args,njs_int_t nargs)623 njs_promise_invoke_then(njs_vm_t *vm, njs_value_t *promise, njs_value_t *args,
624     njs_int_t nargs)
625 {
626     njs_int_t    ret;
627     njs_value_t  function;
628 
629     static const njs_value_t  string_then = njs_string("then");
630 
631     ret = njs_value_property(vm, promise, njs_value_arg(&string_then),
632                              &function);
633     if (njs_slow_path(ret != NJS_OK)) {
634         if (ret == NJS_DECLINED) {
635             goto failed;
636         }
637 
638         return NJS_ERROR;
639     }
640 
641     if (njs_fast_path(njs_is_function(&function))) {
642         return njs_function_call(vm, njs_function(&function), promise, args,
643                                  nargs, &vm->retval);
644     }
645 
646 failed:
647 
648     njs_type_error(vm, "is not a function");
649 
650     return NJS_ERROR;
651 }
652 
653 
654 static njs_int_t
njs_promise_resolve_function(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)655 njs_promise_resolve_function(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
656     njs_index_t unused)
657 {
658     njs_int_t              ret;
659     njs_value_t            *resolution, error, then, arguments[3];
660     njs_promise_t          *promise;
661     njs_function_t         *function;
662     njs_native_frame_t     *active_frame;
663     njs_promise_context_t  *context;
664 
665     static const njs_value_t  string_then = njs_string("then");
666 
667     active_frame = vm->top_frame;
668     context = active_frame->function->context;
669     promise = njs_promise(&context->promise);
670 
671     if (*context->resolved_ref) {
672         njs_vm_retval_set(vm, &njs_value_undefined);
673         return NJS_OK;
674     }
675 
676     *context->resolved_ref = 1;
677 
678     resolution = njs_arg(args, nargs, 1);
679 
680     if (njs_values_same(resolution, &context->promise)) {
681         njs_error_fmt_new(vm, &error, NJS_OBJ_TYPE_TYPE_ERROR,
682                           "promise self resolution");
683         if (njs_slow_path(!njs_is_error(&error))) {
684             return NJS_ERROR;
685         }
686 
687         njs_vm_retval_set(vm, njs_promise_reject(vm, promise, &error));
688 
689         return NJS_OK;
690     }
691 
692     if (!njs_is_object(resolution)) {
693         goto fulfill;
694     }
695 
696     ret = njs_value_property(vm, resolution, njs_value_arg(&string_then),
697                              &then);
698     if (njs_slow_path(ret == NJS_ERROR)) {
699         if (njs_slow_path(njs_is_memory_error(vm, &vm->retval))) {
700             return NJS_ERROR;
701         }
702 
703         njs_vm_retval_set(vm, njs_promise_reject(vm, promise, &vm->retval));
704         if (njs_slow_path(njs_vm_retval(vm)->type == NJS_NULL)) {
705             return NJS_ERROR;
706         }
707 
708         return NJS_OK;
709     }
710 
711     if (!njs_is_function(&then)) {
712         goto fulfill;
713     }
714 
715     arguments[0] = context->promise;
716     arguments[1] = *resolution;
717     arguments[2] = then;
718 
719     function = njs_promise_create_function(vm, sizeof(njs_promise_context_t));
720     if (njs_slow_path(function == NULL)) {
721         return NJS_ERROR;
722     }
723 
724     function->u.native = njs_promise_resolve_thenable_job;
725 
726     ret = njs_promise_add_event(vm, function, arguments, 3);
727     if (njs_slow_path(ret != NJS_OK)) {
728         return ret;
729     }
730 
731     njs_vm_retval_set(vm, &njs_value_undefined);
732 
733     return NJS_OK;
734 
735 fulfill:
736 
737     njs_vm_retval_set(vm, njs_promise_fulfill(vm, promise, resolution));
738     if (njs_slow_path(njs_vm_retval(vm)->type == NJS_NULL)) {
739         return NJS_ERROR;
740     }
741 
742     return NJS_OK;
743 }
744 
745 
746 static njs_int_t
njs_promise_object_resolve(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)747 njs_promise_object_resolve(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
748     njs_index_t unused)
749 {
750     njs_promise_t  *promise;
751 
752     if (njs_slow_path(!njs_is_object(njs_arg(args, nargs, 0)))) {
753         njs_type_error(vm, "this value is not an object");
754         return NJS_ERROR;
755     }
756 
757     promise = njs_promise_resolve(vm, njs_argument(args, 0),
758                                   njs_arg(args, nargs, 1));
759     if (njs_slow_path(promise == NULL)) {
760         return NJS_ERROR;
761     }
762 
763     njs_set_promise(&vm->retval, promise);
764 
765     return NJS_OK;
766 }
767 
768 
769 njs_promise_t *
njs_promise_resolve(njs_vm_t * vm,njs_value_t * constructor,njs_value_t * x)770 njs_promise_resolve(njs_vm_t *vm, njs_value_t *constructor, njs_value_t *x)
771 {
772     njs_int_t                 ret;
773     njs_value_t               value;
774     njs_object_t              *object;
775     njs_promise_capability_t  *capability;
776 
777     static const njs_value_t  string_constructor = njs_string("constructor");
778 
779     if (njs_is_object(x)) {
780         object = njs_object_proto_lookup(njs_object(x), NJS_PROMISE,
781                                          njs_object_t);
782 
783         if (object != NULL) {
784             ret = njs_value_property(vm, x, njs_value_arg(&string_constructor),
785                                      &value);
786             if (njs_slow_path(ret == NJS_ERROR)) {
787                 return NULL;
788             }
789 
790             if (njs_values_same(&value, constructor)) {
791                 return njs_promise(x);
792             }
793         }
794     }
795 
796     capability = njs_promise_new_capability(vm, constructor);
797     if (njs_slow_path(capability == NULL)) {
798         return NULL;
799     }
800 
801     ret = njs_function_call(vm, njs_function(&capability->resolve),
802                             &njs_value_undefined, x, 1, &value);
803     if (njs_slow_path(ret != NJS_OK)) {
804         return NULL;
805     }
806 
807     return njs_promise(&capability->promise);
808 }
809 
810 
811 static njs_int_t
njs_promise_reject_function(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)812 njs_promise_reject_function(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
813     njs_index_t unused)
814 {
815     njs_value_t            *value;
816     njs_native_frame_t     *active_frame;
817     njs_promise_context_t  *context;
818 
819     active_frame = vm->top_frame;
820     context = active_frame->function->context;
821 
822     if (*context->resolved_ref) {
823         njs_vm_retval_set(vm, &njs_value_undefined);
824         return NJS_OK;
825     }
826 
827     *context->resolved_ref = 1;
828 
829     value = njs_promise_reject(vm, njs_promise(&context->promise),
830                                njs_arg(args, nargs, 1));
831     if (njs_slow_path(value->type == NJS_NULL)) {
832         return NJS_ERROR;
833     }
834 
835     njs_vm_retval_set(vm, value);
836 
837     return NJS_OK;
838 }
839 
840 
841 static njs_int_t
njs_promise_object_reject(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)842 njs_promise_object_reject(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
843     njs_index_t unused)
844 {
845     njs_int_t                 ret;
846     njs_value_t               value;
847     njs_promise_capability_t  *capability;
848 
849     if (njs_slow_path(!njs_is_object(njs_arg(args, nargs, 0)))) {
850         njs_type_error(vm, "this value is not an object");
851         return NJS_ERROR;
852     }
853 
854     capability = njs_promise_new_capability(vm, njs_argument(args, 0));
855     if (njs_slow_path(capability == NULL)) {
856         return NJS_ERROR;
857     }
858 
859     ret = njs_function_call(vm, njs_function(&capability->reject),
860                             &njs_value_undefined, njs_arg(args, nargs, 1),
861                             1, &value);
862     if (njs_slow_path(ret != NJS_OK)) {
863         return ret;
864     }
865 
866     njs_vm_retval_set(vm, &capability->promise);
867 
868     return NJS_OK;
869 }
870 
871 
872 static njs_int_t
njs_promise_prototype_then(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)873 njs_promise_prototype_then(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
874     njs_index_t unused)
875 {
876     njs_int_t                 ret;
877     njs_value_t               *promise, *fulfilled, *rejected, constructor;
878     njs_object_t              *object;
879     njs_function_t            *function;
880     njs_promise_capability_t  *capability;
881 
882     promise = njs_arg(args, nargs, 0);
883 
884     if (njs_slow_path(!njs_is_object(promise))) {
885         goto failed;
886     }
887 
888     object = njs_object_proto_lookup(njs_object(promise), NJS_PROMISE,
889                                      njs_object_t);
890     if (njs_slow_path(object == NULL)) {
891         goto failed;
892     }
893 
894     function = njs_promise_create_function(vm, sizeof(njs_promise_context_t));
895     function->u.native = njs_promise_constructor;
896 
897     njs_set_function(&constructor, function);
898 
899     ret = njs_value_species_constructor(vm, promise, &constructor,
900                                         &constructor);
901     if (njs_slow_path(ret != NJS_OK)) {
902         return ret;
903     }
904 
905     capability = njs_promise_new_capability(vm, &constructor);
906     if (njs_slow_path(capability == NULL)) {
907         return NJS_ERROR;
908     }
909 
910     fulfilled = njs_arg(args, nargs, 1);
911     rejected = njs_arg(args, nargs, 2);
912 
913     return njs_promise_perform_then(vm, promise, fulfilled, rejected,
914                                     capability);
915 
916 failed:
917 
918     njs_type_error(vm, "required a promise object");
919 
920     return NJS_ERROR;
921 }
922 
923 
924 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)925 njs_promise_perform_then(njs_vm_t *vm, njs_value_t *value,
926     njs_value_t *fulfilled, njs_value_t *rejected,
927     njs_promise_capability_t *capability)
928 {
929     njs_int_t               ret;
930     njs_value_t             arguments[2];
931     njs_promise_t           *promise;
932     njs_function_t          *function;
933     njs_promise_data_t      *data;
934     njs_promise_reaction_t  *fulfilled_reaction, *rejected_reaction;
935 
936     if (!njs_is_function(fulfilled)) {
937         fulfilled = njs_value_arg(&njs_value_undefined);
938     }
939 
940     if (!njs_is_function(rejected)) {
941         rejected = njs_value_arg(&njs_value_undefined);
942     }
943 
944     promise = njs_promise(value);
945     data = njs_data(&promise->value);
946 
947     fulfilled_reaction = njs_mp_alloc(vm->mem_pool,
948                                       sizeof(njs_promise_reaction_t));
949     if (njs_slow_path(fulfilled_reaction == NULL)) {
950         njs_memory_error(vm);
951         return NJS_ERROR;
952     }
953 
954     fulfilled_reaction->capability = capability;
955     fulfilled_reaction->handler = *fulfilled;
956     fulfilled_reaction->type = NJS_PROMISE_FULFILL;
957 
958     rejected_reaction = njs_mp_alloc(vm->mem_pool,
959                                      sizeof(njs_promise_reaction_t));
960     if (njs_slow_path(rejected_reaction == NULL)) {
961         njs_memory_error(vm);
962         return NJS_ERROR;
963     }
964 
965     rejected_reaction->capability = capability;
966     rejected_reaction->handler = *rejected;
967     rejected_reaction->type = NJS_PROMISE_REJECTED;
968 
969     if (data->state == NJS_PROMISE_PENDING) {
970         njs_queue_insert_tail(&data->fulfill_queue, &fulfilled_reaction->link);
971         njs_queue_insert_tail(&data->reject_queue, &rejected_reaction->link);
972 
973     } else {
974         function = njs_promise_create_function(vm,
975                                                sizeof(njs_promise_context_t));
976         function->u.native = njs_promise_reaction_job;
977 
978         if (data->state == NJS_PROMISE_REJECTED) {
979             njs_set_data(&arguments[0], rejected_reaction, 0);
980 
981             ret = njs_promise_host_rejection_tracker(vm, promise,
982                                                      NJS_PROMISE_HANDLE);
983             if (njs_slow_path(ret != NJS_OK)) {
984                 return ret;
985             }
986 
987         } else {
988             njs_set_data(&arguments[0], fulfilled_reaction, 0);
989         }
990 
991         arguments[1] = data->result;
992 
993         ret = njs_promise_add_event(vm, function, arguments, 2);
994         if (njs_slow_path(ret != NJS_OK)) {
995             return ret;
996         }
997     }
998 
999     data->is_handled = 1;
1000 
1001     if (capability == NULL) {
1002         njs_vm_retval_set(vm, &njs_value_undefined);
1003 
1004     } else {
1005         njs_vm_retval_set(vm, &capability->promise);
1006     }
1007 
1008     return NJS_OK;
1009 }
1010 
1011 
1012 static njs_int_t
njs_promise_prototype_catch(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1013 njs_promise_prototype_catch(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1014     njs_index_t unused)
1015 {
1016     njs_value_t  arguments[2];
1017 
1018     arguments[0] = njs_value_undefined;
1019     arguments[1] = *njs_arg(args, nargs, 1);
1020 
1021     return njs_promise_invoke_then(vm, njs_arg(args, nargs, 0), arguments, 2);
1022 }
1023 
1024 
1025 static njs_int_t
njs_promise_prototype_finally(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1026 njs_promise_prototype_finally(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1027     njs_index_t unused)
1028 {
1029     njs_int_t              ret;
1030     njs_value_t            *promise, *finally, constructor, arguments[2];
1031     njs_function_t         *function;
1032     njs_promise_context_t  *context;
1033 
1034     promise = njs_arg(args, nargs, 0);
1035 
1036     if (njs_slow_path(!njs_is_object(promise))) {
1037         njs_type_error(vm, "required a object");
1038         return NJS_ERROR;
1039     }
1040 
1041     finally = njs_arg(args, nargs, 1);
1042 
1043     function = njs_promise_create_function(vm, sizeof(njs_promise_context_t));
1044     function->u.native = njs_promise_constructor;
1045 
1046     njs_set_function(&constructor, function);
1047 
1048     ret = njs_value_species_constructor(vm, promise, &constructor,
1049                                         &constructor);
1050     if (njs_slow_path(ret != NJS_OK)) {
1051         return ret;
1052     }
1053 
1054     if (!njs_is_function(finally)) {
1055         arguments[0] = *finally;
1056         arguments[1] = *finally;
1057 
1058         return njs_promise_invoke_then(vm, promise, arguments, 2);
1059     }
1060 
1061     function = njs_promise_create_function(vm, sizeof(njs_promise_context_t));
1062     if (njs_slow_path(function == NULL)) {
1063         return NJS_ERROR;
1064     }
1065 
1066     function->u.native = njs_promise_then_finally_function;
1067     function->args_count = 1;
1068 
1069     context = function->context;
1070     context->constructor = constructor;
1071     context->finally = *finally;
1072     context->handler = njs_promise_then_finally_return;
1073 
1074     njs_set_function(&arguments[0], function);
1075 
1076     function = njs_promise_create_function(vm, sizeof(njs_promise_context_t));
1077     if (njs_slow_path(function == NULL)) {
1078         njs_mp_free(vm->mem_pool, njs_function(&arguments[0]));
1079         return NJS_ERROR;
1080     }
1081 
1082     function->u.native = njs_promise_then_finally_function;
1083     function->args_count = 1;
1084 
1085     context = function->context;
1086     context->constructor = constructor;
1087     context->finally = *finally;
1088     context->handler = njs_promise_catch_finally_return;
1089 
1090     njs_set_function(&arguments[1], function);
1091 
1092     return njs_promise_invoke_then(vm, promise, arguments, 2);
1093 }
1094 
1095 
1096 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)1097 njs_promise_then_finally_function(njs_vm_t *vm, njs_value_t *args,
1098     njs_uint_t nargs, njs_index_t unused)
1099 {
1100     njs_int_t              ret;
1101     njs_value_t            value, retval, argument;
1102     njs_promise_t          *promise;
1103     njs_function_t         *function;
1104     njs_native_frame_t     *frame;
1105     njs_promise_context_t  *context;
1106 
1107     frame = vm->top_frame;
1108     context = frame->function->context;
1109 
1110     ret = njs_function_call(vm, njs_function(&context->finally),
1111                             &njs_value_undefined, args, 0, &retval);
1112     if (njs_slow_path(ret != NJS_OK)) {
1113         return ret;
1114     }
1115 
1116     promise = njs_promise_resolve(vm, &context->constructor, &retval);
1117     if (njs_slow_path(promise == NULL)) {
1118         return NJS_ERROR;
1119     }
1120 
1121     njs_set_promise(&value, promise);
1122 
1123     function = njs_promise_create_function(vm, sizeof(njs_value_t));
1124     if (njs_slow_path(function == NULL)) {
1125         return NJS_ERROR;
1126     }
1127 
1128     function->u.native = context->handler;
1129 
1130     *((njs_value_t *) function->context) = *njs_arg(args, nargs, 1);
1131 
1132     njs_set_function(&argument, function);
1133 
1134     return njs_promise_invoke_then(vm, &value, &argument, 1);
1135 }
1136 
1137 
1138 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)1139 njs_promise_then_finally_return(njs_vm_t *vm, njs_value_t *args,
1140     njs_uint_t nargs, njs_index_t unused)
1141 {
1142     njs_vm_retval_set(vm, vm->top_frame->function->context);
1143     return NJS_OK;
1144 }
1145 
1146 
1147 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)1148 njs_promise_catch_finally_return(njs_vm_t *vm, njs_value_t *args,
1149     njs_uint_t nargs, njs_index_t unused)
1150 {
1151     njs_vm_retval_set(vm, vm->top_frame->function->context);
1152     return NJS_ERROR;
1153 }
1154 
1155 
1156 static njs_int_t
njs_promise_reaction_job(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1157 njs_promise_reaction_job(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1158     njs_index_t unused)
1159 {
1160     njs_int_t                 ret;
1161     njs_bool_t                is_error;
1162     njs_value_t               *value, *argument, retval;
1163     njs_promise_reaction_t    *reaction;
1164     njs_promise_capability_t  *capability;
1165 
1166     value = njs_arg(args, nargs, 1);
1167     argument = njs_arg(args, nargs, 2);
1168 
1169     reaction = njs_data(value);
1170     capability = reaction->capability;
1171 
1172     is_error = 0;
1173 
1174     if (njs_is_undefined(&reaction->handler)) {
1175         if (reaction->type == NJS_PROMISE_REJECTED) {
1176             is_error = 1;
1177         }
1178 
1179         retval = *argument;
1180 
1181     } else {
1182         ret = njs_function_call(vm, njs_function(&reaction->handler),
1183                                 &njs_value_undefined, argument, 1, &retval);
1184         if (njs_slow_path(ret != NJS_OK)) {
1185             if (njs_slow_path(njs_is_memory_error(vm, &vm->retval))) {
1186                 return NJS_ERROR;
1187             }
1188 
1189             retval = vm->retval;
1190             is_error = 1;
1191         }
1192     }
1193 
1194     if (capability == NULL) {
1195         njs_vm_retval_set(vm, &retval);
1196         return NJS_OK;
1197     }
1198 
1199     if (is_error) {
1200         ret = njs_function_call(vm, njs_function(&capability->reject),
1201                                 &njs_value_undefined, &retval, 1, &vm->retval);
1202 
1203     } else {
1204         ret = njs_function_call(vm, njs_function(&capability->resolve),
1205                                 &njs_value_undefined, &retval, 1, &vm->retval);
1206     }
1207 
1208     if (njs_slow_path(ret != NJS_OK)) {
1209         return NJS_ERROR;
1210     }
1211 
1212     return NJS_OK;
1213 }
1214 
1215 
1216 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)1217 njs_promise_resolve_thenable_job(njs_vm_t *vm, njs_value_t *args,
1218     njs_uint_t nargs, njs_index_t unused)
1219 {
1220     njs_int_t    ret;
1221     njs_value_t  *promise, retval, arguments[2];
1222 
1223     promise = njs_arg(args, nargs, 1);
1224 
1225     ret = njs_promise_create_resolving_functions(vm, njs_promise(promise),
1226                                                  arguments);
1227     if (njs_slow_path(ret != NJS_OK)) {
1228         return ret;
1229     }
1230 
1231     ret = njs_function_call(vm, njs_function(njs_arg(args, nargs, 3)),
1232                             njs_arg(args, nargs, 2), arguments, 2, &retval);
1233     if (njs_slow_path(ret != NJS_OK)) {
1234 
1235         if (njs_slow_path(njs_is_memory_error(vm, &vm->retval))) {
1236             return NJS_ERROR;
1237         }
1238 
1239         ret = njs_function_call(vm, njs_function(&arguments[1]),
1240                                 &njs_value_undefined, &vm->retval, 1,
1241                                 &vm->retval);
1242         if (njs_slow_path(ret != NJS_OK)) {
1243             return NJS_ERROR;
1244         }
1245     }
1246 
1247     return NJS_OK;
1248 }
1249 
1250 
1251 static njs_int_t
njs_promise_all(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t function_type)1252 njs_promise_all(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1253     njs_index_t function_type)
1254 {
1255     njs_int_t                    ret;
1256     njs_value_t                  *promise, resolve;
1257     njs_iterator_handler_t       handler;
1258     njs_promise_iterator_args_t  pargs;
1259 
1260     promise = njs_argument(args, 0);
1261 
1262     pargs.capability = njs_promise_new_capability(vm, promise);
1263     if (njs_slow_path(pargs.capability == NULL)) {
1264         return NJS_ERROR;
1265     }
1266 
1267     ret = njs_value_property(vm, promise, njs_value_arg(&string_resolve),
1268                              &resolve);
1269     if (njs_slow_path(ret != NJS_OK)) {
1270         return ret;
1271     }
1272 
1273     if (njs_slow_path(!njs_is_function(&resolve))) {
1274         njs_type_error(vm, "resolve is not callable");
1275         return NJS_ERROR;
1276     }
1277 
1278     pargs.function = njs_function(&resolve);
1279     pargs.constructor = promise;
1280 
1281     switch (function_type) {
1282     case NJS_PROMISE_ALL_SETTLED:
1283         handler = njs_promise_perform_all_settled_handler;
1284         break;
1285 
1286     case NJS_PROMISE_ANY:
1287         handler = njs_promise_perform_any_handler;
1288         break;
1289 
1290     default:
1291         handler = njs_promise_perform_all_handler;
1292         break;
1293     }
1294 
1295     return njs_promise_perform_all(vm, njs_arg(args, nargs, 1), &pargs,
1296                                    handler, &vm->retval);
1297 }
1298 
1299 
1300 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)1301 njs_promise_perform_all(njs_vm_t *vm, njs_value_t *iterator,
1302     njs_promise_iterator_args_t *pargs, njs_iterator_handler_t handler,
1303     njs_value_t *retval)
1304 {
1305     int64_t       length;
1306     njs_int_t     ret;
1307     njs_value_t   argument;
1308     njs_object_t  *error;
1309 
1310     if (njs_slow_path(!njs_is_object(pargs->constructor))) {
1311         njs_type_error(vm, "constructor is not object");
1312         return NJS_ERROR;
1313     }
1314 
1315     njs_memzero(&pargs->args, sizeof(njs_iterator_args_t));
1316 
1317     ret = njs_object_length(vm, iterator, &length);
1318     if (njs_slow_path(ret != NJS_OK)) {
1319         return ret;
1320     }
1321 
1322     pargs->args.data = njs_array_alloc(vm, 1, length, 0);
1323     if (njs_slow_path(pargs->args.data == NULL)) {
1324         return NJS_ERROR;
1325     }
1326 
1327     pargs->remaining = njs_mp_alloc(vm->mem_pool, sizeof(uint32_t));
1328     if (njs_slow_path(pargs->remaining == NULL)) {
1329         njs_memory_error(vm);
1330         return NJS_ERROR;
1331     }
1332 
1333     (*pargs->remaining) = 1;
1334 
1335     pargs->args.value = iterator;
1336     pargs->args.to = length;
1337 
1338     ret = njs_object_iterate(vm, &pargs->args, handler);
1339     if (njs_slow_path(ret == NJS_ERROR)) {
1340         return ret;
1341     }
1342 
1343     if (--(*pargs->remaining) == 0) {
1344         njs_mp_free(vm->mem_pool, pargs->remaining);
1345 
1346         njs_set_array(&argument, pargs->args.data);
1347 
1348         if (handler == njs_promise_perform_any_handler) {
1349             error = njs_error_alloc(vm, NJS_OBJ_TYPE_AGGREGATE_ERROR,
1350                                     NULL, &string_any_rejected, &argument);
1351             if (njs_slow_path(error == NULL)) {
1352                 return NJS_ERROR;
1353             }
1354 
1355             njs_set_object(&argument, error);
1356         }
1357 
1358         ret = njs_function_call(vm, njs_function(&pargs->capability->resolve),
1359                                 &njs_value_undefined, &argument, 1, retval);
1360         if (njs_slow_path(ret == NJS_ERROR)) {
1361             return ret;
1362         }
1363     }
1364 
1365     *retval = pargs->capability->promise;
1366 
1367     return NJS_OK;
1368 }
1369 
1370 
1371 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)1372 njs_promise_perform_all_handler(njs_vm_t *vm, njs_iterator_args_t *args,
1373     njs_value_t *value, int64_t index)
1374 {
1375     njs_int_t                    ret;
1376     njs_array_t                  *array;
1377     njs_value_t                  arguments[2], next;
1378     njs_function_t               *on_fulfilled;
1379     njs_promise_capability_t     *capability;
1380     njs_promise_all_context_t    *context;
1381     njs_promise_iterator_args_t  *pargs;
1382 
1383     pargs = (njs_promise_iterator_args_t *) args;
1384 
1385     capability = pargs->capability;
1386 
1387     array = args->data;
1388     njs_set_undefined(&array->start[index]);
1389 
1390     ret = njs_function_call(vm, pargs->function, pargs->constructor, value,
1391                             1, &next);
1392     if (njs_slow_path(ret == NJS_ERROR)) {
1393         return ret;
1394     }
1395 
1396     on_fulfilled = njs_promise_create_function(vm,
1397                                             sizeof(njs_promise_all_context_t));
1398     if (njs_slow_path(on_fulfilled == NULL)) {
1399         return NJS_ERROR;
1400     }
1401 
1402     on_fulfilled->u.native = njs_promise_all_resolve_element_functions;
1403     on_fulfilled->args_count = 1;
1404 
1405     context = on_fulfilled->context;
1406 
1407     context->already_called = 0;
1408     context->index = (uint32_t) index;
1409     context->values = pargs->args.data;
1410     context->capability = capability;
1411     context->remaining_elements = pargs->remaining;
1412 
1413     (*pargs->remaining)++;
1414 
1415     njs_set_function(&arguments[0], on_fulfilled);
1416     arguments[1] = capability->reject;
1417 
1418     ret = njs_promise_invoke_then(vm, &next, arguments, 2);
1419     if (njs_slow_path(ret == NJS_ERROR)) {
1420         return ret;
1421     }
1422 
1423     return NJS_OK;
1424 }
1425 
1426 
1427 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)1428 njs_promise_all_resolve_element_functions(njs_vm_t *vm, njs_value_t *args,
1429     njs_uint_t nargs, njs_index_t unused)
1430 {
1431     njs_value_t                arguments;
1432     njs_promise_all_context_t  *context;
1433 
1434     context = vm->top_frame->function->context;
1435 
1436     if (context->already_called) {
1437         njs_vm_retval_set(vm, &njs_value_undefined);
1438         return NJS_OK;
1439     }
1440 
1441     context->already_called = 1;
1442     context->values->start[context->index] = *njs_arg(args, nargs, 1);
1443 
1444     if (--(*context->remaining_elements) == 0) {
1445         njs_mp_free(vm->mem_pool, context->remaining_elements);
1446 
1447         njs_set_array(&arguments, context->values);
1448 
1449         return njs_function_call(vm,
1450                                  njs_function(&context->capability->resolve),
1451                                  &njs_value_undefined, &arguments, 1,
1452                                  &vm->retval);
1453     }
1454 
1455     njs_vm_retval_set(vm, &njs_value_undefined);
1456 
1457     return NJS_OK;
1458 }
1459 
1460 
1461 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)1462 njs_promise_perform_all_settled_handler(njs_vm_t *vm, njs_iterator_args_t *args,
1463     njs_value_t *value, int64_t index)
1464 {
1465     njs_int_t                    ret;
1466     njs_array_t                  *array;
1467     njs_value_t                  arguments[2], next;
1468     njs_function_t               *on_fulfilled, *on_rejected;
1469     njs_promise_capability_t     *capability;
1470     njs_promise_all_context_t    *context;
1471     njs_promise_iterator_args_t  *pargs;
1472 
1473     pargs = (njs_promise_iterator_args_t *) args;
1474 
1475     capability = pargs->capability;
1476 
1477     array = args->data;
1478     njs_set_undefined(&array->start[index]);
1479 
1480     ret = njs_function_call(vm, pargs->function, pargs->constructor, value,
1481                             1, &next);
1482     if (njs_slow_path(ret == NJS_ERROR)) {
1483         return ret;
1484     }
1485 
1486     on_fulfilled = njs_promise_create_function(vm,
1487                                             sizeof(njs_promise_all_context_t));
1488     if (njs_slow_path(on_fulfilled == NULL)) {
1489         return NJS_ERROR;
1490     }
1491 
1492     context = on_fulfilled->context;
1493 
1494     context->already_called = 0;
1495     context->index = (uint32_t) index;
1496     context->values = pargs->args.data;
1497     context->capability = capability;
1498     context->remaining_elements = pargs->remaining;
1499 
1500     on_rejected = njs_promise_create_function(vm, 0);
1501     if (njs_slow_path(on_rejected == NULL)) {
1502         return NJS_ERROR;
1503     }
1504 
1505     on_fulfilled->u.native = njs_promise_all_settled_element_functions;
1506     on_rejected->u.native = njs_promise_all_settled_element_functions;
1507     on_rejected->magic8 = 1; /* rejected. */
1508 
1509     on_fulfilled->args_count = 1;
1510     on_rejected->args_count = 1;
1511 
1512     on_rejected->context = context;
1513 
1514     (*pargs->remaining)++;
1515 
1516     njs_set_function(&arguments[0], on_fulfilled);
1517     njs_set_function(&arguments[1], on_rejected);
1518 
1519     ret = njs_promise_invoke_then(vm, &next, arguments, 2);
1520     if (njs_slow_path(ret == NJS_ERROR)) {
1521         return ret;
1522     }
1523 
1524     return NJS_OK;
1525 }
1526 
1527 
1528 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)1529 njs_promise_all_settled_element_functions(njs_vm_t *vm,
1530     njs_value_t *args, njs_uint_t nargs, njs_index_t rejected)
1531 {
1532     njs_int_t                  ret;
1533     njs_value_t                arguments, *value;
1534     njs_object_t               *obj;
1535     const njs_value_t          *status, *set;
1536     njs_promise_all_context_t  *context;
1537 
1538     static const njs_value_t  string_status = njs_string("status");
1539     static const njs_value_t  string_fulfilled = njs_string("fulfilled");
1540     static const njs_value_t  string_value = njs_string("value");
1541     static const njs_value_t  string_rejected = njs_string("rejected");
1542     static const njs_value_t  string_reason = njs_string("reason");
1543 
1544     context = vm->top_frame->function->context;
1545 
1546     if (context->already_called) {
1547         njs_vm_retval_set(vm, &njs_value_undefined);
1548         return NJS_OK;
1549     }
1550 
1551     context->already_called = 1;
1552 
1553     obj = njs_object_alloc(vm);
1554     if (njs_slow_path(obj == NULL)) {
1555         return NJS_ERROR;
1556     }
1557 
1558     value = &context->values->start[context->index];
1559 
1560     njs_set_object(value, obj);
1561 
1562     if (rejected) {
1563         status = &string_rejected;
1564         set = &string_reason;
1565 
1566     } else {
1567         status = &string_fulfilled;
1568         set = &string_value;
1569     }
1570 
1571     ret = njs_value_property_set(vm, value, njs_value_arg(&string_status),
1572                                  njs_value_arg(status));
1573     if (njs_slow_path(ret == NJS_ERROR)) {
1574         return ret;
1575     }
1576 
1577     ret = njs_value_property_set(vm, value, njs_value_arg(set),
1578                                  njs_arg(args, nargs, 1));
1579     if (njs_slow_path(ret == NJS_ERROR)) {
1580         return ret;
1581     }
1582 
1583     if (--(*context->remaining_elements) == 0) {
1584         njs_mp_free(vm->mem_pool, context->remaining_elements);
1585 
1586         njs_set_array(&arguments, context->values);
1587 
1588         return njs_function_call(vm,
1589                                  njs_function(&context->capability->resolve),
1590                                  &njs_value_undefined, &arguments, 1,
1591                                  &vm->retval);
1592     }
1593 
1594     njs_vm_retval_set(vm, &njs_value_undefined);
1595 
1596     return NJS_OK;
1597 }
1598 
1599 
1600 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)1601 njs_promise_perform_any_handler(njs_vm_t *vm, njs_iterator_args_t *args,
1602     njs_value_t *value, int64_t index)
1603 {
1604     njs_int_t                    ret;
1605     njs_array_t                  *array;
1606     njs_value_t                  arguments[2], next;
1607     njs_function_t               *on_rejected;
1608     njs_promise_capability_t     *capability;
1609     njs_promise_all_context_t    *context;
1610     njs_promise_iterator_args_t  *pargs;
1611 
1612     pargs = (njs_promise_iterator_args_t *) args;
1613 
1614     capability = pargs->capability;
1615 
1616     array = pargs->args.data;
1617     njs_set_undefined(&array->start[index]);
1618 
1619     ret = njs_function_call(vm, pargs->function, pargs->constructor, value, 1,
1620                             &next);
1621     if (njs_slow_path(ret == NJS_ERROR)) {
1622         return ret;
1623     }
1624 
1625     on_rejected = njs_promise_create_function(vm,
1626                                             sizeof(njs_promise_all_context_t));
1627     if (njs_slow_path(on_rejected == NULL)) {
1628         return NJS_ERROR;
1629     }
1630 
1631     on_rejected->u.native = njs_promise_any_reject_element_functions;
1632     on_rejected->args_count = 1;
1633 
1634     context = on_rejected->context;
1635 
1636     context->already_called = 0;
1637     context->index = (uint32_t) index;
1638     context->values = pargs->args.data;
1639     context->capability = capability;
1640     context->remaining_elements = pargs->remaining;
1641 
1642     (*pargs->remaining)++;
1643 
1644     arguments[0] = capability->resolve;
1645     njs_set_function(&arguments[1], on_rejected);
1646 
1647     ret = njs_promise_invoke_then(vm, &next, arguments, 2);
1648     if (njs_slow_path(ret == NJS_ERROR)) {
1649         return ret;
1650     }
1651 
1652     return NJS_OK;
1653 }
1654 
1655 
1656 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)1657 njs_promise_any_reject_element_functions(njs_vm_t *vm, njs_value_t *args,
1658     njs_uint_t nargs, njs_index_t unused)
1659 {
1660     njs_value_t                argument;
1661     njs_object_t               *error;
1662     njs_promise_all_context_t  *context;
1663 
1664     context = vm->top_frame->function->context;
1665 
1666     if (context->already_called) {
1667         njs_vm_retval_set(vm, &njs_value_undefined);
1668         return NJS_OK;
1669     }
1670 
1671     context->already_called = 1;
1672     context->values->start[context->index] = *njs_arg(args, nargs, 1);
1673 
1674     if (--(*context->remaining_elements) == 0) {
1675         njs_mp_free(vm->mem_pool, context->remaining_elements);
1676 
1677         njs_set_array(&argument, context->values);
1678 
1679         error = njs_error_alloc(vm, NJS_OBJ_TYPE_AGGREGATE_ERROR,
1680                                 NULL, &string_any_rejected, &argument);
1681         if (njs_slow_path(error == NULL)) {
1682             return NJS_ERROR;
1683         }
1684 
1685         njs_set_object(&argument, error);
1686 
1687         return njs_function_call(vm, njs_function(&context->capability->reject),
1688                                  &njs_value_undefined, &argument, 1,
1689                                  &vm->retval);
1690     }
1691 
1692     njs_vm_retval_set(vm, &njs_value_undefined);
1693 
1694     return NJS_OK;
1695 }
1696 
1697 
1698 static njs_int_t
njs_promise_race(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1699 njs_promise_race(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1700     njs_index_t unused)
1701 {
1702     int64_t                      length;
1703     njs_int_t                    ret;
1704     njs_value_t                  *promise, *iterator, resolve;
1705     njs_promise_iterator_args_t  pargs;
1706 
1707     promise = njs_argument(args, 0);
1708     iterator = njs_arg(args, nargs, 1);
1709 
1710     pargs.capability = njs_promise_new_capability(vm, promise);
1711     if (njs_slow_path(pargs.capability == NULL)) {
1712         return NJS_ERROR;
1713     }
1714 
1715     ret = njs_value_property(vm, promise, njs_value_arg(&string_resolve),
1716                              &resolve);
1717     if (njs_slow_path(ret != NJS_OK)) {
1718         return ret;
1719     }
1720 
1721     if (njs_slow_path(!njs_is_function(&resolve))) {
1722         njs_type_error(vm, "resolve is not callable");
1723         return NJS_ERROR;
1724     }
1725 
1726     ret = njs_object_length(vm, iterator, &length);
1727     if (njs_slow_path(ret != NJS_OK)) {
1728         return ret;
1729     }
1730 
1731     njs_memzero(&pargs.args, sizeof(njs_iterator_args_t));
1732 
1733     pargs.function = njs_function(&resolve);
1734     pargs.constructor = promise;
1735 
1736     pargs.args.value = iterator;
1737     pargs.args.to = length;
1738 
1739     ret = njs_object_iterate(vm, &pargs.args, njs_promise_perform_race_handler);
1740     if (njs_slow_path(ret == NJS_ERROR)) {
1741         return ret;
1742     }
1743 
1744     vm->retval = pargs.capability->promise;
1745 
1746     return NJS_OK;
1747 }
1748 
1749 
1750 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)1751 njs_promise_perform_race_handler(njs_vm_t *vm, njs_iterator_args_t *args,
1752     njs_value_t *value, int64_t index)
1753 {
1754     njs_int_t                    ret;
1755     njs_value_t                  arguments[2], next;
1756     njs_promise_capability_t     *capability;
1757     njs_promise_iterator_args_t  *pargs;
1758 
1759     pargs = (njs_promise_iterator_args_t *) args;
1760 
1761     ret = njs_function_call(vm, pargs->function, pargs->constructor, value,
1762                             1, &next);
1763     if (njs_slow_path(ret == NJS_ERROR)) {
1764         return ret;
1765     }
1766 
1767     capability = pargs->capability;
1768 
1769     arguments[0] = capability->resolve;
1770     arguments[1] = capability->reject;
1771 
1772     (void) njs_promise_invoke_then(vm, &next, arguments, 2);
1773 
1774     return NJS_OK;
1775 }
1776 
1777 
1778 static njs_int_t
njs_promise_species(njs_vm_t * vm,njs_value_t * args,njs_uint_t nargs,njs_index_t unused)1779 njs_promise_species(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
1780     njs_index_t unused)
1781 {
1782     njs_vm_retval_set(vm, njs_arg(args, nargs, 0));
1783 
1784     return NJS_OK;
1785 }
1786 
1787 
1788 static const njs_object_prop_t  njs_promise_constructor_properties[] =
1789 {
1790     {
1791         .type = NJS_PROPERTY,
1792         .name = njs_string("name"),
1793         .value = njs_string("Promise"),
1794         .configurable = 1,
1795     },
1796 
1797     {
1798         .type = NJS_PROPERTY,
1799         .name = njs_string("length"),
1800         .value = njs_value(NJS_NUMBER, 1, 1.0),
1801         .configurable = 1,
1802     },
1803 
1804     {
1805         .type = NJS_PROPERTY_HANDLER,
1806         .name = njs_string("prototype"),
1807         .value = njs_prop_handler(njs_object_prototype_create),
1808     },
1809 
1810     {
1811         .type = NJS_PROPERTY,
1812         .name = njs_string("resolve"),
1813         .value = njs_native_function(njs_promise_object_resolve, 1),
1814         .writable = 1,
1815         .configurable = 1,
1816     },
1817 
1818     {
1819         .type = NJS_PROPERTY,
1820         .name = njs_string("reject"),
1821         .value = njs_native_function(njs_promise_object_reject, 1),
1822         .writable = 1,
1823         .configurable = 1,
1824     },
1825 
1826     {
1827         .type = NJS_PROPERTY,
1828         .name = njs_string("all"),
1829         .value = njs_native_function2(njs_promise_all, 1, NJS_PROMISE_ALL),
1830         .writable = 1,
1831         .configurable = 1,
1832     },
1833 
1834     {
1835         .type = NJS_PROPERTY,
1836         .name = njs_string("allSettled"),
1837         .value = njs_native_function2(njs_promise_all, 1,
1838                                       NJS_PROMISE_ALL_SETTLED),
1839         .writable = 1,
1840         .configurable = 1,
1841     },
1842 
1843     {
1844         .type = NJS_PROPERTY,
1845         .name = njs_string("any"),
1846         .value = njs_native_function2(njs_promise_all, 1, NJS_PROMISE_ANY),
1847         .writable = 1,
1848         .configurable = 1,
1849     },
1850 
1851     {
1852         .type = NJS_PROPERTY,
1853         .name = njs_string("race"),
1854         .value = njs_native_function(njs_promise_race, 1),
1855         .writable = 1,
1856         .configurable = 1,
1857     },
1858 
1859     {
1860         .type = NJS_PROPERTY,
1861         .name = njs_wellknown_symbol(NJS_SYMBOL_SPECIES),
1862         .value = njs_value(NJS_INVALID, 1, NAN),
1863         .getter = njs_native_function(njs_promise_species, 0),
1864         .setter = njs_value(NJS_UNDEFINED, 0, NAN),
1865         .writable = NJS_ATTRIBUTE_UNSET,
1866         .configurable = 1,
1867         .enumerable = 0,
1868     },
1869 };
1870 
1871 
1872 const njs_object_init_t  njs_promise_constructor_init = {
1873     njs_promise_constructor_properties,
1874     njs_nitems(njs_promise_constructor_properties),
1875 };
1876 
1877 
1878 static const njs_object_prop_t  njs_promise_prototype_properties[] =
1879 {
1880     {
1881         .type = NJS_PROPERTY_HANDLER,
1882         .name = njs_string("constructor"),
1883         .value = njs_prop_handler(njs_object_prototype_create_constructor),
1884         .writable = 1,
1885         .configurable = 1,
1886     },
1887 
1888     {
1889         .type = NJS_PROPERTY,
1890         .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG),
1891         .value = njs_string("Promise"),
1892         .configurable = 1,
1893     },
1894 
1895     {
1896         .type = NJS_PROPERTY,
1897         .name = njs_string("then"),
1898         .value = njs_native_function(njs_promise_prototype_then, 2),
1899         .writable = 1,
1900         .configurable = 1,
1901     },
1902 
1903     {
1904         .type = NJS_PROPERTY,
1905         .name = njs_string("catch"),
1906         .value = njs_native_function(njs_promise_prototype_catch, 1),
1907         .writable = 1,
1908         .configurable = 1,
1909     },
1910 
1911     {
1912         .type = NJS_PROPERTY,
1913         .name = njs_string("finally"),
1914         .value = njs_native_function(njs_promise_prototype_finally, 1),
1915         .writable = 1,
1916         .configurable = 1,
1917     },
1918 };
1919 
1920 
1921 const njs_object_init_t  njs_promise_prototype_init = {
1922     njs_promise_prototype_properties,
1923     njs_nitems(njs_promise_prototype_properties),
1924 };
1925 
1926 const njs_object_type_init_t  njs_promise_type_init = {
1927     .constructor = njs_native_ctor(njs_promise_constructor, 1, 0),
1928     .prototype_props = &njs_promise_prototype_init,
1929     .constructor_props = &njs_promise_constructor_init,
1930     .prototype_value = { .object = { .type = NJS_OBJECT } },
1931 };
1932