Обработка ошибок в asyn c обработчиках событий в JavaScript в веб-браузере - PullRequest
0 голосов
/ 07 апреля 2020

Это просто еще одна безнадежная попытка обработать ошибки в обработчиках событий asyn c.

Примечание об этом примере: Пример здесь работает иначе, чем если бы он запускался непосредственно в браузере. При запуске непосредственно в браузере не работает ни один из прослушивателей событий на наличие ошибок («error», «unhandledrejection»).

Похоже на Windows 10 в Chrome (Версия 80.0.3987.163 (Official). Build) (64-разрядная версия)) и Firefox (75.0 (64-разрядная версия)).

Единственный способ справиться с этим - не допускать опечаток. Но это не работает и для меня.

Как это должно работать?

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>

EDIT - добавление вывода из Chrome и JS Bin ( Ссылка на JS Пример Bin )

Загрузка страницы

Chrome / Firefox:

Обработчик ошибок: Ошибка скрипта.

JS Корзина:

Обработчик ошибок: Uncaught ReferenceError: justAnError не определен

Обработчик отклонения: undefined

Нажатие левой кнопки

Chrome / Firefox:

Нажата кнопка

JS Корзина:

нажата кнопка

обработчик отклонения: не определено

1 Ответ

2 голосов
/ 13 апреля 2020

Вы можете предоставить себе служебные функции для отчетов об ошибках и обработчиков событий, например:

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, она перехватывается конструктором обещания и превращается в отклонение создаваемого обещания. Если нет, обещание преобразуется в возвращаемое значение fn, что означает, что если fn возвращает обещание, которое отклоняет, обещание, созданное new Promise, отклоняется. Так или иначе, ошибки go в обработчике ошибок.

Я не пытался различить guish между ошибками и отклонениями, поскольку они в основном одно и то же, но вы могли бы, если бы Вы хотите:

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);
    };
}

... then:

const removeBtnClick = addListener(btn, "click", async evt => {
    evt.stopPropagation();
    output("The button was clicked");
    noFunction(); // FIXME: 
});
// ...if you want to remove it later...
removeBtnClick();

Live Example - поскольку ваш оригинал различал синхронные ошибки и отклонения, я использовал этот вариант здесь, но опять-таки, это «действительно различие без разницы, и я бы не стал различать guish их в моем собственном коде:

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