Skip to content

Commit

Permalink
Better error stack traces
Browse files Browse the repository at this point in the history
  • Loading branch information
hoodmane committed May 16, 2023
1 parent cd44c9b commit 833ebcd
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 11 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
18 changes: 12 additions & 6 deletions src/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,36 +69,42 @@ export class SynclinkTask<T> {
});
}

schedule_async(): this {
_schedule_async(): Promise<T> {
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;
},
);
return p;
}

schedule_async(): this {
this._schedule_async();
return this;
}

async then<S>(
then<S>(
onfulfilled: (value: T) => S,
onrejected: (reason: any) => S,
): Promise<S> {
this.schedule_async();
return this._promise.then(onfulfilled, onrejected);
return this._schedule_async().then(onfulfilled, onrejected);
}

catch<S>(onrejected: (reason: any) => S): Promise<S> {
Expand Down
47 changes: 43 additions & 4 deletions src/transfer_handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -103,6 +104,47 @@ 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) => {
const fileName = s.getFileName();
const funcName = s.getFunctionName();
return (
!fileName ||
!/synclink\.m?js$/.test(fileName) ||
funcName?.startsWith("SynclinkTask.") ||
funcName?.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.
*/
Expand Down Expand Up @@ -130,10 +172,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;
},
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down

0 comments on commit 833ebcd

Please sign in to comment.