cosmopolitan/third_party/quickjs/promise.c
2021-08-05 14:43:53 -07:00

1569 lines
58 KiB
C

/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2021 Fabrice Bellard
* Copyright (c) 2017-2021 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "libc/assert.h"
#include "third_party/quickjs/internal.h"
asm(".ident\t\"\\n\\n\
QuickJS (MIT License)\\n\
Copyright (c) 2017-2021 Fabrice Bellard\\n\
Copyright (c) 2017-2021 Charlie Gordon\"");
asm(".include \"libc/disclaimer.inc\"");
/* clang-format off */
typedef enum JSPromiseStateEnum {
JS_PROMISE_PENDING,
JS_PROMISE_FULFILLED,
JS_PROMISE_REJECTED,
} JSPromiseStateEnum;
typedef struct JSPromiseData {
JSPromiseStateEnum promise_state;
/* 0=fulfill, 1=reject, list of JSPromiseReactionData.link */
struct list_head promise_reactions[2];
BOOL is_handled; /* Note: only useful to debug */
JSValue promise_result;
} JSPromiseData;
typedef struct JSPromiseFunctionDataResolved {
int ref_count;
BOOL already_resolved;
} JSPromiseFunctionDataResolved;
typedef struct JSPromiseFunctionData {
JSValue promise;
JSPromiseFunctionDataResolved *presolved;
} JSPromiseFunctionData;
typedef struct JSPromiseReactionData {
struct list_head link; /* not used in promise_reaction_job */
JSValue resolving_funcs[2];
JSValue handler;
} JSPromiseReactionData;
static int js_create_resolving_functions(JSContext *ctx, JSValue *args,
JSValueConst promise);
static void promise_reaction_data_free(JSRuntime *rt,
JSPromiseReactionData *rd)
{
JS_FreeValueRT(rt, rd->resolving_funcs[0]);
JS_FreeValueRT(rt, rd->resolving_funcs[1]);
JS_FreeValueRT(rt, rd->handler);
js_free_rt(rt, rd);
}
static JSValue promise_reaction_job(JSContext *ctx, int argc,
JSValueConst *argv)
{
JSValueConst handler, arg, func;
JSValue res, res2;
BOOL is_reject;
assert(argc == 5);
handler = argv[2];
is_reject = JS_ToBool(ctx, argv[3]);
arg = argv[4];
#ifdef DUMP_PROMISE
printf("promise_reaction_job: is_reject=%d\n", is_reject);
#endif
if (JS_IsUndefined(handler)) {
if (is_reject) {
res = JS_Throw(ctx, JS_DupValue(ctx, arg));
} else {
res = JS_DupValue(ctx, arg);
}
} else {
res = JS_Call(ctx, handler, JS_UNDEFINED, 1, &arg);
}
is_reject = JS_IsException(res);
if (is_reject)
res = JS_GetException(ctx);
func = argv[is_reject];
/* as an extension, we support undefined as value to avoid
creating a dummy promise in the 'await' implementation of async
functions */
if (!JS_IsUndefined(func)) {
res2 = JS_Call(ctx, func, JS_UNDEFINED,
1, (JSValueConst *)&res);
} else {
res2 = JS_UNDEFINED;
}
JS_FreeValue(ctx, res);
return res2;
}
void JS_SetHostPromiseRejectionTracker(JSRuntime *rt,
JSHostPromiseRejectionTracker *cb,
void *opaque)
{
rt->host_promise_rejection_tracker = cb;
rt->host_promise_rejection_tracker_opaque = opaque;
}
static void fulfill_or_reject_promise(JSContext *ctx, JSValueConst promise,
JSValueConst value, BOOL is_reject)
{
JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE);
struct list_head *el, *el1;
JSPromiseReactionData *rd;
JSValueConst args[5];
if (!s || s->promise_state != JS_PROMISE_PENDING)
return; /* should never happen */
set_value(ctx, &s->promise_result, JS_DupValue(ctx, value));
s->promise_state = JS_PROMISE_FULFILLED + is_reject;
#ifdef DUMP_PROMISE
printf("fulfill_or_reject_promise: is_reject=%d\n", is_reject);
#endif
if (s->promise_state == JS_PROMISE_REJECTED && !s->is_handled) {
JSRuntime *rt = ctx->rt;
if (rt->host_promise_rejection_tracker) {
rt->host_promise_rejection_tracker(ctx, promise, value, FALSE,
rt->host_promise_rejection_tracker_opaque);
}
}
list_for_each_safe(el, el1, &s->promise_reactions[is_reject]) {
rd = list_entry(el, JSPromiseReactionData, link);
args[0] = rd->resolving_funcs[0];
args[1] = rd->resolving_funcs[1];
args[2] = rd->handler;
args[3] = JS_NewBool(ctx, is_reject);
args[4] = value;
JS_EnqueueJob(ctx, promise_reaction_job, 5, args);
list_del(&rd->link);
promise_reaction_data_free(ctx->rt, rd);
}
list_for_each_safe(el, el1, &s->promise_reactions[1 - is_reject]) {
rd = list_entry(el, JSPromiseReactionData, link);
list_del(&rd->link);
promise_reaction_data_free(ctx->rt, rd);
}
}
static void reject_promise(JSContext *ctx, JSValueConst promise,
JSValueConst value)
{
fulfill_or_reject_promise(ctx, promise, value, TRUE);
}
static JSValue js_promise_resolve_thenable_job(JSContext *ctx,
int argc, JSValueConst *argv)
{
JSValueConst promise, thenable, then;
JSValue args[2], res;
#ifdef DUMP_PROMISE
printf("js_promise_resolve_thenable_job\n");
#endif
assert(argc == 3);
promise = argv[0];
thenable = argv[1];
then = argv[2];
if (js_create_resolving_functions(ctx, args, promise) < 0)
return JS_EXCEPTION;
res = JS_Call(ctx, then, thenable, 2, (JSValueConst *)args);
if (JS_IsException(res)) {
JSValue error = JS_GetException(ctx);
res = JS_Call(ctx, args[1], JS_UNDEFINED, 1, (JSValueConst *)&error);
JS_FreeValue(ctx, error);
}
JS_FreeValue(ctx, args[0]);
JS_FreeValue(ctx, args[1]);
return res;
}
static void js_promise_resolve_function_free_resolved(JSRuntime *rt,
JSPromiseFunctionDataResolved *sr)
{
if (--sr->ref_count == 0) {
js_free_rt(rt, sr);
}
}
static int js_create_resolving_functions(JSContext *ctx,
JSValue *resolving_funcs,
JSValueConst promise)
{
JSValue obj;
JSPromiseFunctionData *s;
JSPromiseFunctionDataResolved *sr;
int i, ret;
sr = js_malloc(ctx, sizeof(*sr));
if (!sr)
return -1;
sr->ref_count = 1;
sr->already_resolved = FALSE; /* must be shared between the two functions */
ret = 0;
for(i = 0; i < 2; i++) {
obj = JS_NewObjectProtoClass(ctx, ctx->function_proto,
JS_CLASS_PROMISE_RESOLVE_FUNCTION + i);
if (JS_IsException(obj))
goto fail;
s = js_malloc(ctx, sizeof(*s));
if (!s) {
JS_FreeValue(ctx, obj);
fail:
if (i != 0)
JS_FreeValue(ctx, resolving_funcs[0]);
ret = -1;
break;
}
sr->ref_count++;
s->presolved = sr;
s->promise = JS_DupValue(ctx, promise);
JS_SetOpaque(obj, s);
js_function_set_properties(ctx, obj, JS_ATOM_empty_string, 1);
resolving_funcs[i] = obj;
}
js_promise_resolve_function_free_resolved(ctx->rt, sr);
return ret;
}
static void js_promise_resolve_function_finalizer(JSRuntime *rt, JSValue val)
{
JSPromiseFunctionData *s = JS_VALUE_GET_OBJ(val)->u.promise_function_data;
if (s) {
js_promise_resolve_function_free_resolved(rt, s->presolved);
JS_FreeValueRT(rt, s->promise);
js_free_rt(rt, s);
}
}
static void js_promise_resolve_function_mark(JSRuntime *rt, JSValueConst val,
JS_MarkFunc *mark_func)
{
JSPromiseFunctionData *s = JS_VALUE_GET_OBJ(val)->u.promise_function_data;
if (s) {
JS_MarkValue(rt, s->promise, mark_func);
}
}
static JSValue js_promise_resolve_function_call(JSContext *ctx,
JSValueConst func_obj,
JSValueConst this_val,
int argc, JSValueConst *argv,
int flags)
{
JSObject *p = JS_VALUE_GET_OBJ(func_obj);
JSPromiseFunctionData *s;
JSValueConst resolution, args[3];
JSValue then;
BOOL is_reject;
s = p->u.promise_function_data;
if (!s || s->presolved->already_resolved)
return JS_UNDEFINED;
s->presolved->already_resolved = TRUE;
is_reject = p->class_id - JS_CLASS_PROMISE_RESOLVE_FUNCTION;
if (argc > 0)
resolution = argv[0];
else
resolution = JS_UNDEFINED;
#ifdef DUMP_PROMISE
printf("js_promise_resolving_function_call: is_reject=%d resolution=", is_reject);
JS_DumpValue(ctx, resolution);
printf("\n");
#endif
if (is_reject || !JS_IsObject(resolution)) {
goto done;
} else if (js_same_value(ctx, resolution, s->promise)) {
JS_ThrowTypeError(ctx, "promise self resolution");
goto fail_reject;
}
then = JS_GetProperty(ctx, resolution, JS_ATOM_then);
if (JS_IsException(then)) {
JSValue error;
fail_reject:
error = JS_GetException(ctx);
reject_promise(ctx, s->promise, error);
JS_FreeValue(ctx, error);
} else if (!JS_IsFunction(ctx, then)) {
JS_FreeValue(ctx, then);
done:
fulfill_or_reject_promise(ctx, s->promise, resolution, is_reject);
} else {
args[0] = s->promise;
args[1] = resolution;
args[2] = then;
JS_EnqueueJob(ctx, js_promise_resolve_thenable_job, 3, args);
JS_FreeValue(ctx, then);
}
return JS_UNDEFINED;
}
static void js_promise_finalizer(JSRuntime *rt, JSValue val)
{
JSPromiseData *s = JS_GetOpaque(val, JS_CLASS_PROMISE);
struct list_head *el, *el1;
int i;
if (!s)
return;
for(i = 0; i < 2; i++) {
list_for_each_safe(el, el1, &s->promise_reactions[i]) {
JSPromiseReactionData *rd =
list_entry(el, JSPromiseReactionData, link);
promise_reaction_data_free(rt, rd);
}
}
JS_FreeValueRT(rt, s->promise_result);
js_free_rt(rt, s);
}
static void js_promise_mark(JSRuntime *rt, JSValueConst val,
JS_MarkFunc *mark_func)
{
JSPromiseData *s = JS_GetOpaque(val, JS_CLASS_PROMISE);
struct list_head *el;
int i;
if (!s)
return;
for(i = 0; i < 2; i++) {
list_for_each(el, &s->promise_reactions[i]) {
JSPromiseReactionData *rd =
list_entry(el, JSPromiseReactionData, link);
JS_MarkValue(rt, rd->resolving_funcs[0], mark_func);
JS_MarkValue(rt, rd->resolving_funcs[1], mark_func);
JS_MarkValue(rt, rd->handler, mark_func);
}
}
JS_MarkValue(rt, s->promise_result, mark_func);
}
static JSValue js_promise_constructor(JSContext *ctx, JSValueConst new_target,
int argc, JSValueConst *argv)
{
JSValueConst executor;
JSValue obj;
JSPromiseData *s;
JSValue args[2], ret;
int i;
executor = argv[0];
if (check_function(ctx, executor))
return JS_EXCEPTION;
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_PROMISE);
if (JS_IsException(obj))
return JS_EXCEPTION;
s = js_mallocz(ctx, sizeof(*s));
if (!s)
goto fail;
s->promise_state = JS_PROMISE_PENDING;
s->is_handled = FALSE;
for(i = 0; i < 2; i++)
init_list_head(&s->promise_reactions[i]);
s->promise_result = JS_UNDEFINED;
JS_SetOpaque(obj, s);
if (js_create_resolving_functions(ctx, args, obj))
goto fail;
ret = JS_Call(ctx, executor, JS_UNDEFINED, 2, (JSValueConst *)args);
if (JS_IsException(ret)) {
JSValue ret2, error;
error = JS_GetException(ctx);
ret2 = JS_Call(ctx, args[1], JS_UNDEFINED, 1, (JSValueConst *)&error);
JS_FreeValue(ctx, error);
if (JS_IsException(ret2))
goto fail1;
JS_FreeValue(ctx, ret2);
}
JS_FreeValue(ctx, ret);
JS_FreeValue(ctx, args[0]);
JS_FreeValue(ctx, args[1]);
return obj;
fail1:
JS_FreeValue(ctx, args[0]);
JS_FreeValue(ctx, args[1]);
fail:
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
static JSValue js_promise_executor(JSContext *ctx,
JSValueConst this_val,
int argc, JSValueConst *argv,
int magic, JSValue *func_data)
{
int i;
for(i = 0; i < 2; i++) {
if (!JS_IsUndefined(func_data[i]))
return JS_ThrowTypeError(ctx, "resolving function already set");
func_data[i] = JS_DupValue(ctx, argv[i]);
}
return JS_UNDEFINED;
}
static JSValue js_promise_executor_new(JSContext *ctx)
{
JSValueConst func_data[2];
func_data[0] = JS_UNDEFINED;
func_data[1] = JS_UNDEFINED;
return JS_NewCFunctionData(ctx, js_promise_executor, 2,
0, 2, func_data);
}
static JSValue js_new_promise_capability(JSContext *ctx,
JSValue *resolving_funcs,
JSValueConst ctor)
{
JSValue executor, result_promise;
JSCFunctionDataRecord *s;
int i;
executor = js_promise_executor_new(ctx);
if (JS_IsException(executor))
return executor;
if (JS_IsUndefined(ctor)) {
result_promise = js_promise_constructor(ctx, ctor, 1,
(JSValueConst *)&executor);
} else {
result_promise = JS_CallConstructor(ctx, ctor, 1,
(JSValueConst *)&executor);
}
if (JS_IsException(result_promise))
goto fail;
s = JS_GetOpaque(executor, JS_CLASS_C_FUNCTION_DATA);
for(i = 0; i < 2; i++) {
if (check_function(ctx, s->data[i]))
goto fail;
}
for(i = 0; i < 2; i++)
resolving_funcs[i] = JS_DupValue(ctx, s->data[i]);
JS_FreeValue(ctx, executor);
return result_promise;
fail:
JS_FreeValue(ctx, executor);
JS_FreeValue(ctx, result_promise);
return JS_EXCEPTION;
}
JSValue JS_NewPromiseCapability(JSContext *ctx, JSValue *resolving_funcs)
{
return js_new_promise_capability(ctx, resolving_funcs, JS_UNDEFINED);
}
JSValue js_promise_resolve(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int magic)
{
JSValue result_promise, resolving_funcs[2], ret;
BOOL is_reject = magic;
if (!JS_IsObject(this_val))
return JS_ThrowTypeErrorNotAnObject(ctx);
if (!is_reject && JS_GetOpaque(argv[0], JS_CLASS_PROMISE)) {
JSValue ctor;
BOOL is_same;
ctor = JS_GetProperty(ctx, argv[0], JS_ATOM_constructor);
if (JS_IsException(ctor))
return ctor;
is_same = js_same_value(ctx, ctor, this_val);
JS_FreeValue(ctx, ctor);
if (is_same)
return JS_DupValue(ctx, argv[0]);
}
result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val);
if (JS_IsException(result_promise))
return result_promise;
ret = JS_Call(ctx, resolving_funcs[is_reject], JS_UNDEFINED, 1, argv);
JS_FreeValue(ctx, resolving_funcs[0]);
JS_FreeValue(ctx, resolving_funcs[1]);
if (JS_IsException(ret)) {
JS_FreeValue(ctx, result_promise);
return ret;
}
JS_FreeValue(ctx, ret);
return result_promise;
}
#if 0
static JSValue js_promise___newPromiseCapability(JSContext *ctx,
JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue result_promise, resolving_funcs[2], obj;
JSValueConst ctor;
ctor = argv[0];
if (!JS_IsObject(ctor))
return JS_ThrowTypeErrorNotAnObject(ctx);
result_promise = js_new_promise_capability(ctx, resolving_funcs, ctor);
if (JS_IsException(result_promise))
return result_promise;
obj = JS_NewObject(ctx);
if (JS_IsException(obj)) {
JS_FreeValue(ctx, resolving_funcs[0]);
JS_FreeValue(ctx, resolving_funcs[1]);
JS_FreeValue(ctx, result_promise);
return JS_EXCEPTION;
}
JS_DefinePropertyValue(ctx, obj, JS_ATOM_promise, result_promise, JS_PROP_C_W_E);
JS_DefinePropertyValue(ctx, obj, JS_ATOM_resolve, resolving_funcs[0], JS_PROP_C_W_E);
JS_DefinePropertyValue(ctx, obj, JS_ATOM_reject, resolving_funcs[1], JS_PROP_C_W_E);
return obj;
}
#endif
static __exception int remainingElementsCount_add(JSContext *ctx,
JSValueConst resolve_element_env,
int addend)
{
JSValue val;
int remainingElementsCount;
val = JS_GetPropertyUint32(ctx, resolve_element_env, 0);
if (JS_IsException(val))
return -1;
if (JS_ToInt32Free(ctx, &remainingElementsCount, val))
return -1;
remainingElementsCount += addend;
if (JS_SetPropertyUint32(ctx, resolve_element_env, 0,
JS_NewInt32(ctx, remainingElementsCount)) < 0)
return -1;
return (remainingElementsCount == 0);
}
#define PROMISE_MAGIC_all 0
#define PROMISE_MAGIC_allSettled 1
#define PROMISE_MAGIC_any 2
/* used by C code. */
static JSValue js_aggregate_error_constructor(JSContext *ctx,
JSValueConst errors)
{
JSValue obj;
obj = JS_NewObjectProtoClass(ctx,
ctx->native_error_proto[JS_AGGREGATE_ERROR],
JS_CLASS_ERROR);
if (JS_IsException(obj))
return obj;
JS_DefinePropertyValue(ctx, obj, JS_ATOM_errors, JS_DupValue(ctx, errors),
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
return obj;
}
static JSValue js_promise_all_resolve_element(JSContext *ctx,
JSValueConst this_val,
int argc, JSValueConst *argv,
int magic,
JSValue *func_data)
{
int resolve_type = magic & 3;
int is_reject = magic & 4;
BOOL alreadyCalled = JS_ToBool(ctx, func_data[0]);
JSValueConst values = func_data[2];
JSValueConst resolve = func_data[3];
JSValueConst resolve_element_env = func_data[4];
JSValue ret, obj;
int is_zero, index;
if (JS_ToInt32(ctx, &index, func_data[1]))
return JS_EXCEPTION;
if (alreadyCalled)
return JS_UNDEFINED;
func_data[0] = JS_NewBool(ctx, TRUE);
if (resolve_type == PROMISE_MAGIC_allSettled) {
JSValue str;
obj = JS_NewObject(ctx);
if (JS_IsException(obj))
return JS_EXCEPTION;
str = JS_NewString(ctx, is_reject ? "rejected" : "fulfilled");
if (JS_IsException(str))
goto fail1;
if (JS_DefinePropertyValue(ctx, obj, JS_ATOM_status,
str,
JS_PROP_C_W_E) < 0)
goto fail1;
if (JS_DefinePropertyValue(ctx, obj,
is_reject ? JS_ATOM_reason : JS_ATOM_value,
JS_DupValue(ctx, argv[0]),
JS_PROP_C_W_E) < 0) {
fail1:
JS_FreeValue(ctx, obj);
return JS_EXCEPTION;
}
} else {
obj = JS_DupValue(ctx, argv[0]);
}
if (JS_DefinePropertyValueUint32(ctx, values, index,
obj, JS_PROP_C_W_E) < 0)
return JS_EXCEPTION;
is_zero = remainingElementsCount_add(ctx, resolve_element_env, -1);
if (is_zero < 0)
return JS_EXCEPTION;
if (is_zero) {
if (resolve_type == PROMISE_MAGIC_any) {
JSValue error;
error = js_aggregate_error_constructor(ctx, values);
if (JS_IsException(error))
return JS_EXCEPTION;
ret = JS_Call(ctx, resolve, JS_UNDEFINED, 1, (JSValueConst *)&error);
JS_FreeValue(ctx, error);
} else {
ret = JS_Call(ctx, resolve, JS_UNDEFINED, 1, (JSValueConst *)&values);
}
if (JS_IsException(ret))
return ret;
JS_FreeValue(ctx, ret);
}
return JS_UNDEFINED;
}
/* magic = 0: Promise.all 1: Promise.allSettled */
static JSValue js_promise_all(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv, int magic)
{
JSValue result_promise, resolving_funcs[2], item, next_promise, ret;
JSValue next_method = JS_UNDEFINED, values = JS_UNDEFINED;
JSValue resolve_element_env = JS_UNDEFINED, resolve_element, reject_element;
JSValue promise_resolve = JS_UNDEFINED, iter = JS_UNDEFINED;
JSValueConst then_args[2], resolve_element_data[5];
BOOL done;
int index, is_zero, is_promise_any = (magic == PROMISE_MAGIC_any);
if (!JS_IsObject(this_val))
return JS_ThrowTypeErrorNotAnObject(ctx);
result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val);
if (JS_IsException(result_promise))
return result_promise;
promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve);
if (JS_IsException(promise_resolve) ||
check_function(ctx, promise_resolve))
goto fail_reject;
iter = JS_GetIterator(ctx, argv[0], FALSE);
if (JS_IsException(iter)) {
JSValue error;
fail_reject:
error = JS_GetException(ctx);
ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1,
(JSValueConst *)&error);
JS_FreeValue(ctx, error);
if (JS_IsException(ret))
goto fail;
JS_FreeValue(ctx, ret);
} else {
next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
if (JS_IsException(next_method))
goto fail_reject;
values = JS_NewArray(ctx);
if (JS_IsException(values))
goto fail_reject;
resolve_element_env = JS_NewArray(ctx);
if (JS_IsException(resolve_element_env))
goto fail_reject;
/* remainingElementsCount field */
if (JS_DefinePropertyValueUint32(ctx, resolve_element_env, 0,
JS_NewInt32(ctx, 1),
JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE | JS_PROP_WRITABLE) < 0)
goto fail_reject;
index = 0;
for(;;) {
/* XXX: conformance: should close the iterator if error on 'done'
access, but not on 'value' access */
item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
if (JS_IsException(item))
goto fail_reject;
if (done)
break;
next_promise = JS_Call(ctx, promise_resolve,
this_val, 1, (JSValueConst *)&item);
JS_FreeValue(ctx, item);
if (JS_IsException(next_promise)) {
fail_reject1:
JS_IteratorClose(ctx, iter, TRUE);
goto fail_reject;
}
resolve_element_data[0] = JS_NewBool(ctx, FALSE);
resolve_element_data[1] = (JSValueConst)JS_NewInt32(ctx, index);
resolve_element_data[2] = values;
resolve_element_data[3] = resolving_funcs[is_promise_any];
resolve_element_data[4] = resolve_element_env;
resolve_element =
JS_NewCFunctionData(ctx, js_promise_all_resolve_element, 1,
magic, 5, resolve_element_data);
if (JS_IsException(resolve_element)) {
JS_FreeValue(ctx, next_promise);
goto fail_reject1;
}
if (magic == PROMISE_MAGIC_allSettled) {
reject_element =
JS_NewCFunctionData(ctx, js_promise_all_resolve_element, 1,
magic | 4, 5, resolve_element_data);
if (JS_IsException(reject_element)) {
JS_FreeValue(ctx, next_promise);
goto fail_reject1;
}
} else if (magic == PROMISE_MAGIC_any) {
if (JS_DefinePropertyValueUint32(ctx, values, index,
JS_UNDEFINED, JS_PROP_C_W_E) < 0)
goto fail_reject1;
reject_element = resolve_element;
resolve_element = JS_DupValue(ctx, resolving_funcs[0]);
} else {
reject_element = JS_DupValue(ctx, resolving_funcs[1]);
}
if (remainingElementsCount_add(ctx, resolve_element_env, 1) < 0) {
JS_FreeValue(ctx, next_promise);
JS_FreeValue(ctx, resolve_element);
JS_FreeValue(ctx, reject_element);
goto fail_reject1;
}
then_args[0] = resolve_element;
then_args[1] = reject_element;
ret = JS_InvokeFree(ctx, next_promise, JS_ATOM_then, 2, then_args);
JS_FreeValue(ctx, resolve_element);
JS_FreeValue(ctx, reject_element);
if (check_exception_free(ctx, ret))
goto fail_reject1;
index++;
}
is_zero = remainingElementsCount_add(ctx, resolve_element_env, -1);
if (is_zero < 0)
goto fail_reject;
if (is_zero) {
if (magic == PROMISE_MAGIC_any) {
JSValue error;
error = js_aggregate_error_constructor(ctx, values);
if (JS_IsException(error))
goto fail_reject;
JS_FreeValue(ctx, values);
values = error;
}
ret = JS_Call(ctx, resolving_funcs[is_promise_any], JS_UNDEFINED,
1, (JSValueConst *)&values);
if (check_exception_free(ctx, ret))
goto fail_reject;
}
}
done:
JS_FreeValue(ctx, promise_resolve);
JS_FreeValue(ctx, resolve_element_env);
JS_FreeValue(ctx, values);
JS_FreeValue(ctx, next_method);
JS_FreeValue(ctx, iter);
JS_FreeValue(ctx, resolving_funcs[0]);
JS_FreeValue(ctx, resolving_funcs[1]);
return result_promise;
fail:
JS_FreeValue(ctx, result_promise);
result_promise = JS_EXCEPTION;
goto done;
}
static JSValue js_promise_race(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue result_promise, resolving_funcs[2], item, next_promise, ret;
JSValue next_method = JS_UNDEFINED, iter = JS_UNDEFINED;
JSValue promise_resolve = JS_UNDEFINED;
BOOL done;
if (!JS_IsObject(this_val))
return JS_ThrowTypeErrorNotAnObject(ctx);
result_promise = js_new_promise_capability(ctx, resolving_funcs, this_val);
if (JS_IsException(result_promise))
return result_promise;
promise_resolve = JS_GetProperty(ctx, this_val, JS_ATOM_resolve);
if (JS_IsException(promise_resolve) ||
check_function(ctx, promise_resolve))
goto fail_reject;
iter = JS_GetIterator(ctx, argv[0], FALSE);
if (JS_IsException(iter)) {
JSValue error;
fail_reject:
error = JS_GetException(ctx);
ret = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED, 1,
(JSValueConst *)&error);
JS_FreeValue(ctx, error);
if (JS_IsException(ret))
goto fail;
JS_FreeValue(ctx, ret);
} else {
next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
if (JS_IsException(next_method))
goto fail_reject;
for(;;) {
/* XXX: conformance: should close the iterator if error on 'done'
access, but not on 'value' access */
item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
if (JS_IsException(item))
goto fail_reject;
if (done)
break;
next_promise = JS_Call(ctx, promise_resolve,
this_val, 1, (JSValueConst *)&item);
JS_FreeValue(ctx, item);
if (JS_IsException(next_promise)) {
fail_reject1:
JS_IteratorClose(ctx, iter, TRUE);
goto fail_reject;
}
ret = JS_InvokeFree(ctx, next_promise, JS_ATOM_then, 2,
(JSValueConst *)resolving_funcs);
if (check_exception_free(ctx, ret))
goto fail_reject1;
}
}
done:
JS_FreeValue(ctx, promise_resolve);
JS_FreeValue(ctx, next_method);
JS_FreeValue(ctx, iter);
JS_FreeValue(ctx, resolving_funcs[0]);
JS_FreeValue(ctx, resolving_funcs[1]);
return result_promise;
fail:
//JS_FreeValue(ctx, next_method); // why not???
JS_FreeValue(ctx, result_promise);
result_promise = JS_EXCEPTION;
goto done;
}
int perform_promise_then(JSContext *ctx,
JSValueConst promise,
JSValueConst *resolve_reject,
JSValueConst *cap_resolving_funcs)
{
JSPromiseData *s = JS_GetOpaque(promise, JS_CLASS_PROMISE);
JSPromiseReactionData *rd_array[2], *rd;
int i, j;
rd_array[0] = NULL;
rd_array[1] = NULL;
for(i = 0; i < 2; i++) {
JSValueConst handler;
rd = js_mallocz(ctx, sizeof(*rd));
if (!rd) {
if (i == 1)
promise_reaction_data_free(ctx->rt, rd_array[0]);
return -1;
}
for(j = 0; j < 2; j++)
rd->resolving_funcs[j] = JS_DupValue(ctx, cap_resolving_funcs[j]);
handler = resolve_reject[i];
if (!JS_IsFunction(ctx, handler))
handler = JS_UNDEFINED;
rd->handler = JS_DupValue(ctx, handler);
rd_array[i] = rd;
}
if (s->promise_state == JS_PROMISE_PENDING) {
for(i = 0; i < 2; i++)
list_add_tail(&rd_array[i]->link, &s->promise_reactions[i]);
} else {
JSValueConst args[5];
if (s->promise_state == JS_PROMISE_REJECTED && !s->is_handled) {
JSRuntime *rt = ctx->rt;
if (rt->host_promise_rejection_tracker) {
rt->host_promise_rejection_tracker(ctx, promise, s->promise_result,
TRUE, rt->host_promise_rejection_tracker_opaque);
}
}
i = s->promise_state - JS_PROMISE_FULFILLED;
rd = rd_array[i];
args[0] = rd->resolving_funcs[0];
args[1] = rd->resolving_funcs[1];
args[2] = rd->handler;
args[3] = JS_NewBool(ctx, i);
args[4] = s->promise_result;
JS_EnqueueJob(ctx, promise_reaction_job, 5, args);
for(i = 0; i < 2; i++)
promise_reaction_data_free(ctx->rt, rd_array[i]);
}
s->is_handled = TRUE;
return 0;
}
static JSValue js_promise_then(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValue ctor, result_promise, resolving_funcs[2];
JSPromiseData *s;
int i, ret;
s = JS_GetOpaque2(ctx, this_val, JS_CLASS_PROMISE);
if (!s)
return JS_EXCEPTION;
ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED);
if (JS_IsException(ctor))
return ctor;
result_promise = js_new_promise_capability(ctx, resolving_funcs, ctor);
JS_FreeValue(ctx, ctor);
if (JS_IsException(result_promise))
return result_promise;
ret = perform_promise_then(ctx, this_val, argv,
(JSValueConst *)resolving_funcs);
for(i = 0; i < 2; i++)
JS_FreeValue(ctx, resolving_funcs[i]);
if (ret) {
JS_FreeValue(ctx, result_promise);
return JS_EXCEPTION;
}
return result_promise;
}
static JSValue js_promise_catch(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValueConst args[2];
args[0] = JS_UNDEFINED;
args[1] = argv[0];
return JS_Invoke(ctx, this_val, JS_ATOM_then, 2, args);
}
static JSValue js_promise_finally_value_thunk(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv,
int magic, JSValue *func_data)
{
return JS_DupValue(ctx, func_data[0]);
}
static JSValue js_promise_finally_thrower(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv,
int magic, JSValue *func_data)
{
return JS_Throw(ctx, JS_DupValue(ctx, func_data[0]));
}
static JSValue js_promise_then_finally_func(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv,
int magic, JSValue *func_data)
{
JSValueConst ctor = func_data[0];
JSValueConst onFinally = func_data[1];
JSValue res, promise, ret, then_func;
res = JS_Call(ctx, onFinally, JS_UNDEFINED, 0, NULL);
if (JS_IsException(res))
return res;
promise = js_promise_resolve(ctx, ctor, 1, (JSValueConst *)&res, 0);
JS_FreeValue(ctx, res);
if (JS_IsException(promise))
return promise;
if (magic == 0) {
then_func = JS_NewCFunctionData(ctx, js_promise_finally_value_thunk, 0,
0, 1, argv);
} else {
then_func = JS_NewCFunctionData(ctx, js_promise_finally_thrower, 0,
0, 1, argv);
}
if (JS_IsException(then_func)) {
JS_FreeValue(ctx, promise);
return then_func;
}
ret = JS_InvokeFree(ctx, promise, JS_ATOM_then, 1, (JSValueConst *)&then_func);
JS_FreeValue(ctx, then_func);
return ret;
}
static JSValue js_promise_finally(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
{
JSValueConst onFinally = argv[0];
JSValue ctor, ret;
JSValue then_funcs[2];
JSValueConst func_data[2];
int i;
ctor = JS_SpeciesConstructor(ctx, this_val, JS_UNDEFINED);
if (JS_IsException(ctor))
return ctor;
if (!JS_IsFunction(ctx, onFinally)) {
then_funcs[0] = JS_DupValue(ctx, onFinally);
then_funcs[1] = JS_DupValue(ctx, onFinally);
} else {
func_data[0] = ctor;
func_data[1] = onFinally;
for(i = 0; i < 2; i++) {
then_funcs[i] = JS_NewCFunctionData(ctx, js_promise_then_finally_func, 1, i, 2, func_data);
if (JS_IsException(then_funcs[i])) {
if (i == 1)
JS_FreeValue(ctx, then_funcs[0]);
JS_FreeValue(ctx, ctor);
return JS_EXCEPTION;
}
}
}
JS_FreeValue(ctx, ctor);
ret = JS_Invoke(ctx, this_val, JS_ATOM_then, 2, (JSValueConst *)then_funcs);
JS_FreeValue(ctx, then_funcs[0]);
JS_FreeValue(ctx, then_funcs[1]);
return ret;
}
static const JSCFunctionListEntry js_promise_funcs[] = {
JS_CFUNC_MAGIC_DEF("resolve", 1, js_promise_resolve, 0 ),
JS_CFUNC_MAGIC_DEF("reject", 1, js_promise_resolve, 1 ),
JS_CFUNC_MAGIC_DEF("all", 1, js_promise_all, PROMISE_MAGIC_all ),
JS_CFUNC_MAGIC_DEF("allSettled", 1, js_promise_all, PROMISE_MAGIC_allSettled ),
JS_CFUNC_MAGIC_DEF("any", 1, js_promise_all, PROMISE_MAGIC_any ),
JS_CFUNC_DEF("race", 1, js_promise_race ),
//JS_CFUNC_DEF("__newPromiseCapability", 1, js_promise___newPromiseCapability ),
JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL),
};
static const JSCFunctionListEntry js_promise_proto_funcs[] = {
JS_CFUNC_DEF("then", 2, js_promise_then ),
JS_CFUNC_DEF("catch", 1, js_promise_catch ),
JS_CFUNC_DEF("finally", 1, js_promise_finally ),
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Promise", JS_PROP_CONFIGURABLE ),
};
/* magic = GEN_MAGIC_x */
static JSValue js_async_generator_next(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv,
int magic)
{
JSAsyncGeneratorData *s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_GENERATOR);
JSValue promise, resolving_funcs[2];
JSAsyncGeneratorRequest *req;
promise = JS_NewPromiseCapability(ctx, resolving_funcs);
if (JS_IsException(promise))
return JS_EXCEPTION;
if (!s) {
JSValue err, res2;
JS_ThrowTypeError(ctx, "not an AsyncGenerator object");
err = JS_GetException(ctx);
res2 = JS_Call(ctx, resolving_funcs[1], JS_UNDEFINED,
1, (JSValueConst *)&err);
JS_FreeValue(ctx, err);
JS_FreeValue(ctx, res2);
JS_FreeValue(ctx, resolving_funcs[0]);
JS_FreeValue(ctx, resolving_funcs[1]);
return promise;
}
req = js_mallocz(ctx, sizeof(*req));
if (!req)
goto fail;
req->completion_type = magic;
req->result = JS_DupValue(ctx, argv[0]);
req->promise = JS_DupValue(ctx, promise);
req->resolving_funcs[0] = resolving_funcs[0];
req->resolving_funcs[1] = resolving_funcs[1];
list_add_tail(&req->link, &s->queue);
if (s->state != JS_ASYNC_GENERATOR_STATE_EXECUTING) {
js_async_generator_resume_next(ctx, s);
}
return promise;
fail:
JS_FreeValue(ctx, resolving_funcs[0]);
JS_FreeValue(ctx, resolving_funcs[1]);
JS_FreeValue(ctx, promise);
return JS_EXCEPTION;
}
static const JSCFunctionListEntry js_async_generator_proto_funcs[] = {
JS_CFUNC_MAGIC_DEF("next", 1, js_async_generator_next, GEN_MAGIC_NEXT ),
JS_CFUNC_MAGIC_DEF("return", 1, js_async_generator_next, GEN_MAGIC_RETURN ),
JS_CFUNC_MAGIC_DEF("throw", 1, js_async_generator_next, GEN_MAGIC_THROW ),
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncGenerator", JS_PROP_CONFIGURABLE ),
};
static void js_async_function_resolve_finalizer(JSRuntime *rt, JSValue val)
{
JSObject *p = JS_VALUE_GET_OBJ(val);
JSAsyncFunctionData *s = p->u.async_function_data;
if (s) {
js_async_function_free(rt, s);
}
}
static void js_async_function_resolve_mark(JSRuntime *rt, JSValueConst val,
JS_MarkFunc *mark_func)
{
JSObject *p = JS_VALUE_GET_OBJ(val);
JSAsyncFunctionData *s = p->u.async_function_data;
if (s) {
mark_func(rt, &s->header);
}
}
static void js_async_from_sync_iterator_finalizer(JSRuntime *rt, JSValue val)
{
JSAsyncFromSyncIteratorData *s =
JS_GetOpaque(val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR);
if (s) {
JS_FreeValueRT(rt, s->sync_iter);
JS_FreeValueRT(rt, s->next_method);
js_free_rt(rt, s);
}
}
static void js_async_from_sync_iterator_mark(JSRuntime *rt, JSValueConst val,
JS_MarkFunc *mark_func)
{
JSAsyncFromSyncIteratorData *s =
JS_GetOpaque(val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR);
if (s) {
JS_MarkValue(rt, s->sync_iter, mark_func);
JS_MarkValue(rt, s->next_method, mark_func);
}
}
static void js_async_generator_finalizer(JSRuntime *rt, JSValue obj)
{
JSAsyncGeneratorData *s = JS_GetOpaque(obj, JS_CLASS_ASYNC_GENERATOR);
if (s) {
js_async_generator_free(rt, s);
}
}
static void js_async_generator_mark(JSRuntime *rt, JSValueConst val,
JS_MarkFunc *mark_func)
{
JSAsyncGeneratorData *s = JS_GetOpaque(val, JS_CLASS_ASYNC_GENERATOR);
struct list_head *el;
JSAsyncGeneratorRequest *req;
if (s) {
list_for_each(el, &s->queue) {
req = list_entry(el, JSAsyncGeneratorRequest, link);
JS_MarkValue(rt, req->result, mark_func);
JS_MarkValue(rt, req->promise, mark_func);
JS_MarkValue(rt, req->resolving_funcs[0], mark_func);
JS_MarkValue(rt, req->resolving_funcs[1], mark_func);
}
if (s->state != JS_ASYNC_GENERATOR_STATE_COMPLETED &&
s->state != JS_ASYNC_GENERATOR_STATE_AWAITING_RETURN) {
async_func_mark(rt, &s->func_state, mark_func);
}
}
}
static JSClassShortDef const js_async_class_def[] = {
{ JS_ATOM_Promise, js_promise_finalizer, js_promise_mark }, /* JS_CLASS_PROMISE */
{ JS_ATOM_PromiseResolveFunction, js_promise_resolve_function_finalizer, js_promise_resolve_function_mark }, /* JS_CLASS_PROMISE_RESOLVE_FUNCTION */
{ JS_ATOM_PromiseRejectFunction, js_promise_resolve_function_finalizer, js_promise_resolve_function_mark }, /* JS_CLASS_PROMISE_REJECT_FUNCTION */
{ JS_ATOM_AsyncFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_ASYNC_FUNCTION */
{ JS_ATOM_AsyncFunctionResolve, js_async_function_resolve_finalizer, js_async_function_resolve_mark }, /* JS_CLASS_ASYNC_FUNCTION_RESOLVE */
{ JS_ATOM_AsyncFunctionReject, js_async_function_resolve_finalizer, js_async_function_resolve_mark }, /* JS_CLASS_ASYNC_FUNCTION_REJECT */
{ JS_ATOM_empty_string, js_async_from_sync_iterator_finalizer, js_async_from_sync_iterator_mark }, /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */
{ JS_ATOM_AsyncGeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_ASYNC_GENERATOR_FUNCTION */
{ JS_ATOM_AsyncGenerator, js_async_generator_finalizer, js_async_generator_mark }, /* JS_CLASS_ASYNC_GENERATOR */
};
static void js_async_function_terminate(JSRuntime *rt, JSAsyncFunctionData *s)
{
if (s->is_active) {
async_func_free(rt, &s->func_state);
s->is_active = FALSE;
}
}
void js_async_function_free0(JSRuntime *rt, JSAsyncFunctionData *s)
{
js_async_function_terminate(rt, s);
JS_FreeValueRT(rt, s->resolving_funcs[0]);
JS_FreeValueRT(rt, s->resolving_funcs[1]);
remove_gc_object(&s->header);
js_free_rt(rt, s);
}
static int js_async_function_resolve_create(JSContext *ctx,
JSAsyncFunctionData *s,
JSValue *resolving_funcs)
{
int i;
JSObject *p;
for(i = 0; i < 2; i++) {
resolving_funcs[i] =
JS_NewObjectProtoClass(ctx, ctx->function_proto,
JS_CLASS_ASYNC_FUNCTION_RESOLVE + i);
if (JS_IsException(resolving_funcs[i])) {
if (i == 1)
JS_FreeValue(ctx, resolving_funcs[0]);
return -1;
}
p = JS_VALUE_GET_OBJ(resolving_funcs[i]);
s->header.ref_count++;
p->u.async_function_data = s;
}
return 0;
}
static void js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
{
JSValue func_ret, ret2;
func_ret = async_func_resume(ctx, &s->func_state);
if (JS_IsException(func_ret)) {
JSValue error;
fail:
error = JS_GetException(ctx);
ret2 = JS_Call(ctx, s->resolving_funcs[1], JS_UNDEFINED,
1, (JSValueConst *)&error);
JS_FreeValue(ctx, error);
js_async_function_terminate(ctx->rt, s);
JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */
} else {
JSValue value;
value = s->func_state.frame.cur_sp[-1];
s->func_state.frame.cur_sp[-1] = JS_UNDEFINED;
if (JS_IsUndefined(func_ret)) {
/* function returned */
ret2 = JS_Call(ctx, s->resolving_funcs[0], JS_UNDEFINED,
1, (JSValueConst *)&value);
JS_FreeValue(ctx, ret2); /* XXX: what to do if exception ? */
JS_FreeValue(ctx, value);
js_async_function_terminate(ctx->rt, s);
} else {
JSValue promise, resolving_funcs[2], resolving_funcs1[2];
int i, res;
/* await */
JS_FreeValue(ctx, func_ret); /* not used */
promise = js_promise_resolve(ctx, ctx->promise_ctor,
1, (JSValueConst *)&value, 0);
JS_FreeValue(ctx, value);
if (JS_IsException(promise))
goto fail;
if (js_async_function_resolve_create(ctx, s, resolving_funcs)) {
JS_FreeValue(ctx, promise);
goto fail;
}
/* Note: no need to create 'thrownawayCapability' as in
the spec */
for(i = 0; i < 2; i++)
resolving_funcs1[i] = JS_UNDEFINED;
res = perform_promise_then(ctx, promise,
(JSValueConst *)resolving_funcs,
(JSValueConst *)resolving_funcs1);
JS_FreeValue(ctx, promise);
for(i = 0; i < 2; i++)
JS_FreeValue(ctx, resolving_funcs[i]);
if (res)
goto fail;
}
}
}
static JSValue js_async_function_resolve_call(JSContext *ctx,
JSValueConst func_obj,
JSValueConst this_obj,
int argc, JSValueConst *argv,
int flags)
{
JSObject *p = JS_VALUE_GET_OBJ(func_obj);
JSAsyncFunctionData *s = p->u.async_function_data;
BOOL is_reject = p->class_id - JS_CLASS_ASYNC_FUNCTION_RESOLVE;
JSValueConst arg;
if (argc > 0)
arg = argv[0];
else
arg = JS_UNDEFINED;
s->func_state.throw_flag = is_reject;
if (is_reject) {
JS_Throw(ctx, JS_DupValue(ctx, arg));
} else {
/* return value of await */
s->func_state.frame.cur_sp[-1] = JS_DupValue(ctx, arg);
}
js_async_function_resume(ctx, s);
return JS_UNDEFINED;
}
static JSValue js_async_function_call(JSContext *ctx, JSValueConst func_obj,
JSValueConst this_obj,
int argc, JSValueConst *argv, int flags)
{
JSValue promise;
JSAsyncFunctionData *s;
s = js_mallocz(ctx, sizeof(*s));
if (!s)
return JS_EXCEPTION;
s->header.ref_count = 1;
add_gc_object(ctx->rt, &s->header, JS_GC_OBJ_TYPE_ASYNC_FUNCTION);
s->is_active = FALSE;
s->resolving_funcs[0] = JS_UNDEFINED;
s->resolving_funcs[1] = JS_UNDEFINED;
promise = JS_NewPromiseCapability(ctx, s->resolving_funcs);
if (JS_IsException(promise))
goto fail;
if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
fail:
JS_FreeValue(ctx, promise);
js_async_function_free(ctx->rt, s);
return JS_EXCEPTION;
}
s->is_active = TRUE;
js_async_function_resume(ctx, s);
js_async_function_free(ctx->rt, s);
return promise;
}
static JSValue js_async_generator_function_call(JSContext *ctx, JSValueConst func_obj,
JSValueConst this_obj,
int argc, JSValueConst *argv,
int flags)
{
JSValue obj, func_ret;
JSAsyncGeneratorData *s;
s = js_mallocz(ctx, sizeof(*s));
if (!s)
return JS_EXCEPTION;
s->state = JS_ASYNC_GENERATOR_STATE_SUSPENDED_START;
init_list_head(&s->queue);
if (async_func_init(ctx, &s->func_state, func_obj, this_obj, argc, argv)) {
s->state = JS_ASYNC_GENERATOR_STATE_COMPLETED;
goto fail;
}
/* execute the function up to 'OP_initial_yield' (no yield nor
await are possible) */
func_ret = async_func_resume(ctx, &s->func_state);
if (JS_IsException(func_ret))
goto fail;
JS_FreeValue(ctx, func_ret);
obj = js_create_from_ctor(ctx, func_obj, JS_CLASS_ASYNC_GENERATOR);
if (JS_IsException(obj))
goto fail;
s->generator = JS_VALUE_GET_OBJ(obj);
JS_SetOpaque(obj, s);
return obj;
fail:
js_async_generator_free(ctx->rt, s);
return JS_EXCEPTION;
}
/* AsyncFunction */
static const JSCFunctionListEntry js_async_function_proto_funcs[] = {
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncFunction", JS_PROP_CONFIGURABLE ),
};
static const JSCFunctionListEntry js_async_iterator_proto_funcs[] = {
JS_CFUNC_DEF("[Symbol.asyncIterator]", 0, js_iterator_proto_iterator ),
};
static JSValue js_async_from_sync_iterator_unwrap(JSContext *ctx,
JSValueConst this_val,
int argc, JSValueConst *argv,
int magic, JSValue *func_data)
{
return js_create_iterator_result(ctx, JS_DupValue(ctx, argv[0]),
JS_ToBool(ctx, func_data[0]));
}
static JSValue js_async_from_sync_iterator_unwrap_func_create(JSContext *ctx,
BOOL done)
{
JSValueConst func_data[1];
func_data[0] = (JSValueConst)JS_NewBool(ctx, done);
return JS_NewCFunctionData(ctx, js_async_from_sync_iterator_unwrap,
1, 0, 1, func_data);
}
static JSValue js_async_from_sync_iterator_next(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv,
int magic)
{
JSValue promise, resolving_funcs[2], value, err, method;
JSAsyncFromSyncIteratorData *s;
int done;
int is_reject;
promise = JS_NewPromiseCapability(ctx, resolving_funcs);
if (JS_IsException(promise))
return JS_EXCEPTION;
s = JS_GetOpaque(this_val, JS_CLASS_ASYNC_FROM_SYNC_ITERATOR);
if (!s) {
JS_ThrowTypeError(ctx, "not an Async-from-Sync Iterator");
goto reject;
}
if (magic == GEN_MAGIC_NEXT) {
method = JS_DupValue(ctx, s->next_method);
} else {
method = JS_GetProperty(ctx, s->sync_iter,
magic == GEN_MAGIC_RETURN ? JS_ATOM_return :
JS_ATOM_throw);
if (JS_IsException(method))
goto reject;
if (JS_IsUndefined(method) || JS_IsNull(method)) {
if (magic == GEN_MAGIC_RETURN) {
err = js_create_iterator_result(ctx, JS_DupValue(ctx, argv[0]), TRUE);
is_reject = 0;
} else {
err = JS_DupValue(ctx, argv[0]);
is_reject = 1;
}
goto done_resolve;
}
}
value = JS_IteratorNext2(ctx, s->sync_iter, method,
argc >= 1 ? 1 : 0, argv, &done);
JS_FreeValue(ctx, method);
if (JS_IsException(value))
goto reject;
if (done == 2) {
JSValue obj = value;
value = JS_IteratorGetCompleteValue(ctx, obj, &done);
JS_FreeValue(ctx, obj);
if (JS_IsException(value))
goto reject;
}
if (JS_IsException(value)) {
JSValue res2;
reject:
err = JS_GetException(ctx);
is_reject = 1;
done_resolve:
res2 = JS_Call(ctx, resolving_funcs[is_reject], JS_UNDEFINED,
1, (JSValueConst *)&err);
JS_FreeValue(ctx, err);
JS_FreeValue(ctx, res2);
JS_FreeValue(ctx, resolving_funcs[0]);
JS_FreeValue(ctx, resolving_funcs[1]);
return promise;
}
{
JSValue value_wrapper_promise, resolve_reject[2];
int res;
value_wrapper_promise = js_promise_resolve(ctx, ctx->promise_ctor,
1, (JSValueConst *)&value, 0);
if (JS_IsException(value_wrapper_promise)) {
JS_FreeValue(ctx, value);
goto reject;
}
resolve_reject[0] =
js_async_from_sync_iterator_unwrap_func_create(ctx, done);
if (JS_IsException(resolve_reject[0])) {
JS_FreeValue(ctx, value_wrapper_promise);
goto fail;
}
JS_FreeValue(ctx, value);
resolve_reject[1] = JS_UNDEFINED;
res = perform_promise_then(ctx, value_wrapper_promise,
(JSValueConst *)resolve_reject,
(JSValueConst *)resolving_funcs);
JS_FreeValue(ctx, resolve_reject[0]);
JS_FreeValue(ctx, value_wrapper_promise);
JS_FreeValue(ctx, resolving_funcs[0]);
JS_FreeValue(ctx, resolving_funcs[1]);
if (res) {
JS_FreeValue(ctx, promise);
return JS_EXCEPTION;
}
}
return promise;
fail:
JS_FreeValue(ctx, value);
JS_FreeValue(ctx, resolving_funcs[0]);
JS_FreeValue(ctx, resolving_funcs[1]);
JS_FreeValue(ctx, promise);
return JS_EXCEPTION;
}
static const JSCFunctionListEntry js_async_from_sync_iterator_proto_funcs[] = {
JS_CFUNC_MAGIC_DEF("next", 1, js_async_from_sync_iterator_next, GEN_MAGIC_NEXT ),
JS_CFUNC_MAGIC_DEF("return", 1, js_async_from_sync_iterator_next, GEN_MAGIC_RETURN ),
JS_CFUNC_MAGIC_DEF("throw", 1, js_async_from_sync_iterator_next, GEN_MAGIC_THROW ),
};
static const JSCFunctionListEntry js_async_generator_function_proto_funcs[] = {
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "AsyncGeneratorFunction", JS_PROP_CONFIGURABLE ),
};
void JS_AddIntrinsicPromise(JSContext *ctx)
{
JSRuntime *rt = ctx->rt;
JSValue obj1;
if (!JS_IsRegisteredClass(rt, JS_CLASS_PROMISE)) {
init_class_range(rt, js_async_class_def, JS_CLASS_PROMISE,
countof(js_async_class_def));
rt->class_array[JS_CLASS_PROMISE_RESOLVE_FUNCTION].call = js_promise_resolve_function_call;
rt->class_array[JS_CLASS_PROMISE_REJECT_FUNCTION].call = js_promise_resolve_function_call;
rt->class_array[JS_CLASS_ASYNC_FUNCTION].call = js_async_function_call;
rt->class_array[JS_CLASS_ASYNC_FUNCTION_RESOLVE].call = js_async_function_resolve_call;
rt->class_array[JS_CLASS_ASYNC_FUNCTION_REJECT].call = js_async_function_resolve_call;
rt->class_array[JS_CLASS_ASYNC_GENERATOR_FUNCTION].call = js_async_generator_function_call;
}
/* Promise */
ctx->class_proto[JS_CLASS_PROMISE] = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_PROMISE],
js_promise_proto_funcs,
countof(js_promise_proto_funcs));
obj1 = JS_NewCFunction2(ctx, js_promise_constructor, "Promise", 1,
JS_CFUNC_constructor, 0);
ctx->promise_ctor = JS_DupValue(ctx, obj1);
JS_SetPropertyFunctionList(ctx, obj1,
js_promise_funcs,
countof(js_promise_funcs));
JS_NewGlobalCConstructor2(ctx, obj1, "Promise",
ctx->class_proto[JS_CLASS_PROMISE]);
/* AsyncFunction */
ctx->class_proto[JS_CLASS_ASYNC_FUNCTION] = JS_NewObjectProto(ctx, ctx->function_proto);
obj1 = JS_NewCFunction3(ctx, (JSCFunction *)js_function_constructor,
"AsyncFunction", 1,
JS_CFUNC_constructor_or_func_magic, JS_FUNC_ASYNC,
ctx->function_ctor);
JS_SetPropertyFunctionList(ctx,
ctx->class_proto[JS_CLASS_ASYNC_FUNCTION],
js_async_function_proto_funcs,
countof(js_async_function_proto_funcs));
JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_ASYNC_FUNCTION],
0, JS_PROP_CONFIGURABLE);
JS_FreeValue(ctx, obj1);
/* AsyncIteratorPrototype */
ctx->async_iterator_proto = JS_NewObject(ctx);
JS_SetPropertyFunctionList(ctx, ctx->async_iterator_proto,
js_async_iterator_proto_funcs,
countof(js_async_iterator_proto_funcs));
/* AsyncFromSyncIteratorPrototype */
ctx->class_proto[JS_CLASS_ASYNC_FROM_SYNC_ITERATOR] =
JS_NewObjectProto(ctx, ctx->async_iterator_proto);
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_ASYNC_FROM_SYNC_ITERATOR],
js_async_from_sync_iterator_proto_funcs,
countof(js_async_from_sync_iterator_proto_funcs));
/* AsyncGeneratorPrototype */
ctx->class_proto[JS_CLASS_ASYNC_GENERATOR] =
JS_NewObjectProto(ctx, ctx->async_iterator_proto);
JS_SetPropertyFunctionList(ctx,
ctx->class_proto[JS_CLASS_ASYNC_GENERATOR],
js_async_generator_proto_funcs,
countof(js_async_generator_proto_funcs));
/* AsyncGeneratorFunction */
ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION] =
JS_NewObjectProto(ctx, ctx->function_proto);
obj1 = JS_NewCFunction3(ctx, (JSCFunction *)js_function_constructor,
"AsyncGeneratorFunction", 1,
JS_CFUNC_constructor_or_func_magic,
JS_FUNC_ASYNC_GENERATOR,
ctx->function_ctor);
JS_SetPropertyFunctionList(ctx,
ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION],
js_async_generator_function_proto_funcs,
countof(js_async_generator_function_proto_funcs));
JS_SetConstructor2(ctx, ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION],
ctx->class_proto[JS_CLASS_ASYNC_GENERATOR],
JS_PROP_CONFIGURABLE, JS_PROP_CONFIGURABLE);
JS_SetConstructor2(ctx, obj1, ctx->class_proto[JS_CLASS_ASYNC_GENERATOR_FUNCTION],
0, JS_PROP_CONFIGURABLE);
JS_FreeValue(ctx, obj1);
}