Merge pull request #776 from nofficalfs/Dev-Enhancement-Noname

fix some question.
This commit is contained in:
Spmario233 2024-01-12 22:49:17 +08:00 committed by GitHub
commit 07c3dbd6d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 28912 additions and 28633 deletions

View File

@ -91,7 +91,8 @@ new Promise(resolve => {
script.async = true
script.onerror = (event) => {
console.error(event)
const message = `您使用的浏览器或无名杀客户端加载内容失败!\n报错内容: \n${event}\n若该BUG不为您个人原因造成的请及时反馈给无名杀开发组`;
const message = `您使用的浏览器或无名杀客户端加载内容失败!\n请检查游戏环境以及"(游戏根目录)/game/entry.js"文件的位置\n若该BUG不为您个人原因造成的请及时反馈给无名杀开发组`;
console.error(message);
alert(message);
exit()
}

View File

@ -799,10 +799,11 @@ export class Get extends Uninstantable {
*
* @template T
* @param {T} obj - 要复制的对象若不是对象则直接返回原值
* @param {boolean} [copyKeyDeep = false] - 是否深复制`Map``key`
* @param {WeakMap<object, unknown>} [map] - 拷贝用的临时存储用于处理循环引用请勿自行赋值
* @returns {T} - 深拷贝后的对象若传入值不是对象则为传入值
*/
static copy(obj, map = new WeakMap()) {
static copy(obj, copyKeyDeep = false, map = new WeakMap()) {
// 参考[这里](https://juejin.cn/post/7315612852890026021)实现深拷贝
// 不再判断是否能structuredClone是因为structuredClone会把Symbol给毙了
const getType = (obj) => Object.prototype.toString.call(obj);
@ -815,29 +816,36 @@ export class Get extends Uninstantable {
"[object Arguments]": true,
};
if (typeof obj !== "object" || obj === null)
if (typeof obj !== "object" || obj === null || !canTranverse[getType(obj)])
return obj;
// @ts-ignore
if (map.has(obj)) return map.get(obj);
const constructor = obj.constructor;
let target;
if (!canTranverse[getType(obj)]) {
target = obj;
return target;
}
// @ts-ignore
else target = constructor ? new constructor(target) : Object.create(null);
const target =
constructor
? (
// 这三类数据处理单独处理
// 实际上需要处理的只有Map和Set
// 除此之外的就只能祝愿有拷贝构造函数了
(Array.isArray(obj) || obj instanceof Map || obj instanceof Set)
// @ts-ignore
? new constructor()
// @ts-ignore
: new constructor(obj)
)
: Object.create(null);
map.set(obj, target);
if (obj instanceof Map) {
obj.forEach((value, key) => {
target.set(get.copy(key, map), get.copy(value, map));
target.set(copyKeyDeep ? get.copy(key, copyKeyDeep, map) : key, get.copy(value, copyKeyDeep, map));
});
} else if (obj instanceof Set) {
obj.forEach((value) => {
target.add(get.copy(value, map));
target.add(get.copy(value, copyKeyDeep, map));
});
}
@ -848,7 +856,7 @@ export class Get extends Uninstantable {
if (obj.hasOwnProperty(key)) {
const result = { enumerable, configurable };
if (descriptor.hasOwnProperty('value')) {
result.value = get.copy(descriptor.value, map);
result.value = get.copy(descriptor.value, copyKeyDeep, map);
result.writable = descriptor.writable;
} else {
const { get, set } = descriptor;
@ -862,7 +870,7 @@ export class Get extends Uninstantable {
const symbols = Object.getOwnPropertySymbols(obj);
symbols.forEach((symbol) => {
target[symbol] = get.copy(obj[symbol], map);
target[symbol] = get.copy(obj[symbol], copyKeyDeep, map);
});
return target;
@ -1645,12 +1653,17 @@ export class Get extends Uninstantable {
}
}
}
if (obj instanceof lib.element.Button || (obj instanceof HTMLDivElement && obj.classList.contains('button'))) return 'button';
if (obj instanceof lib.element.Button) return 'button';
if (obj instanceof lib.element.Card) return 'card';
if (obj instanceof lib.element.Player) return 'player';
if (obj instanceof lib.element.Dialog) return 'dialog';
if (obj instanceof lib.element.GameEvent ||
obj instanceof lib.element.GameEventPromise) return 'event';
if (typeof obj !== 'object' || obj === null) return;
if (lib.experimental.symbol.itemType in obj)
return obj[lib.experimental.symbol.itemType];
}
static equipNum(card) {
if (get.type(card) == 'equip') {

View File

@ -8,6 +8,7 @@ import { UI as ui } from '../ui/index.js';
import { userAgent } from '../util/index.js';
import * as config from '../util/config.js';
import { promiseErrorHandlerMap } from '../util/browser.js';
import { gnc } from '../gnc/index.js';
import { importCardPack, importCharacterPack, importExtension, importMode } from './import.js';
@ -33,7 +34,7 @@ export async function boot() {
// 设定游戏加载时间,超过时间未加载就提醒
const configLoadTime = localStorage.getItem(lib.configprefix + 'loadtime');
// 现在不暴露到全局变量里了直接传给onload
const resetGameTimeout = setTimeout(lib.init.reset, configLoadTime ? parseInt(configLoadTime) : 10000)
const resetGameTimeout = setTimeout(lib.init.reset, configLoadTime ? parseInt(configLoadTime) : 10000);
if (Reflect.has(window, 'cordovaLoadTimeout')) {
clearTimeout(Reflect.get(window, 'cordovaLoadTimeout'));
@ -57,6 +58,7 @@ export async function boot() {
_status.event = lib.element.GameEvent.initialGameEvent();
setWindowListener();
await setOnError();
// 无名杀更新日志
if (window.noname_update) {
@ -86,7 +88,7 @@ export async function boot() {
const waitDomLoad = new Promise((resolve) => {
if (document.readyState !== 'complete') {
window.onload = resolve;
} else resolve(void 0)
} else resolve(void 0);
}).then(onWindowReady.bind(window));
@ -141,7 +143,7 @@ export async function boot() {
// 读取模式
if (config2.mode) config.set('mode', config2.mode);
if (config.get('mode_config')[config.get('mode')] === undefined)
config.get('mode_config')[config.get('mode')] = {};
config.get('mode_config')[config.get('mode')] = {};
// 复制共有模式设置
for (const name in config.get('mode_config').global) {
@ -697,9 +699,9 @@ async function onWindowReady() {
document.addEventListener('deviceready', async () => {
const { cordovaReady } = await import('./cordova.js');
await cordovaReady();
resolve(void 0)
resolve(void 0);
});
})
});
}
/*
if (_status.packLoaded) {
@ -751,33 +753,123 @@ function setServerIndex() {
}
}
function setWindowListener() {
/**
* @type { [Error, NodeJS.CallSite[]][] }
*/
const errorList = [];
// 这他娘的能捕获浏览器控制台里的输入值,尽量别用
// 在火狐里无效
Error.prepareStackTrace = function (e, stackTraces) {
errorList.push([e, stackTraces]);
};
// 已经有用了
window.addEventListener("unhandledrejection", promiseRejectionEvent => {
promiseRejectionEvent.promise.catch(e => {
const result = errorList.find(v => v[0] === e);
if (result) {
// @ts-ignore
window.onerror(
result[0].message,
result[1][0].getScriptNameOrSourceURL() || void 0,
result[1][0].getLineNumber() || void 0,
result[1][0].getColumnNumber() || void 0,
result[0]
);
}
});
async function setOnError() {
const [core] = get.coreInfo();
const promiseErrorHandler = new ((core in promiseErrorHandlerMap) ? promiseErrorHandlerMap[core] : promiseErrorHandlerMap.other);
if (promiseErrorHandler.onLoad) await promiseErrorHandler.onLoad();
window.addEventListener("unhandledrejection", async (event) => {
if (promiseErrorHandler.onHandle) await promiseErrorHandler.onHandle(event);
});
window.onerror = function (msg, src, line, column, err) {
if (promiseErrorHandler.onErrorPrepare) promiseErrorHandler.onErrorPrepare();
const winPath = window.__dirname ? ('file:///' + (__dirname.replace(new RegExp('\\\\', 'g'), '/') + '/')) : '';
let str = `错误文件: ${typeof src == 'string' ? decodeURI(src).replace(lib.assetURL, '').replace(winPath, '') : '未知文件'}`;
str += `\n错误信息: ${msg}`;
const tip = lib.getErrorTip(msg);
if (tip) str += `\n错误提示: ${tip}`;
str += `\n行号: ${line}`;
str += `\n列号: ${column}`;
const version = Reflect.has(lib, 'version') ? Reflect.get(lib, 'version') : '';
const reg = /[^\d.]/;
const match = version.match(reg) != null;
str += '\n' + `${match ? '游戏' : '无名杀'}版本: ${version || '未知版本'}`;
if (match) str += '\n⚠您使用的游戏代码不是源于libccy/noname无名杀官方仓库请自行寻找您所使用的游戏版本开发者反馈';
if (_status && _status.event) {
let evt = _status.event;
str += `\nevent.name: ${evt.name}\nevent.step: ${evt.step}`;
// @ts-ignore
if (evt.parent) str += `\nevent.parent.name: ${evt.parent.name}\nevent.parent.step: ${evt.parent.step}`;
// @ts-ignore
if (evt.parent && evt.parent.parent) str += `\nevent.parent.parent.name: ${evt.parent.parent.name}\nevent.parent.parent.step: ${evt.parent.parent.step}`;
if (evt.player || evt.target || evt.source || evt.skill || evt.card) {
str += '\n-------------';
}
if (evt.player) {
if (lib.translate[evt.player.name]) str += `\nplayer: ${lib.translate[evt.player.name]}[${evt.player.name}]`;
else str += '\nplayer: ' + evt.player.name;
let distance = get.distance(_status.roundStart, evt.player, 'absolute');
if (distance != Infinity) {
str += `\n座位号: ${distance + 1}`;
}
}
if (evt.target) {
if (lib.translate[evt.target.name]) str += `\ntarget: ${lib.translate[evt.target.name]}[${evt.target.name}]`;
else str += '\ntarget: ' + evt.target.name;
}
if (evt.source) {
if (lib.translate[evt.source.name]) str += `\nsource: ${lib.translate[evt.source.name]}[${evt.source.name}]`;
else str += '\nsource: ' + evt.source.name;
}
if (evt.skill) {
if (lib.translate[evt.skill]) str += `\nskill: ${lib.translate[evt.skill]}[${evt.skill}]`;
else str += '\nskill: ' + evt.skill;
}
if (evt.card) {
if (lib.translate[evt.card.name]) str += `\ncard: ${lib.translate[evt.card.name]}[${evt.card.name}]`;
else str += '\ncard: ' + evt.card.name;
}
}
str += '\n-------------';
if (typeof line == 'number' && (typeof Reflect.get(game, 'readFile') == 'function' || location.origin != 'file://')) {
const createShowCode = function (lines) {
let showCode = '';
if (lines.length >= 10) {
if (line > 4) {
for (let i = line - 5; i < line + 6 && i < lines.length; i++) {
showCode += `${i + 1}| ${line == i + 1 ? '⚠️' : ''}${lines[i]}\n`;
}
} else {
for (let i = 0; i < line + 6 && i < lines.length; i++) {
showCode += `${i + 1}| ${line == i + 1 ? '⚠️' : ''}${lines[i]}\n`;
}
}
} else {
showCode = lines.map((_line, i) => `${i + 1}| ${line == i + 1 ? '⚠️' : ''}${_line}\n`).toString();
}
return showCode;
};
//协议名须和html一致(网页端防跨域)且文件是js
if (typeof src == 'string' && src.startsWith(location.protocol) && src.endsWith('.js')) {
//获取代码
const codes = lib.init.reqSync('local:' + decodeURI(src).replace(lib.assetURL, '').replace(winPath, ''));
if (codes) {
const lines = codes.split("\n");
str += '\n' + createShowCode(lines);
str += '\n-------------';
}
}
//解析parsex里的content fun内容(通常是技能content)
else if (err && err.stack && err.stack.split('\n')[1].trim().startsWith('at Object.eval [as content]')) {
const codes = _status.event.content;
if (typeof codes == 'function') {
const lines = codes.toString().split("\n");
str += '\n' + createShowCode(lines);
str += '\n-------------';
}
}
}
if (err && err.stack) str += '\n' + decodeURI(err.stack).replace(new RegExp(lib.assetURL, 'g'), '').replace(new RegExp(winPath, 'g'), '');
alert(str);
Reflect.set(window, 'ea', Array.from(arguments));
Reflect.set(window, 'em', msg);
Reflect.set(window, 'el', line);
Reflect.set(window, 'ec', column);
Reflect.set(window, 'eo', err);
game.print(str);
if (promiseErrorHandler.onErrorFinish) promiseErrorHandler.onErrorFinish();
// @ts-ignore
if (!lib.config.errstop) {
_status.withError = true;
game.loop();
}
};
}
function setWindowListener() {
window.onkeydown = function (e) {
if (!Reflect.has(ui, 'menuContainer') || !Reflect.get(ui, 'menuContainer').classList.contains('hidden')) {
if (e.keyCode == 116 || ((e.ctrlKey || e.metaKey) && e.keyCode == 82)) {
@ -875,107 +967,4 @@ function setWindowListener() {
// }
}
};
window.onerror = function (msg, src, line, column, err) {
errorList.length = 0;
const winPath = window.__dirname ? ('file:///' + (__dirname.replace(new RegExp('\\\\', 'g'), '/') + '/')) : '';
let str = `错误文件: ${typeof src == 'string' ? decodeURI(src).replace(lib.assetURL, '').replace(winPath, '') : '未知文件'}`;
str += `\n错误信息: ${msg}`;
const tip = lib.getErrorTip(msg);
if (tip) str += `\n错误提示: ${tip}`;
str += `\n行号: ${line}`;
str += `\n列号: ${column}`;
const version = Reflect.has(lib, 'version') ? Reflect.get(lib, 'version') : '';
const reg = /[^\d.]/;
const match = version.match(reg) != null;
str += '\n' + `${match ? '游戏' : '无名杀'}版本: ${version || '未知版本'}`;
if (match) str += '\n⚠您使用的游戏代码不是源于libccy/noname无名杀官方仓库请自行寻找您所使用的游戏版本开发者反馈';
if (_status && _status.event) {
let evt = _status.event;
str += `\nevent.name: ${evt.name}\nevent.step: ${evt.step}`;
// @ts-ignore
if (evt.parent) str += `\nevent.parent.name: ${evt.parent.name}\nevent.parent.step: ${evt.parent.step}`;
// @ts-ignore
if (evt.parent && evt.parent.parent) str += `\nevent.parent.parent.name: ${evt.parent.parent.name}\nevent.parent.parent.step: ${evt.parent.parent.step}`;
if (evt.player || evt.target || evt.source || evt.skill || evt.card) {
str += '\n-------------';
}
if (evt.player) {
if (lib.translate[evt.player.name]) str += `\nplayer: ${lib.translate[evt.player.name]}[${evt.player.name}]`;
else str += '\nplayer: ' + evt.player.name;
let distance = get.distance(_status.roundStart, evt.player, 'absolute');
if (distance != Infinity) {
str += `\n座位号: ${distance + 1}`;
}
}
if (evt.target) {
if (lib.translate[evt.target.name]) str += `\ntarget: ${lib.translate[evt.target.name]}[${evt.target.name}]`;
else str += '\ntarget: ' + evt.target.name;
}
if (evt.source) {
if (lib.translate[evt.source.name]) str += `\nsource: ${lib.translate[evt.source.name]}[${evt.source.name}]`;
else str += '\nsource: ' + evt.source.name;
}
if (evt.skill) {
if (lib.translate[evt.skill]) str += `\nskill: ${lib.translate[evt.skill]}[${evt.skill}]`;
else str += '\nskill: ' + evt.skill;
}
if (evt.card) {
if (lib.translate[evt.card.name]) str += `\ncard: ${lib.translate[evt.card.name]}[${evt.card.name}]`;
else str += '\ncard: ' + evt.card.name;
}
}
str += '\n-------------';
if (typeof line == 'number' && (typeof Reflect.get(game, 'readFile') == 'function' || location.origin != 'file://')) {
const createShowCode = function (lines) {
let showCode = '';
if (lines.length >= 10) {
if (line > 4) {
for (let i = line - 5; i < line + 6 && i < lines.length; i++) {
showCode += `${i + 1}| ${line == i + 1 ? '⚠️' : ''}${lines[i]}\n`;
}
} else {
for (let i = 0; i < line + 6 && i < lines.length; i++) {
showCode += `${i + 1}| ${line == i + 1 ? '⚠️' : ''}${lines[i]}\n`;
}
}
} else {
showCode = lines.map((_line, i) => `${i + 1}| ${line == i + 1 ? '⚠️' : ''}${_line}\n`).toString();
}
return showCode;
};
//协议名须和html一致(网页端防跨域)且文件是js
if (typeof src == 'string' && src.startsWith(location.protocol) && src.endsWith('.js')) {
//获取代码
const codes = lib.init.reqSync('local:' + decodeURI(src).replace(lib.assetURL, '').replace(winPath, ''));
if (codes) {
const lines = codes.split("\n");
str += '\n' + createShowCode(lines);
str += '\n-------------';
}
}
//解析parsex里的content fun内容(通常是技能content)
else if (err && err.stack && err.stack.split('\n')[1].trim().startsWith('at Object.eval [as content]')) {
const codes = _status.event.content;
if (typeof codes == 'function') {
const lines = codes.toString().split("\n");
str += '\n' + createShowCode(lines);
str += '\n-------------';
}
}
}
if (err && err.stack) str += '\n' + decodeURI(err.stack).replace(new RegExp(lib.assetURL, 'g'), '').replace(new RegExp(winPath, 'g'), '');
alert(str);
Reflect.set(window, 'ea', Array.from(arguments));
Reflect.set(window, 'em', msg);
Reflect.set(window, 'el', line);
Reflect.set(window, 'ec', column);
Reflect.set(window, 'eo', err);
game.print(str);
// @ts-ignore
if (!lib.config.errstop) {
_status.withError = true;
game.loop();
}
};
}

View File

@ -8,11 +8,11 @@ import { GNC as gnc } from '../../gnc/index.js';
// 未来再改
export const Content = {
emptyEvent: async (event) => {
emptyEvent: () => {
event.trigger(event.name);
},
//增加明置手牌
addShownCards: async (event, _trigger, player) => {
addShownCards: () => {
const hs = player.getCards('h'), showingCards = event._cards.filter(showingCard => hs.includes(showingCard)), shown = player.getShownCards();
event.gaintag.forEach(tag => player.addGaintag(showingCards, tag));
if (!(event.cards = showingCards.filter(showingCard => !shown.includes(showingCard))).length) return;
@ -6665,8 +6665,7 @@ export const Content = {
"step 5";
ui.clear();
},
draw: async (event, _trigger, player) => {
let { num } = event;
draw: function() {
// if(lib.config.background_audio){
// game.playAudio('effect','draw');
// }
@ -6724,7 +6723,6 @@ export const Content = {
}
if (event.gaintag) next.gaintag.addArray(event.gaintag);
event.result = cards;
await next;
},
discard: function () {
"step 0";

View File

@ -1,5 +1,8 @@
import { Uninstantable } from "../../util/index.js";
export class Experimental extends Uninstantable {
import { ExperimentalSymbol } from "./symbol.js";
export class Experimental extends Uninstantable {
static symbol = ExperimentalSymbol
static symbols = ExperimentalSymbol
}

View File

@ -0,0 +1,5 @@
import { Uninstantable } from "../../util/index.js";
export class ExperimentalSymbol extends Uninstantable {
static itemType = Symbol('noname.experimental.itemType')
}

File diff suppressed because it is too large Load Diff

19
noname/util/browser.js Normal file
View File

@ -0,0 +1,19 @@
import { PromiseErrorHandler } from './struct/index.js';
/**
* 从浏览器名到不同浏览器下异步处理方式的映射
*
* `key`的值同`get.coreInfo`函数返回值的第一个元素
*
* @type {Record<"firefox" | "chrome" | "safari" | "other", new () => PromiseErrorHandler>}
*/
export const promiseErrorHandlerMap = {
'chrome': PromiseErrorHandler.ChromePromiseErrorHandler,
'firefox': PromiseErrorHandler.FirefoxPromiseErrorHandler,
'safari': PromiseErrorHandler.UnknownPromiseErrorHandler,
'other': PromiseErrorHandler.UnknownPromiseErrorHandler
};
/**
* @typedef {import('./struct/interface/promise-error-handler.js').PromiseErrorHandler} PromiseErrorHandler
*/

View File

@ -1,18 +1,24 @@
/**
*
* 一个非常普通的
*/
export class Mutex {
/**
* 锁目前的状态只有unlockedlocked两种情况
*
* @type {'locked' | 'unlocked'}
*/
#status;
/**
* 上锁后用于等待的锁Promise
*
* @type {null | Promise<void>}
*/
#promise;
/**
* 上锁后用于触发锁Promise的resolve函数
*
* @type {null | function(): void}
*/
#resolve;
@ -23,6 +29,11 @@ export class Mutex {
this.#resolve = null;
}
/**
* 上锁
*
* 请时刻记住使用`await Mutex#lock()`来使锁正常工作
*/
async lock() {
switch (this.#status) {
case 'locked':
@ -36,6 +47,11 @@ export class Mutex {
}
}
/**
* 解锁
*
* 请不要在未上锁的情况下解锁
*/
unlock() {
if (this.#status === 'unlocked') throw new Error('This Mutex is not locked.');
@ -44,6 +60,7 @@ export class Mutex {
}
/**
* 启用锁的try-finally封装用于在函数执行完后自动解放锁的控制权就算发生错误
*
* @param {function(): void | Promise<void>} content
*/

View File

@ -0,0 +1,5 @@
/**
* 无名杀内部所需要的数据结构
*/
export * as PromiseErrorHandler from './promise-error-handler/index.js';

View File

@ -0,0 +1,5 @@
/**
*
*/
export { PromiseErrorHandler } from './promise-error-handler'

View File

@ -0,0 +1,53 @@
/**
*
*/
export interface PromiseErrorHandler {
/**
*
*
*
*
*
*/
onLoad?(): void | Promise<void>
/**
*
*
*
*
* 使
*
*
*/
onUnload?(): void | Promise<void>
/**
* `unhandledrejection`
*
*
*
*
*
* @param event -
*/
onHandle?(event: PromiseRejectionEvent): void | Promise<void>
/**
* `window.onerror`
*
* `window.onerror`
*
*
*/
onErrorPrepare?(): void
/**
* `window.onerror`****
*
* `window.onerror`
*
*
*/
onErrorFinish?(): void
}

View File

@ -0,0 +1,87 @@
/**
* 关于`Google Chrome`的异步错误处理
*
* `Chrome`所用的`v8`引擎为`Error`提供了特有的报错栈堆处理函数用于用户自定义报错栈堆的内容
*
* 我们用到了`Error.prepareStackTrace(error, structuredStackTrace)`这个函数这个函数的信息可参考[这里](https://v8.dev/docs/stack-trace-api#customizing-stack-traces)
*
* 该函数提供了结构化的栈堆信息很幸运的是这个结构化的栈堆能直接告诉我们报错的文件以及位置故我们使用该函数让异步报错能直接定位原始位置
*
* @implements {PromiseErrorHandler}
*/
export class ChromePromiseErrorHandler {
/**
* 用于临时记录报错信息的列表通过`Error.prepareStackTrace`更新该列表
*
* @type {[Error, NodeJS.CallSite[]][]}
*/
#errorList;
/**
* @type {typeof Error.prepareStackTrace}
*/
#originErrorPrepareStackTrace;
/**
* 初始化`Error.prepareStackTrace`将该值赋值成我们需要的函数
*
* 未防止本来Error.prepareStackTrace便存在赋值的行为我们将原始值存储并在需要的函数中调用
*
* > 这或许就是本体扩展化的第一步小声
*/
onLoad() {
this.#errorList = [];
this.#originErrorPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = (error, stackTraces) => {
// 其实这步或许不需要
// 但真赋值了Error.prepareStackTrace的话保不齐会出现需要返回值的情况
const result = this.#originErrorPrepareStackTrace
? this.#originErrorPrepareStackTrace(error, stackTraces)
: void 0;
this.#errorList.push([error, stackTraces]);
return result;
};
}
/**
* 将原来可能的`Error.prepareStackTrace`赋值回去
*/
onUnload() {
Error.prepareStackTrace = this.#originErrorPrepareStackTrace;
}
/**
* 在获取报错的时候我们通过发生报错的`Promise`来进行捕获错误的操作
*
* 如果捕获出来的错误存放我们存报错栈堆的列表中则证明该错误能获取到栈堆由此来获取报错的地址和行列号
*
* @param {PromiseRejectionEvent} event
*/
onHandle(event) {
event.promise.catch((error) => {
const result = this.#errorList.find(savedError => savedError[0] === error);
if (result) {
// @ts-ignore
window.onerror(
result[0].message,
result[1][0].getScriptNameOrSourceURL(),
result[1][0].getLineNumber() || void 0,
result[1][0].getColumnNumber() || void 0,
result[0]
);
}
});
}
/**
* 正式报错时便不再需要报错信息了故直接清空列表释放内存
*/
onErrorPrepare() {
this.#errorList.length = 0;
}
}
/**
* @typedef {import('../interface/promise-error-handler').PromiseErrorHandler} PromiseErrorHandler
*/

View File

@ -0,0 +1,40 @@
/**
* 关于`Mozilla Firefox`的异步错误处理
*
* 很幸运Mozilla直接为`Firefox`的报错提供了地址和行列号故我们能直接获取到要获取的信息不用像`v8`那样通过栈堆获取
*
* 虽然但是我们还是需要判断一下捕获的报错是否是错误
*
* @implements {PromiseErrorHandler}
*/
export class FirefoxPromiseErrorHandler {
/**
* 在获取报错的时候我们通过发生报错的`Promise`来进行捕获错误的操作
*
* 如果捕获到的错误是`Error`则能直接通过`Firefox`的特性来获取地址和行列号
*
* @param {PromiseRejectionEvent} event
*/
onHandle(event) {
event.promise.catch((error) => {
if (typeof error === 'object' && error instanceof Error) {
// Firefox在大环境下默认情况必须要那么多ts-ignore
// @ts-ignore
window.onerror(
error.message,
// @ts-ignore
error.fileName,
// @ts-ignore
error.lineNumber,
// @ts-ignore
error.columnNumber,
error
);
}
});
}
}
/**
* @typedef {import('../interface/promise-error-handler').PromiseErrorHandler} PromiseErrorHandler
*/

View File

@ -0,0 +1,13 @@
/**
* 关于不同浏览器下对异步错误的处理方式
*
* 目前已实现的浏览器如下
*
* - `Google Chrome`包括`electron``cordova`以及`crosswalk`
* - `Mozilla Firefox`
*
*/
export { ChromePromiseErrorHandler } from './chrome.js';
export { FirefoxPromiseErrorHandler } from './firefox.js';
export { UnknownPromiseErrorHandler } from './unknown.js';

View File

@ -0,0 +1,31 @@
/**
* 关于除已实现浏览器外其余浏览器的异步错误处理
*
* 很遗憾对于这类浏览器因为标准未涉及报错栈堆或地址及行列号故我们只能直接暴力的throw出我们捕获道德错误
*
* 尽管我们还是会为了这类浏览器判断是不是捕获到了一个`Error`
*
* 总之虽然这里跟Safari无关但我们还是为新时代IE默哀一秒
*
* @implements {PromiseErrorHandler}
*/
export class UnknownPromiseErrorHandler {
/**
* 在获取报错的时候我们通过发生报错的`Promise`来进行捕获错误的操作
*
* 如果捕获到的错误是`Error`...我们只能暴力的将`Error`再次`throw`出去
*
* @param {PromiseRejectionEvent} event
*/
onHandle(event) {
event.promise.catch((error) => {
if (typeof error === 'object' && error instanceof Error) {
throw error;
}
});
}
}
/**
* @typedef {import('../interface/promise-error-handler').PromiseErrorHandler} PromiseErrorHandler
*/