представлять
Все мы знаем, что try catch не может отлавливать ошибки в асинхронных задачах setTimeout, в чем причина этого? А асинхронный код особенно распространен в js, как можно сравнивать?
Ситуации, которые невозможно поймать
function main() {
try {
setTimeout(() => {
throw new Error('async error')
}, 1000)
} catch(e) {
console.log(e, 'err')
console.log('continue...')
}
}
main();
В этом коде функция обратного вызова setTimeout выдает ошибку, которая не будет поймана в catch, что приведет к падению программы с ошибкой.
Так что попробуйте поймать в js не означает, что вы можете сидеть сложа руки и расслабиться после написания одного. Вы должны написать каждую функцию? При каких обстоятельствах можно попытаться поймать ошибку?
асинхронная задача
-
Ошибка в функции обратного вызова макроса не может быть обнаружена
Каштан выше немного изменен, напишите try catch в основной задаче, а затем вызовите асинхронную задачу, задача выдаст ошибку через одну секунду.
// 异步任务 const task = () => { setTimeout(() => { throw new Error('async error') }, 1000) } // 主任务 function main() { try { task(); } catch(e) { console.log(e, 'err') console.log('continue...') } }
В этом случае main не может поймать ошибку, связанную с механизмом выполнения браузера. Асинхронная задача добавляется в очередь задач циклом событий и вынимается и помещается в стек (основной процесс js) для выполнения.Когда задача вынимается и выполняется, основной стек уже вышел, то есть контекст изменился, поэтому main не может поймать ошибку задачи.
Обратные вызовы событий и обратные вызовы запросов относятся к задачам, поэтому рассуждение одинаковое. Обзор Eventloop может видеть этостатья
-
Обратный вызов микрозадачи (обещание)
// 返回一个 promise 对象 const promiseFetch = () => new Promise((reslove) => { reslove(); }) function main() { try { // 回调函数里抛出错误 promiseFetch().then(() => { throw new Error('err') }) } catch(e) { console.log(e, 'eeee'); console.log('continue'); } }
Задача обещания, то есть callback-функция в then, выдает ошибку и не может быть поймана. Поскольку очередь микрозадач очищается между двумя задачами, когда then помещается в стек, основная функция также выталкивается из стека.
Дело не в том, что функция обратного вызова не может попытаться поймать
У многих людей может возникнуть непонимание, потому что большинство ситуаций, которые невозможно отловить, происходят в функции обратного вызова, и они думают, что функция обратного вызова не может быть поймана.
Не совсем так, посмотрите на самый обычный каштан.
// 定义一个 fn,参数是函数。
const fn = (cb: () => void) => {
cb();
};
function main() {
try {
// 传入 callback,fn 执行会调用,并抛出错误。
fn(() => {
throw new Error('123');
})
} catch(e) {
console.log('error');
}
}
main();
Результат конечно достойный. Потому что, когда callback выполняется, он все еще находится в том же цикле событий, что и main, то есть в тике цикла событий. Таким образом, контекст не меняется, и ошибки могут быть обнаружены. Основной причиной по-прежнему является синхронный код, и асинхронные задачи не встречаются.
Перехват исключения для обещания
Конструктор
Сначала посмотрите на два фрагмента кода:
function main1() {
try {
new Promise(() => {
throw new Error('promise1 error')
})
} catch(e) {
console.log(e.message);
}
}
function main2() {
try {
Promise.reject('promise2 error');
} catch(e) {
console.log(e.message);
}
}
Вышеупомянутые два try catch не могут поймать ошибку, потому что ошибка внутри промиса не выскочит наружу, а будет съедена промисом.Его может поймать только promise.catch, поэтому вы должны написать catch при использовании промиса.
Затем давайте посмотрим на два фрагмента кода, в которых используется promise.catch:
// reject
const p1 = new Promise((reslove, reject) => {
if(1) {
reject();
}
});
p1.catch((e) => console.log('p1 error'));
// throw new Error
const p2 = new Promise((reslove, reject) => {
if(1) {
throw new Error('p2 error')
}
});
p2.catch((e) => console.log('p2 error'));
Будь то отклонение или выдача новой ошибки внутри обещания, ее можно поймать обратным вызовом catch.
Здесь нам нужно отличить его от каштанов нашей исходной микрозадачи.Микрозадача обещания относится к обратному вызову then, а вот первый параметр, переданный конструктором промиса, и новый промис выполняется синхронно.
then
Как поймать ошибку после этого.
function main3() {
Promise.resolve(true).then(() => {
try {
throw new Error('then');
} catch(e) {
return e;
}
}).then(e => console.log(e.message));
}
Вы можете поймать ошибку только внутри функции обратного вызова и вернуть сообщение об ошибке, и ошибка будет передана следующему обратному вызову.
Перехватывайте асинхронные ошибки с помощью промисов
const p3 = () => new Promise((reslove, reject) => {
setTimeout(() => {
reject('async error');
})
});
function main3() {
p3().catch(e => console.log(e));
}
main3();
Оберните асинхронную операцию с помощью Promise, передайте внутреннюю оценку, отклоните ошибку и перехватите ее извне с помощью promise.catch.
Перехват исключения для async/await
Сначала мы симулируем функцию fetchFailure при ошибке запроса, функция выборки обычно возвращает обещание.
Основная функция изменена на асинхронную, а catch перехватывает ошибку, вызванную отклонением fetchFailure. Вы можете получить его.
const fetchFailure = () => new Promise((resolve, reject) => {
setTimeout(() => {// 模拟请求
if(1) reject('fetch failure...');
})
})
async function main () {
try {
const res = await fetchFailure();
console.log(res, 'res');
} catch(e) {
console.log(e, 'e.message');
}
}
main();
Асинхронная функция будет скомпилирована в несколько секций, согласно ключевому слову await и catch, например, основная функция разбита на три секции.
- fetchFailure 2. console.log(res) 3. catch
Ход итерации контролируется пошагово, например, «следующий», который должен спуститься один раз, от 1 до 2, асинхронный управляется Promise.then(), вы можете понять это как цепочку обещаний, если вы заинтересованы, вы можете пойти, чтобы исследовать это. Суть в том, что у генератора также есть состояние "throw". Когда состояние Promise отклоняется, оно будет всплывать до тех пор, пока не будет выполнен step('throw'), а затем код в catchconsole.log(e, 'e.message');
воплощать в жизнь.
Очевидно, что обработка ошибок в async/await более элегантна, и, конечно же, Promise используется внутри.
Сделайте еще один шаг вперед
Асинхронная функция - мощный инструмент для обработки асинхронных процессов, но она не будет автоматически ловить ошибки. Нам нужно написать try catch самим. Если мы напишем по одной для каждой функции, это довольно хлопотно. В бизнесе много асинхронных функций.
Первое, что приходит на ум, это извлечь try catch и логику после catch.
const handle = async (fn: any) => {
try {
return await fn();
} catch(e) {
// do sth
console.log(e, 'e.messagee');
}
}
async function main () {
const res = await handle(fetchFailure);
console.log(res, 'res');
}
Напишите функцию более высокого порядка для переноса fetchFailure, логику повторного использования функции более высокого порядка, например, try catch здесь, а затем выполните входящую функцию-параметр.
Затем добавьте передачу параметров функции обратного вызова и возвращаемое значение для соответствия первой ошибке, что соответствует синтаксису node/go. следующим образом:
const handleTryCatch = (fn: (...args: any[]) => Promise<{}>) => async (...args: any[]) => {
try {
return [null, await fn(...args)];
} catch(e) {
console.log(e, 'e.messagee');
return [e];
}
}
async function main () {
const [err, res] = await handleTryCatch(fetchFailure)('');
if(err) {
console.log(err, 'err');
return;
}
console.log(res, 'res');
}
Но есть еще несколько проблем, одна это логика после catch, которая не поддерживает кастомизацию, и о возвращаемом значении всегда нужно судить, есть ли ошибка, его тоже можно абстрагировать. Поэтому мы можем сделать статью по отлову функций высшего порядка, например добавить какие-то callback-функции для обработки ошибок для поддержки разной логики, и тогда обработку ошибок в проекте можно будет просто разделить на несколько категорий с кодом.
// 1. 三阶函数。第一次传入错误处理的 handle,第二次是传入要修饰的 async 函数,最后返回一个新的 function。
const handleTryCatch = (handle: (e: Error) => void = errorHandle) =>
(fn: (...args: any[]) => Promise<{}>) => async(...args: any[]) => {
try {
return [null, await fn(...args)];
} catch(e) {
return [handle(e)];
}
}
// 2. 定义各种各样的错误类型
// 我们可以把错误信息格式化,成为代码里可以处理的样式,比如包含错误码和错误信息
class DbError extends Error {
public errmsg: string;
public errno: number;
constructor(msg: string, code: number) {
super(msg);
this.errmsg = msg || 'db_error_msg';
this.errno = code || 20010;
}
}
class ValidatedError extends Error {
public errmsg: string;
public errno: number;
constructor(msg: string, code: number) {
super(msg);
this.errmsg = msg || 'validated_error_msg';
this.errno = code || 20010;
}
}
// 3. 错误处理的逻辑,这可能只是其中一类。通常错误处理都是按功能需求来划分
// 比如请求失败(200 但是返回值有错误信息),比如 node 中写 db 失败等。
const errorHandle = (e: Error) => {
// do something
if(e instanceof ValidatedError || e instanceof DbError) {
// do sth
return e;
}
return {
code: 101,
errmsg: 'unKnown'
};
}
const usualHandleTryCatch = handleTryCatch(errorHandle);
// 以上的代码都是多个模块复用的,那实际的业务代码可能只需要这样。
async function main () {
const [error, res] = await usualHandleTryCatch(fetchFail)(false);
if(error) {
// 因为 catch 已经做了拦截,甚至可以加入一些通用逻辑,这里甚至不用判断 if error
console.log(error, 'error');
return;
}
console.log(res, 'res');
}
После решения проблемы повторного использования некоторой логики ошибок достаточно инкапсулировать ее в разные обработчики ошибок. Но когда используются эти процессоры, поскольку все они являются функциями более высокого порядка, вы можете использовать метод написания декоратора es6.
Однако декораторы можно использовать только в классах и методах классов, поэтому, если они представлены в виде функций, их нельзя использовать. Однако в повседневной разработке, такой как компоненты React или магазин Mobx, все они существуют в виде классов, поэтому существует множество сценариев использования.
Например, измените его на декоратор класса:
const asyncErrorWrapper = (errorHandler: (e: Error) => void = errorHandle) => (target: Function) => {
const props = Object.getOwnPropertyNames(target.prototype);
props.forEach((prop) => {
var value = target.prototype[prop];
if(Object.prototype.toString.call(value) === '[object AsyncFunction]'){
target.prototype[prop] = async (...args: any[]) => {
try{
return await value.apply(this,args);
}catch(err){
return errorHandler(err);
}
}
}
});
}
@asyncErrorWrapper(errorHandle)
class Store {
async getList (){
return Promise.reject('类装饰:失败了');
}
}
const store = new Store();
async function main() {
const o = await store.getList();
}
main();
То, как написан этот декоратор класса, это увидетьХуан ЦзыиЯ написал это, спасибо за вдохновение.
Обработка ошибок в koa
Если вы не знакомы с коа, вы можете пропустить его.
Конечно, описанный выше асинхронный метод также можно использовать в koa, но обычно, когда мы используем koa для написания сервера, мы все обрабатываем запросы.Транзакция http удаляет промежуточное программное обеспечение ответа, поэтому обработка ошибок koa помогает использование промежуточного ПО.
Например, мой подход заключается в том, чтобы фиксировать ошибки в первом промежуточном программном обеспечении.Из-за луковой модели первое промежуточное программное обеспечение все равно будет выполняться в конце, и когда промежуточное программное обеспечение выдает ошибку, я ожидаю, что смогу зафиксировать и обработать ее здесь. . . .
// 第一个中间件
const errorCatch = async(ctx, next) => {
try {
await next();
} catch(e) {
// 在此捕获 error 路由,throw 出的 Error
console.log(e, e.message, 'error');
ctx.body = 'error';
}
}
app.use(errorCatch);
// logger
app.use(async (ctx, next) => {
console.log(ctx.req.body, 'body');
await next();
})
// router 的某个中间件
router.get('/error', async (ctx, next) => {
if(1) {
throw new Error('错误测试')
}
await next();
})
Зачем писать try catch в первом промежуточном программном обеспечении, чтобы перехватывать ошибки, выдаваемые предыдущим промежуточным программным обеспечением? Прежде всего, как мы объясняли ранее в async/await, в asyncawait handle()
, внутри функции дескриптораthrow new Error
илиPromise.reject()
Может быть пойман асинхронным уловом. Так что просто нужна следующая функция, чтобы получить ошибку и выдать ее, а затем посмотреть на следующую функцию.
// compose 是传入中间件的数组,最终形成中间件链的,next 控制游标。
compose(middlewares) {
return (context) => {
let index = 0;
// 为了每个中间件都可以是异步调用,即 `await next()` 这种写法,每个 next 都要返回一个 promise 对象
function next(index) {
const func = middlewares[index];
try {
// 在此处写 try catch,因为是写到 Promise 构造体中的,所以抛出的错误能被 catch
return new Promise((resolve, reject) => {
if (index >= middlewares.length) return reject('next is inexistence');
resolve(func(context, () => next(index + 1)));
});
} catch(err) {
// 捕获到错误,返回错误
return Promise.reject(err);
}
}
return next(index);
}
}
Следующая функция берет текущее выполнение промежуточного программного обеспечения в соответствии с индексом. Если промежуточная функция является асинхронной функцией, она преобразуется в выполнение генератора, а внутренняя последовательность асинхронного кода контролируется сама по себе, и мы знаем, что ошибка асинхронной функции может быть перехвачена с помощью try catch, поэтому добавьте try catch в следующая функция.Ошибка функции промежуточного программного обеспечения может быть вызвана возвратом. Итак, мы можем захватить первое промежуточное ПО. Подробный код можно увидеть нижесокращенно коа
Затем koa также предоставляет ctx.throw и global app.on для отлова ошибок. Если вы не пишете промежуточное программное обеспечение для обработки ошибок, вы можете использовать ctx.throw для возврата к внешнему интерфейсу, чтобы не допустить ошибки кода. Но throw new Error также выгоден, потому что в логике кода определенного промежуточного программного обеспечения, когда мы не хотим, чтобы промежуточное программное обеспечение выполнялось, возвращаем его непосредственно во внешний интерфейс, выбрасываем ошибку напрямую и позволяем общему промежуточному программному обеспечению обрабатывать ее, в любом случае это неверная информация.
// 定义不同的错误类型,在此可以捕获,并处理。
const errorCatch = async(ctx, next) => {
try {
await next();
} catch (err) {
const { errmsg, errno, status = 500, redirect } = err;
if (err instanceof ValidatedError || err instanceof DbError || err instanceof AuthError || err instanceof RequestError) {
ctx.status = 200;
ctx.body = {
errmsg,
errno,
};
return;
}
ctx.status = status;
if (status === 302 && redirect) {
console.log(redirect);
ctx.redirect(redirect);
}
if (status === 500) {
ctx.body = {
errmsg: err.message,
errno: 90001,
};
ctx.app.emit('error', err, ctx);
}
}
}
app.use(errorCatch);
// logger
app.use(async (ctx, next) => {
console.log(ctx.req.body, 'body');
await next();
})
// 通过 ctx.throw
app.use(async (ctx, next) => {
//will NOT log the error and will return `Error Message` as the response body with status 400
ctx.throw(400,'Error Message');
});
// router 的某个中间件
router.get('/error', async (ctx, next) => {
if(1) {
throw new Error('错误测试')
}
await next();
})
// 最后的兜底
app.on('error', (err, ctx) => {
/* centralized error handling:
* console.log error
* write error to log file
* save error and request information to database if ctx.request match condition
* ...
*/
});
наконец
Код этой статьи хранится вэто
В общем, удобнее совмещать асинхронность с промисом, чтобы иметь дело с асинхронными ошибками js. Кроме того, у зрелых фреймворков (react, koa) есть хорошие способы обработки ошибок, попробуйте посмотреть, как с этим справится официальный.
Это лишь часть моего понимания обработки асинхронных ошибок в js. Тем не менее, есть много мест, где исключения должны быть перехвачены во внешнем интерфейсе, например, ошибки кода внешнего интерфейса, междоменные ошибки Cors, ошибки iframe и даже ошибки реакции и vue, с которыми нам нужно иметь дело, а также мониторинг исключений и отчетность, чтобы помочь нам решить их своевременно проблем и аналитической стабильности. Мы берем самые разные решения и применяем их в наших проектах, чтобы нам не пришлось снова беспокоиться о зависании страницы или багах, чтобы мы могли отправиться в отпуск и отдохнуть в целости и сохранности😆
Наконец, адрес блога:GitHub.com/Сунь Юнцзянь…