/* * 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); }