Merge pull request #807 from nofficalfs/Dev-Fix-ErrorHandler

fix: error handle.
This commit is contained in:
Spmario233 2024-01-18 14:53:06 +08:00 committed by GitHub
commit bab68de606
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 111 additions and 12 deletions

View File

@ -1,36 +1,49 @@
/**
* 关于`Google Chrome`的异步错误处理
*
* `Chrome`所用的`v8`引擎为`Error`提供了特有的报错栈堆处理函数用于用户自定义报错栈堆的内容
* 由于`v8`提供的`Error.prepareStackTrace(error, structuredStackTrace)`接口存在一些限制导致不适合无名杀
*
* 我们用到了`Error.prepareStackTrace(error, structuredStackTrace)`这个函数这个函数的信息可参考[这里](https://v8.dev/docs/stack-trace-api#customizing-stack-traces)
* 故我们直接解析`Error`的栈堆信息来获取相关内容
*
* 该函数提供了结构化的栈堆信息很幸运的是这个结构化的栈堆能直接告诉我们报错的文件以及位置故我们使用该函数让异步报错能直接定位原始位置
* ~~`Chrome`所用的`v8`引擎为`Error`提供了特有的报错栈堆处理接口用于用户自定义报错栈堆的内容~~
*
* ~~我们用到了`Error.prepareStackTrace(error, structuredStackTrace)`这个接口这个接口的信息可参考[这里](https://v8.dev/docs/stack-trace-api#customizing-stack-traces)~~
*
* ~~该接口提供了结构化的栈堆信息很幸运的是这个结构化的栈堆能直接告诉我们报错的文件以及位置故我们使用该接口让异步报错能直接定位原始位置~~
*
* @implements {PromiseErrorHandler}
*/
export class ChromePromiseErrorHandler {
/**
* 用于临时记录报错信息的列表通过`Error.prepareStackTrace`更新该列表
* ~~用于临时记录报错信息的列表通过`Error.prepareStackTrace`更新该列表~~
*
* @type {[Error, NodeJS.CallSite[]][]}
* 现在用于存储报错过的错误信息
*
* @type {Error[]}
*/
#errorList;
/**
* @type {typeof Error.prepareStackTrace}
*
* @deprecated
*/
#originErrorPrepareStackTrace;
#_originErrorPrepareStackTrace;
/**
* 初始化`Error.prepareStackTrace`将该值赋值成我们需要的函数
* 判断是否是v8错误栈堆用到的正则
*/
#STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;
/**
* ~~初始化`Error.prepareStackTrace`将该值赋值成我们需要的函数~~
*
* 未防止本来Error.prepareStackTrace便存在赋值的行为我们将原始值存储并在需要的函数中调用
* ~~未防止本来Error.prepareStackTrace便存在赋值的行为我们将原始值存储并在需要的函数中调用~~
*
* > 这或许就是本体扩展化的第一步小声
* 初始化存储报错信息的列表
*/
onLoad() {
/*
this.#errorList = [];
this.#originErrorPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = (error, stackTraces) => {
@ -42,13 +55,19 @@ export class ChromePromiseErrorHandler {
this.#errorList.push([error, stackTraces]);
return result;
};
*/
this.#errorList = [];
}
/**
* 将原来可能的`Error.prepareStackTrace`赋值回去
* ~~将原来可能的`Error.prepareStackTrace`赋值回去~~
*
* @deprecated
*/
onUnload() {
/*
Error.prepareStackTrace = this.#originErrorPrepareStackTrace;
*/
}
/**
@ -60,6 +79,61 @@ export class ChromePromiseErrorHandler {
*/
onHandle(event) {
event.promise.catch((error) => {
// 如果`error`是个错误,则继续处理
if (error instanceof Error) {
// 如果已经处理过该错误,则不再处理
if (this.#errorList.includes(error)) return;
this.#errorList.push(error);
// 如果`error`拥有字符串形式的报错栈堆且报错栈堆确实符合v8的stack
if (typeof error.stack === 'string' && this.#STACK_REGEXP.test(error.stack)) {
// 获取符合栈堆信息的字符串,一般来说就是从第二行开始的所有行
// 为了处理eval的情况故必须获取完行数
let lines = error.stack.split('\n').filter((line) =>
this.#STACK_REGEXP.test(line));
// 提供类型信息防止vscode报错
/**
* @type {string | undefined}
*/
let fileName = void 0;
/**
* @type {string | undefined}
*/
let line = void 0;
/**
* @type {string | undefined}
*/
let column = void 0;
// 从第一条开始遍历一直遍历到不存在eval的位置
for (let currentLine = 0; currentLine < lines.length; ++currentLine) {
if (/\(eval /.test(lines[currentLine])) continue;
let formatedLine = lines[currentLine].replace(/^\s+/, '').replace(/\(eval code/g, '(').replace(/^.*?\s+/, '');
const location = formatedLine.match(/ (\(.+\)$)/);
if (location) formatedLine = formatedLine.replace(location[0], '');
const locationParts = extractLocation(location ? location[1] : formatedLine);
fileName = ['eval', '<anonymous>'].includes(locationParts[0]) ? void 0 : locationParts[0];
line = locationParts[1];
column = locationParts[2];
break;
}
// @ts-ignore
window.onerror(error.message, fileName, line, column, error);
}
// 反之我们只能不考虑报错文件信息直接调用onerror
else {
// @ts-ignore
window.onerror(error.message, void 0, void 0, void 0, error);
}
}
/*
console.error(error)
const result = this.#errorList.find(savedError => savedError[0] === error);
if (result) {
@ -72,17 +146,42 @@ export class ChromePromiseErrorHandler {
result[0]
);
}
*/
});
}
/**
* 正式报错时便不再需要报错信息了故直接清空列表释放内存
* ~~正式报错时便不再需要报错信息了故直接清空列表释放内存~~
*
* @deprecated
*/
onErrorPrepare() {
/*
this.#errorList.length = 0;
*/
}
}
/**
* 简易的解析报错栈堆位置信息的函数
*
* @param {string} urlLike
* @returns {string[]}
*/
export function extractLocation(urlLike) {
// 不存在地址信息的字符串
if (!/:/.test(urlLike)) {
return [urlLike];
}
// 捕获位置用到的正则
const regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/;
const parts = regExp.exec(urlLike.replace(/[()]/g, ''));
// @ts-ignore
return [parts[1], parts[2] || void 0, parts[3] || void 0];
}
/**
* @typedef {import('../interface/promise-error-handler').PromiseErrorHandler} PromiseErrorHandler
*/