From cc08aab7d28f5b5573c80a3766714944bf666160 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Mon, 15 May 2023 22:22:39 -0700 Subject: [PATCH] Better error stack traces --- package.json | 1 + src/task.ts | 27 ++++++++++++++---------- src/transfer_handlers.ts | 44 ++++++++++++++++++++++++++++++++++++---- tsconfig.json | 2 +- 4 files changed, 58 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 24e9fa4..3187764 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "devDependencies": { "chai": "4.2.0", "conditional-type-checks": "1.0.5", + "error-stack-parser": "^2.1.4", "esbuild": "^0.17.15", "husky": "4.2.5", "karma": "^6.4.1", diff --git a/src/task.ts b/src/task.ts index df2a6df..f23a391 100644 --- a/src/task.ts +++ b/src/task.ts @@ -69,46 +69,51 @@ export class SynclinkTask { }); } - schedule_async(): this { + _schedule_async(): Promise { if (this.mode === "async") { // already scheduled - return this; + return this._promise; } if (this.mode === "sync") { throw new Error("Already synchronously scheduled"); } this.mode = "async"; - this.do_async().then( + const p = this.do_async().then( (value) => { // console.log("resolving", this.taskId, "value", value); this._resolved = true; this._result = value; this._resolve(value); + return value; }, (reason) => { this._exception = reason; this._reject(reason); + throw reason; }, ); + this._promise = p; + return p; + } + + schedule_async(): this { + this._schedule_async(); return this; } - async then( + then( onfulfilled: (value: T) => S, onrejected: (reason: any) => S, ): Promise { - this.schedule_async(); - return this._promise.then(onfulfilled, onrejected); + return this._schedule_async().then(onfulfilled, onrejected); } - catch(onrejected: (reason: any) => S): Promise { - this.schedule_async(); - return this._promise.catch(onrejected); + catch(onrejected: (reason: any) => S): Promise { + return this._schedule_async().catch(onrejected); } finally(onfinally: () => void): Promise { - this.schedule_async(); - return this._promise.finally(onfinally); + return this._schedule_async().finally(onfinally); } schedule_sync(): this { diff --git a/src/transfer_handlers.ts b/src/transfer_handlers.ts index daa462b..5e076e2 100644 --- a/src/transfer_handlers.ts +++ b/src/transfer_handlers.ts @@ -3,6 +3,7 @@ import { generateUUID } from "./request_response"; import { createProxy, expose, wrap } from "./async_task"; import { FakeMessageChannel } from "./fake_message_channel"; import { ProxyMarked, proxyMarker } from "./types"; +import * as ErrorStackParser from "error-stack-parser"; export const throwMarker = Symbol("Synclink.thrown"); @@ -103,6 +104,44 @@ type SerializedThrownValue = | { isError: true; value: Error } | { isError: false; value: unknown }; +const CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m; + +function renderChromeStack(s: ErrorStackParser.StackFrame): string { + const locationInfo = [s.fileName, s.lineNumber, s.columnNumber].join(":"); + if (s.functionName) { + return ` at ${s.functionName} (${locationInfo})`; + } else { + return ` at ${locationInfo}`; + } +} + +function renderFirefoxStack(s: ErrorStackParser.StackFrame): string { + const locationInfo = [s.fileName, s.lineNumber, s.columnNumber].join(":"); + return `${s.functionName || ""}@${locationInfo}`; +} + +function fixStackTrace(origError: Error): Error { + const e = new Error(origError.message); + e.name = origError.name; + const isChromeStack = origError.stack?.match(CHROME_IE_STACK_REGEXP); + const stackList = ErrorStackParser.parse(origError) + .concat(ErrorStackParser.parse(e)) + .filter( + (s) => + !s.fileName || + !/synclink\.m?js$/.test(s.fileName) || + s.functionName?.startsWith("SynclinkTask.") || + s.functionName?.startsWith("then"), + ) + .map(isChromeStack ? renderChromeStack : renderFirefoxStack); + if (isChromeStack) { + stackList.unshift(e.stack!.split("\n", 2)[0]); + } + const stack = stackList.join("\n"); + e.stack = stack; + return e; +} + /** * Internal transfer handler to handle thrown exceptions. */ @@ -130,10 +169,7 @@ export const throwTransferHandler: TransferHandler< }, deserialize(serialized) { if (serialized.isError) { - throw Object.assign( - new Error(serialized.value.message), - serialized.value, - ); + throw fixStackTrace(serialized.value); } throw serialized.value; }, diff --git a/tsconfig.json b/tsconfig.json index a06f6a4..78ef081 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { /* Basic Options */ - "target": "es2015" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */, + "target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */, "module": "esnext" /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, "lib": [ "esnext",