在 Web 浏览器的 JavaScript 中处理异步事件处理程序中的错误
Handling errors in async event handlers in JavaScript in the web browser
这只是在异步事件处理程序中处理错误的又一次无望尝试。
关于这个例子的注释:这里的例子和直接在 运行浏览器。如果 运行 直接在浏览器中 none 错误事件侦听器正在工作(“error”、“unhandledrejection”)。
它在 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 的输出
(Link to JS Bin example)
正在加载页面
Chrome/Firefox:
error handler: Script error.
JS斌:
error handler: Uncaught ReferenceError: justAnError is not defined
rejection handler: undefined
单击左键
Chrome/Firefox:
The button was clicked
JS斌:
The button was clicked
rejection handler: undefined
您可以为自己提供用于错误报告和包装事件处理程序的实用函数,如下所示:
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 的拒绝。如果没有,承诺将解析为 fn
的 return 值,这意味着如果 fn
return 是拒绝的承诺,则由 [= 创建的承诺23=] 被拒绝。所以无论哪种方式,错误都会转到错误处理程序。
我没有尝试区分错误和拒绝,因为它们本质上是同一回事,但如果您愿意,您可以:
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">
这只是在异步事件处理程序中处理错误的又一次无望尝试。
关于这个例子的注释:这里的例子和直接在 运行浏览器。如果 运行 直接在浏览器中 none 错误事件侦听器正在工作(“error”、“unhandledrejection”)。
它在 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 的输出 (Link to JS Bin example)
正在加载页面
Chrome/Firefox:
error handler: Script error.
JS斌:
error handler: Uncaught ReferenceError: justAnError is not defined
rejection handler: undefined
单击左键
Chrome/Firefox:
The button was clicked
JS斌:
The button was clicked
rejection handler: undefined
您可以为自己提供用于错误报告和包装事件处理程序的实用函数,如下所示:
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 的拒绝。如果没有,承诺将解析为 fn
的 return 值,这意味着如果 fn
return 是拒绝的承诺,则由 [= 创建的承诺23=] 被拒绝。所以无论哪种方式,错误都会转到错误处理程序。
我没有尝试区分错误和拒绝,因为它们本质上是同一回事,但如果您愿意,您可以:
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">