Skip to content

Commit

Permalink
Add "unhandledrejection" global event when a Promise is not caught
Browse files Browse the repository at this point in the history
  • Loading branch information
TooTallNate committed Oct 21, 2023
1 parent 99cf6dd commit 4eff094
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/giant-planes-sip.md
@@ -0,0 +1,5 @@
---
'nxjs-runtime': patch
---

Add "unhandledrejection" global event when a Promise is not caught
3 changes: 3 additions & 0 deletions packages/runtime/src/$.ts
@@ -1,6 +1,9 @@
import type { MemoryDescriptor, Memory } from './wasm';

export interface Init {
onUnhandledRejection(
fn: (promise: Promise<unknown>, reason: any) => void
): void;
batteryInit(): void;
batteryInitClass(c: any): void;
batteryExit(): void;
Expand Down
19 changes: 18 additions & 1 deletion packages/runtime/src/index.ts
Expand Up @@ -10,7 +10,12 @@ import {
processTimers,
} from './timers';
import { console } from './console';
import { KeyboardEvent, TouchEvent, UIEvent } from './polyfills/event';
import {
KeyboardEvent,
TouchEvent,
UIEvent,
PromiseRejectionEvent,
} from './polyfills/event';

export type {
SwitchClass,
Expand Down Expand Up @@ -81,6 +86,7 @@ import * as WebAssembly from './wasm';
def('WebAssembly', WebAssembly);

import './source-map';
import { $ } from './$';

/**
* The `import.meta` meta-property exposes context-specific metadata to a JavaScript module.
Expand Down Expand Up @@ -125,6 +131,17 @@ def(
);
def('dispatchEvent', EventTarget.prototype.dispatchEvent.bind(globalThis));

$.onUnhandledRejection((p, r) => {
const ev = new PromiseRejectionEvent('unhandledrejection', {
promise: p,
reason: r,
});
dispatchEvent(ev);
if (!ev.defaultPrevented) {
console.error('Uncaught (in promise)', r);
}
});

Switch.addEventListener('frame', (event) => {
const {
keyboardInitialized,
Expand Down
19 changes: 19 additions & 0 deletions packages/runtime/src/polyfills/event.ts
Expand Up @@ -493,8 +493,27 @@ export class ErrorEvent extends Event implements globalThis.ErrorEvent {
}
}

export interface PromiseRejectionEventInit extends EventInit {
promise: Promise<any>;
reason?: any;
}

export class PromiseRejectionEvent
extends Event
implements globalThis.PromiseRejectionEvent
{
promise: Promise<any>;
reason: any;
constructor(type: string, options: PromiseRejectionEventInit) {
super(type, options);
this.promise = options.promise;
this.reason = options.reason;
}
}

def('Event', Event);
def('ErrorEvent', ErrorEvent);
def('PromiseRejectionEvent', PromiseRejectionEvent);
def('UIEvent', UIEvent);
def('KeyboardEvent', KeyboardEvent);
def('TouchEvent', TouchEvent);
35 changes: 35 additions & 0 deletions source/error.c
Expand Up @@ -4,13 +4,48 @@ void print_js_error(JSContext *ctx)
{
JSValue exception_val = JS_GetException(ctx);
const char *exception_str = JS_ToCString(ctx, exception_val);
fprintf(stderr, "%s\n", exception_str);
fflush(stderr);

printf("%s\n", exception_str);
JS_FreeCString(ctx, exception_str);

JSValue stack_val = JS_GetPropertyStr(ctx, exception_val, "stack");
const char *stack_str = JS_ToCString(ctx, stack_val);
fprintf(stderr, "%s\n", stack_str);
fflush(stderr);
printf("%s\n", stack_str);
JS_FreeCString(ctx, stack_str);

JS_FreeValue(ctx, exception_val);
}

static JSValue nx_set_unhandled_rejection_handler(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
nx_context_t *nx_ctx = JS_GetContextOpaque(ctx);
nx_ctx->unhandled_rejection_handler = JS_DupValue(ctx, argv[0]);
return JS_UNDEFINED;
}

void nx_promise_rejection_handler(JSContext *ctx, JSValueConst promise,
JSValueConst reason,
JS_BOOL is_handled, void *opaque)
{
nx_context_t *nx_ctx = JS_GetContextOpaque(ctx);
JSValueConst args[] = {promise, reason};
JSValue ret_val = JS_Call(ctx, nx_ctx->unhandled_rejection_handler, JS_NULL, 2, args);
if (JS_IsException(ret_val))
{
print_js_error(ctx);
}
JS_FreeValue(ctx, ret_val);
}

static const JSCFunctionListEntry function_list[] = {
JS_CFUNC_DEF("onUnhandledRejection", 1, nx_set_unhandled_rejection_handler),
};

void nx_init_error(JSContext *ctx, JSValueConst init_obj)
{
JS_SetPropertyFunctionList(ctx, init_obj, function_list, countof(function_list));
}
8 changes: 7 additions & 1 deletion source/error.h
@@ -1,4 +1,10 @@
#pragma once
#include <quickjs/quickjs.h>
#include "types.h"

void print_js_error(JSContext *ctx);

void nx_promise_rejection_handler(JSContext *ctx, JSValueConst promise,
JSValueConst reason,
JS_BOOL is_handled, void *opaque);

void nx_init_error(JSContext *ctx, JSValueConst init_obj);
6 changes: 5 additions & 1 deletion source/main.c
Expand Up @@ -460,14 +460,17 @@ int main(int argc, char *argv[])
nx_context_t *nx_ctx = malloc(sizeof(nx_context_t));
memset(nx_ctx, 0, sizeof(nx_context_t));
nx_ctx->thpool = thpool_init(4);
nx_ctx->unhandled_rejection_handler = JS_UNDEFINED;
pthread_mutex_init(&(nx_ctx->async_done_mutex), NULL);
JS_SetContextOpaque(ctx, nx_ctx);
JS_SetHostPromiseRejectionTracker(rt, nx_promise_rejection_handler, ctx);

/* The internal `$` object contains native functions that are wrapped in the JS runtime */
JSValue global_obj = JS_GetGlobalObject(ctx);
JSValue init_obj = JS_NewObject(ctx);
nx_init_wasm(ctx, init_obj);
nx_init_error(ctx, init_obj);
nx_init_battery(ctx, init_obj);
nx_init_wasm(ctx, init_obj);
JS_SetPropertyStr(ctx, global_obj, "$", init_obj);

// First try the `main.js` file on the RomFS
Expand Down Expand Up @@ -711,6 +714,7 @@ int main(int argc, char *argv[])
JS_FreeValue(ctx, native_obj);
JS_FreeValue(ctx, switch_obj);
JS_FreeValue(ctx, global_obj);
JS_FreeValue(ctx, nx_ctx->unhandled_rejection_handler);

JS_FreeContext(ctx);
JS_FreeRuntime(rt);
Expand Down
1 change: 1 addition & 0 deletions source/types.h
Expand Up @@ -58,6 +58,7 @@ typedef struct
FT_Library ft_library;
HidVibrationDeviceHandle vibration_device_handles[2];
IM3Environment wasm_env;
JSValue unhandled_rejection_handler;
} nx_context_t;

inline nx_context_t *nx_get_context(JSContext *ctx)
Expand Down

0 comments on commit 4eff094

Please sign in to comment.