javascript - 在 Web 浏览器的 JavaScript 中处理异步事件处理程序中的错误
问题描述
这只是处理异步事件处理程序中的错误的另一种无望尝试。
关于此示例的说明:此处的示例与直接在浏览器中运行的示例不同。如果直接在浏览器中运行,则任何错误事件侦听器都不起作用(“错误”、“未处理拒绝”)。
它在 Chrome(版本 80.0.3987.163(官方构建)(64 位))和 Firefox(75.0(64 位))中的 Windows 10 上看起来很相似。
我发现处理这个问题的唯一方法是永远不要打错字。但这对我也不起作用。
这应该如何工作?
window.addEventListener("error", evt => {
console.warn("error event handler", evt);
output("error handler: " + evt.message, "yellow");
});
window.addEventListener("unhandledrejection", evt => {
console.warn("rejection event handler", evt);
output("rejection handler: " + evt.message, "green");
});
function output(txt, color) {
const div = document.createElement("p");
div.textContent = txt;
if (color) div.style.backgroundColor = color;
document.body.appendChild(div);
}
const btn = document.createElement("button");
btn.innerHTML = "The button";
btn.addEventListener("click", async evt => {
evt.stopPropagation();
output("The button was clicked");
noFunction(); // FIXME:
})
document.body.appendChild(btn);
const btn2 = document.createElement("button");
btn2.innerHTML = "With try/catch";
btn2.addEventListener("click", async evt => {
evt.stopPropagation();
try {
output("Button 2 was clicked");
noFunction2(); // FIXME:
} catch (err) {
console.warn("catch", err)
throw Error(err);
}
})
document.body.appendChild(btn2);
new Promise(function(resolve, reject) {
setTimeout(function() {
return reject('oh noes');
}, 100);
});
justAnError();
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<script defer src="error-test.js"></script>
编辑 - 添加来自 Chrome 和 JS Bin 的输出(链接到 JS Bin 示例)
加载页面
铬/火狐:
错误处理程序:脚本错误。
JS斌:
错误处理程序:未捕获的 ReferenceError:未定义 justAnError
拒绝处理程序:未定义
单击左键
铬/火狐:
按钮被点击
JS斌:
按钮被点击
拒绝处理程序:未定义
解决方案
您可以为自己提供用于错误报告和包装事件处理程序的实用程序函数,如下所示:
function handleError(err) {
if (!(err instanceof Error)) {
err = Error(err);
}
output("error handler: " + err.message, "yellow");
}
function wrapHandler(fn) {
return function(evt) {
new Promise(resolve => {
resolve(fn(evt));
}).catch(e => {
handleError(e);
});
};
}
这支持async
和非async
事件处理程序。如果有一个同步错误调用fn
,它会被 Promise 构造函数捕获并变成对正在创建的 Promise 的拒绝。如果没有,则 promise 被解析为 的返回值fn
,这意味着如果fn
返回一个拒绝的 promise,则由创建的 promisenew Promise
被拒绝。所以无论哪种方式,错误都会进入错误处理程序。
我没有尝试区分错误和拒绝,因为它们本质上是相同的,但如果你愿意,你可以:
function handleError(err, isRejection) {
if (!(err instanceof Error)) {
err = Error(err);
}
output("error handler: " + err.message, isRejection ? "green" : "yellow");
}
function wrapHandler(fn) {
return function(evt) {
try {
const result = fn(event);
Promise.resolve(result).catch(e => handleError(e, true));
} catch (e) {
handleError(e, false);
}
};
}
无论哪种方式,您都需要设置全局处理程序来使用它并阻止默认值:
window.addEventListener("error", errorEvent => {
handleError(errorEvent.error, false); // Remove the `, false` if you're not trying to make a distinction
errorEvent.preventDefault();
});
window.addEventListener("unhandledrejection", errorEvent => {
handleError(errorEvent.reason, true); // Remove the `, true` if you're not trying to make a distinction
errorEvent.preventDefault();
});
您可以wrapHandler
在设置处理程序时直接使用:
btn.addEventListener("click", wrapHandler(async evt => {
evt.stopPropagation();
output("The button was clicked");
noFunction(); // FIXME:
}));
...或通过具有另一个实用功能:
function addListener(elm, eventName, fn) {
const handler = wrapHandler(fn);
return elm.addEventListener(eventName, handler);
return function() {
elm.removeEventListener(handler);
};
}
...然后:
const removeBtnClick = addListener(btn, "click", async evt => {
evt.stopPropagation();
output("The button was clicked");
noFunction(); // FIXME:
});
// ...if you want to remove it later...
removeBtnClick();
实时示例-由于您的原始版本区分了同步错误和拒绝,因此我在这里使用了该变体,但同样,它确实是没有区别的区别,我不会在我自己的代码中区分它们:
function handleError(err, isRejection) {
if (!(err instanceof Error)) {
err = Error(err);
}
output("error handler: " + err.message, isRejection ? "green" : "yellow");
}
window.addEventListener("error", errorEvent => {
handleError(errorEvent.error, false);
errorEvent.preventDefault();
});
window.addEventListener("unhandledrejection", errorEvent => {
handleError(errorEvent.reason, true);
errorEvent.preventDefault();
});
function wrapHandler(fn) {
return function(evt) {
try {
const result = fn(event);
Promise.resolve(result).catch(e => handleError(e, true));
} catch (e) {
handleError(e, false);
}
};
}
function addListener(elm, eventName, fn) {
const handler = wrapHandler(fn);
return elm.addEventListener(eventName, handler);
return function() {
elm.removeEventListener(handler);
};
}
function output(txt, color) {
const div = document.createElement("p");
div.textContent = txt;
if (color) div.style.backgroundColor = color;
document.body.appendChild(div);
}
const btn = document.createElement("button");
btn.innerHTML = "The button";
addListener(btn, "click", async evt => {
evt.stopPropagation();
output("The button was clicked");
noFunction(); // FIXME:
});
document.body.appendChild(btn);
const btn2 = document.createElement("button");
btn2.innerHTML = "With try/catch";
addListener(btn2, "click", async evt => {
evt.stopPropagation();
try {
output("Button 2 was clicked");
noFunction2(); // FIXME:
} catch (err) {
console.warn("catch", err)
throw Error(err);
}
});
document.body.appendChild(btn2);
new Promise(function(resolve, reject) {
setTimeout(function() {
return reject('oh noes');
}, 100);
});
justAnError();
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
推荐阅读
- reactjs - Heroku 应用程序在部署 React Hasura GraphQL 应用程序时显示空白页
- javascript - For with React 的结构和实现
- python - 如何使用 Django 做一些特定的过滤器?
- sql - 聚合函数和 SYS_CONNECT_BY_PATH?
- c++ - 有没有办法让用户在 switch 语句中输入一个单词而不是数字?
- c# - VS 2019 version 16.8.1 falsely reports that App derives from Application, when it does NOT
- javascript - Cannot Delete Fields with Period in Key Name
- android-studio - Flutter not installing properly for Android Studio
- typescript - 值可以是函数或空时的打字稿错误
- r - 在 Shiny 中按列名过滤雷达图