Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 45 additions & 2 deletions api-test.c
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,13 @@ static void promise_hook_cb(JSContext *ctx, JSPromiseHookType type,
type == JS_PROMISE_HOOK_RESOLVE);
promise_hook_state.hook_type_call_count[type]++;
assert(opaque == (void *)&promise_hook_state);
if (type == JS_PROMISE_HOOK_BEFORE || type == JS_PROMISE_HOOK_AFTER) {
JSValue global_object = JS_GetGlobalObject(ctx);
JS_SetPropertyStr(ctx, global_object,
type == JS_PROMISE_HOOK_BEFORE ? "before_promise" : "after_promise",
JS_DupValue(ctx, promise));
JS_FreeValue(ctx, global_object);
}
if (!JS_IsUndefined(parent_promise)) {
JSValue global_object = JS_GetGlobalObject(ctx);
JS_SetPropertyStr(ctx, global_object, "actual",
Expand Down Expand Up @@ -858,8 +865,8 @@ static void promise_hook(void)
assert(1 == JS_ExecutePendingJob(rt, &unused));
assert(!JS_HasException(ctx));
assert(4 == cc[JS_PROMISE_HOOK_INIT]);
assert(0 == cc[JS_PROMISE_HOOK_BEFORE]);
assert(0 == cc[JS_PROMISE_HOOK_AFTER]);
assert(1 == cc[JS_PROMISE_HOOK_BEFORE]);
assert(1 == cc[JS_PROMISE_HOOK_AFTER]);
assert(4 == cc[JS_PROMISE_HOOK_RESOLVE]);
assert(!JS_IsJobPending(rt));
v = JS_GetPropertyStr(ctx, global_object, "count");
Expand All @@ -876,6 +883,42 @@ static void promise_hook(void)
JS_FreeValue(ctx, expected);
}
memset(&promise_hook_state, 0, sizeof(promise_hook_state));
{
// await continuation has its own promise, parented by the awaited promise
static const char code[] =
"globalThis.actual = undefined;"
"globalThis.before_promise = undefined;"
"globalThis.after_promise = undefined;"
"globalThis.awaited = Promise.resolve();"
"(async () => { await awaited; })()";
JSValue ret = eval(ctx, code);
assert(!JS_IsException(ret));
assert(JS_IsPromise(ret));
assert(JS_IsJobPending(rt));
while (JS_IsJobPending(rt))
assert(1 == JS_ExecutePendingJob(rt, &unused));
assert(1 == cc[JS_PROMISE_HOOK_BEFORE]);
assert(1 == cc[JS_PROMISE_HOOK_AFTER]);
JSValue parent = JS_GetPropertyStr(ctx, global_object, "actual");
JSValue awaited = JS_GetPropertyStr(ctx, global_object, "awaited");
JSValue before_promise = JS_GetPropertyStr(ctx, global_object, "before_promise");
JSValue after_promise = JS_GetPropertyStr(ctx, global_object, "after_promise");
assert(!JS_IsException(parent));
assert(!JS_IsException(awaited));
assert(!JS_IsException(before_promise));
assert(!JS_IsException(after_promise));
assert(JS_IsSameValue(ctx, parent, awaited));
assert(JS_IsPromise(before_promise));
assert(JS_IsSameValue(ctx, before_promise, after_promise));
assert(!JS_IsSameValue(ctx, before_promise, awaited));
assert(!JS_IsSameValue(ctx, before_promise, ret));
JS_FreeValue(ctx, parent);
JS_FreeValue(ctx, awaited);
JS_FreeValue(ctx, before_promise);
JS_FreeValue(ctx, after_promise);
JS_FreeValue(ctx, ret);
}
memset(&promise_hook_state, 0, sizeof(promise_hook_state));
{
// module with thenable; fires before and after hooks
static const char code[] =
Expand Down
36 changes: 32 additions & 4 deletions quickjs.c
Original file line number Diff line number Diff line change
Expand Up @@ -21538,7 +21538,9 @@ static bool js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
goto resolved;
} else {
JSValue promise, resolving_funcs[2], resolving_funcs1[2];
int i, res;
JSValue thrownaway_promise = JS_UNDEFINED;
JSRuntime *rt = ctx->rt;
int i, res = -1;

/* await */
JS_FreeValue(ctx, func_ret); /* not used */
Expand All @@ -21552,16 +21554,27 @@ static bool js_async_function_resume(JSContext *ctx, JSAsyncFunctionData *s)
goto fail;
}

/* Note: no need to create 'thrownawayCapability' as in
the spec */
for(i = 0; i < 2; i++)
resolving_funcs1[i] = JS_UNDEFINED;
if (rt->promise_hook) {
JSValueLink link = {rt->parent_promise, promise};
rt->parent_promise = &link;
thrownaway_promise = JS_NewPromiseCapability(ctx,
resolving_funcs1);
rt->parent_promise = link.next;
if (JS_IsException(thrownaway_promise))
goto cleanup;
}
res = perform_promise_then(ctx, promise,
vc(resolving_funcs),
vc(resolving_funcs1));
cleanup:
JS_FreeValue(ctx, promise);
for(i = 0; i < 2; i++)
for(i = 0; i < 2; i++) {
JS_FreeValue(ctx, resolving_funcs[i]);
JS_FreeValue(ctx, resolving_funcs1[i]);
}
JS_FreeValue(ctx, thrownaway_promise);
if (res)
goto fail;
}
Expand Down Expand Up @@ -55613,8 +55626,11 @@ static JSValue promise_reaction_job(JSContext *ctx, int argc,
JSValueConst *argv)
{
JSValueConst handler, func;
JSValueConst promise = JS_UNDEFINED;
JSValue res, res2;
JSValueConst arg;
JSPromiseFunctionData *s;
JSRuntime *rt = ctx->rt;
bool is_reject;

assert(argc == 5);
Expand All @@ -55624,6 +55640,14 @@ static JSValue promise_reaction_job(JSContext *ctx, int argc,

promise_trace(ctx, "promise_reaction_job: is_reject=%d\n", is_reject);

s = JS_GetOpaque(argv[0], JS_CLASS_PROMISE_RESOLVE_FUNCTION);
if (s)
promise = s->promise;

if (rt->promise_hook) {
rt->promise_hook(ctx, JS_PROMISE_HOOK_BEFORE, promise, JS_UNDEFINED,
rt->promise_hook_opaque);
}
if (JS_IsUndefined(handler)) {
if (is_reject) {
res = JS_Throw(ctx, js_dup(arg));
Expand All @@ -55633,6 +55657,10 @@ static JSValue promise_reaction_job(JSContext *ctx, int argc,
} else {
res = JS_Call(ctx, handler, JS_UNDEFINED, 1, &arg);
}
if (rt->promise_hook) {
rt->promise_hook(ctx, JS_PROMISE_HOOK_AFTER, promise, JS_UNDEFINED,
rt->promise_hook_opaque);
}
is_reject = JS_IsException(res);
if (is_reject) {
if (unlikely(JS_IsUncatchableError(ctx->rt->current_exception)))
Expand Down
7 changes: 3 additions & 4 deletions quickjs.h
Original file line number Diff line number Diff line change
Expand Up @@ -1165,15 +1165,14 @@ JS_EXTERN JSValue JS_NewPrivateSymbol(JSContext *ctx, const char *description);

typedef enum JSPromiseHookType {
JS_PROMISE_HOOK_INIT, // emitted when a new promise is created
JS_PROMISE_HOOK_BEFORE, // runs right before promise.then is invoked
JS_PROMISE_HOOK_AFTER, // runs right after promise.then is invoked
JS_PROMISE_HOOK_BEFORE, // runs right before a promise continuation executes
JS_PROMISE_HOOK_AFTER, // runs right after a promise continuation executes
JS_PROMISE_HOOK_RESOLVE, // not emitted for rejected promises
} JSPromiseHookType;

// parent_promise is only passed in when type == JS_PROMISE_HOOK_INIT and
// is then either a promise object or JS_UNDEFINED if the new promise does
// not have a parent promise; only promises created with promise.then have
// a parent promise
// not have a parent promise; continuation promises have a parent promise
typedef void JSPromiseHook(JSContext *ctx, JSPromiseHookType type,
JSValueConst promise, JSValueConst parent_promise,
void *opaque);
Expand Down
Loading