首页 > 解决方案 > 在 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斌:

按钮被点击

拒绝处理程序:未定义

标签: javascriptgoogle-chromefirefoxerror-handlinges6-promise

解决方案


您可以为自己提供用于错误报告和包装事件处理程序的实用程序函数,如下所示:

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">


推荐阅读