亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

JS異步錯誤捕獲的一些事小結

2024-05-06 16:50:18
字體:
來源:轉載
供稿:網友

引入

我們都知道 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 并不是說寫上一個就可以高枕無憂了。難道每個函數都要寫嗎,
那什么情況下 try catch 無法捕獲 error 呢?

異步任務

  • 宏任務的回調函數中的錯誤無法捕獲

上面的栗子稍微改一下,主任務中寫一段 try catch,然后調用異步任務 task,task 會在一秒之后拋出一個錯誤。

// 異步任務const task = () => { setTimeout(() => { throw new Error('async error') }, 1000)}// 主任務function main() { try { task(); } catch(e) { console.log(e, 'err') console.log('continue...') }}

這種情況下 main 是無法 catch error 的,這跟瀏覽器的執行機制有關。異步任務由 eventloop 加入任務隊列,并取出入棧(js 主進程)執行,而當 task 取出執行的時候, main 的棧已經退出了,也就是上下文環境已經改變,所以 main 無法捕獲 task 的錯誤。

事件回調,請求回調同屬 tasks,所以道理是一樣的。eventloop 復習可以看這篇文章

  • 微任務(promise)的回調
// 返回一個 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'); }}

promise 的任務,也就是 then 里面的回調函數,拋出錯誤同樣也無法 catch。因為微任務隊列是在兩個 task 之間清空的,所以 then 入棧的時候,main 函數也已經出棧了。

并不是回調函數無法 try catch

很多人可能有一個誤解,因為大部分遇到無法 catch 的情況,都發生在回調函數,就認為回調函數不能 catch。

不全對,看一個最普通的栗子。

// 定義一個 fn,參數是函數。const fn = (cb: () => void) => { cb();};function main() { try { // 傳入 callback,fn 執行會調用,并拋出錯誤。 fn(() => { throw new Error('123'); }) } catch(e) { console.log('error'); }}main();

結果當然是可以 catch 的。因為 callback 執行的時候,跟 main 還在同一次事件循環中,即一個 eventloop tick。所以上下文沒有變化,錯誤是可以 catch 的。

根本原因還是同步代碼,并沒有遇到異步任務。

promise 的異常捕獲

構造函數

先看兩段代碼:

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 都不能捕獲到 error,因為 promise 內部的錯誤不會冒泡出來,而是被 promise 吃掉了,只有通過 promise.catch 才可以捕獲,所以用 Promise 一定要寫 catch 啊。

然后我們再來看一下使用 promise.catch 的兩段代碼:

// rejectconst p1 = new Promise((reslove, reject) => { if(1) { reject(); }});p1.catch((e) => console.log('p1 error'));
// throw new Errorconst p2 = new Promise((reslove, reject) => { if(1) { throw new Error('p2 error') }});p2.catch((e) => console.log('p2 error'));

promise 內部的無論是 reject 或者 throw new Error,都可以通過 catch 回調捕獲。

這里要跟我們最開始微任務的栗子區分,promise 的微任務指的是 then 的回調,而此處是 Promise 構造函數傳入的第一個參數,new Promise 是同步執行的。

then

那 then 之后的錯誤如何捕獲呢。

function main3() { Promise.resolve(true).then(() => { try { throw new Error('then'); } catch(e) { return e; } }).then(e => console.log(e.message));}

只能是在回調函數內部 catch 錯誤,并把錯誤信息返回,error 會傳遞到下一個 then 的回調。

用 Promise 捕獲異步錯誤

const p3 = () => new Promise((reslove, reject) => { setTimeout(() => { reject('async error'); })});function main3() { p3().catch(e => console.log(e));}main3();

把異步操作用 Promise 包裝,通過內部判斷,把錯誤 reject,在外面通過 promise.catch 捕獲。

async/await 的異常捕獲

首先我們模擬一個請求失敗的函數 fetchFailure,fetch 函數通常都是返回一個 promise。

main 函數改成 async,catch 去捕獲 fetchFailure reject 拋出的錯誤。能不能獲取到呢。

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

async 函數會被編譯成好幾段,根據 await 關鍵字,以及 catch 等,比如 main 函數就是拆成三段。

1.fetchFailure 2. console.log(res) 3. catch

通過 step 來控制迭代的進度,比如 "next",就是往下走一次,從 1->2,異步是通過 Promise.then() 控制的,你可以理解為就是一個 Promise 鏈,感興趣的可以去研究一下。 關鍵是生成器也有一個 "throw" 的狀態,當 Promise 的狀態 reject 后,會向上冒泡,直到 step('throw') 執行,然后 catch 里的代碼 console.log(e, 'e.message'); 執行。

明顯感覺 async/await 的錯誤處理更優雅一些,當然也是內部配合使用了 Promise。

更進一步

async 函數處理異步流程是利器,但是它也不會自動去 catch 錯誤,需要我們自己寫 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,然后執行傳入的參數-函數 即可。

然后,加上回調函數的參數傳遞,以及返回值遵守 first-error,向 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 后的邏輯,這塊還不支持自定義,再就是返回值總要判斷一下,是否有 error,也可以抽象一下。

所以我們可以在高階函數的 catch 處做一下文章,比如加入一些錯誤處理的回調函數支持不同的邏輯,然后一個項目中錯誤處理可以簡單分幾類,做不同的處理,就可以盡可能的復用代碼了。

// 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 的 store,都是以 class 的形式存在的,所以使用場景挺多的。

比如改成類裝飾器:

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

這種 class 裝飾器的寫法是看到黃子毅 這么寫過,感謝靈感。

koa 的錯誤處理

如果對 koa 不熟悉,可以選擇跳過不看。

koa 中當然也可以用上面 async 的做法,不過通常我們用 koa 寫 server 的時候,都是處理請求,一次 http 事務會掉起響應的中間件,所以 koa 的錯誤處理很好的利用了中間件的特性。

比如我的做法是,第一個中間件為捕獲 error,因為洋蔥模型的緣故,第一個中間件最后仍會執行,而當某個中間件拋出錯誤后,我期待能在此捕獲并處理。

// 第一個中間件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);// loggerapp.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,就可以捕獲前面中間件 throw 出的錯誤呢。首先我們前面 async/await 的地方解釋過,async 中await handle(),handle 函數內部的 throw new Error 或者 Promise.reject() 是可以被 async 的 catch 捕獲的。所以只需要 next 函數能夠拿到錯誤,并拋出就可以了,那看看 next 函數。

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

next 函數根據 index,取出當前的中間件執行。中間件函數如果是 async 函數,同樣的轉化為 generator 執行,內部的異步代碼順序由它自己控制,而我們知道 async 函數的錯誤是可以通過 try catch 捕獲的,所以在 next 函數中加上 try catch 捕獲中間件函數的錯誤,再 return 拋出去即可。所以我們才可以在第一個中間件捕獲。詳細代碼可以看下簡版 koa

然后 koa 還提供了 ctx.throw 和全局的 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);// loggerapp.use(async (ctx, next) => { console.log(ctx.req.body, 'body'); await next();})// 通過 ctx.throwapp.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 * ... */});

最后

本文的代碼都存放于

總的來說,目前 async 結合 promise 去處理 js 的異步錯誤會是比較方便的。另外,成熟的框架(react、koa)對于錯誤處理都有不錯的方式,盡可能去看一下官方是如何處理的。

這只是我對 js 中處理異步錯誤的一些理解。不過前端的需要捕獲異常的地方有很多,比如前端的代碼錯誤,cors 跨域錯誤,iframe 的錯誤,甚至 react 和 vue 的錯誤我們都需要處理,以及異常的監控和上報,以幫助我們及時的解決問題以及分析穩定性。采取多種方案應用到我們的項目中,讓我們不擔心頁面掛了,或者又報 bug 了,才能安安穩穩的去度假休息
注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
色小说视频一区| 久久人人爽亚洲精品天堂| 欧美一级在线亚洲天堂| 国产日韩中文在线| 51精品国产黑色丝袜高跟鞋| 另类色图亚洲色图| 久久亚洲春色中文字幕| 性金发美女69hd大尺寸| 国产91网红主播在线观看| 欧洲中文字幕国产精品| 亚洲自拍偷拍在线| 欧美日韩亚洲一区二区三区| 4438全国亚洲精品在线观看视频| 亚洲无av在线中文字幕| 97视频免费观看| 91欧美激情另类亚洲| 欧美亚洲一区在线| 欧美精品在线免费观看| 久久久精品网站| 欧美性视频网站| 久久艳片www.17c.com| 亚洲美女av在线| 欧美做受高潮1| 一区二区三区四区在线观看视频| 裸体女人亚洲精品一区| 欧美精品在线看| 国产精品成av人在线视午夜片| 欧美高清自拍一区| 国产精品电影观看| 国产+成+人+亚洲欧洲| 国产精品久久久一区| 懂色av中文一区二区三区天美| 中文字幕在线成人| 韩国美女主播一区| 正在播放欧美一区| 精品国产91久久久久久| 成人网在线免费看| 8090理伦午夜在线电影| 久久九九全国免费精品观看| 成人在线播放av| 国产精品老牛影院在线观看| 日韩激情片免费| 亚洲毛片在线看| 一区二区av在线| 美女999久久久精品视频| 91社影院在线观看| 91av中文字幕| 国产精品欧美日韩| www.日韩视频| 91国偷自产一区二区三区的观看方式| 亚洲第一av在线| 欧美激情国产高清| 久久这里有精品| 日韩最新免费不卡| 欧美乱人伦中文字幕在线| 日韩国产精品亚洲а∨天堂免| 精品福利樱桃av导航| 国产精品人成电影| 久久这里只有精品视频首页| 日韩精品中文字幕在线观看| 91精品视频专区| 欧美性视频网站| 国产日韩精品在线| 国产精品日韩专区| 欧美高清第一页| 欧美日韩国产成人高清视频| 日韩一级黄色av| 国产精品永久免费| 久久久av免费| 国产精品18久久久久久麻辣| 日本91av在线播放| 一本色道久久88综合亚洲精品ⅰ| 久久成人国产精品| 亚洲黄页网在线观看| 国产精品成av人在线视午夜片| 国产97人人超碰caoprom| 91禁国产网站| 欧美成人午夜激情视频| 国内久久久精品| 国产精品成av人在线视午夜片| 久久影视电视剧免费网站清宫辞电视| 欧美电影免费观看高清完整| 美日韩在线视频| 国产日韩精品入口| 国内精品小视频| 亚洲精品国产精品自产a区红杏吧| 亚洲欧美日韩国产中文专区| 国产999精品久久久影片官网| 国产三级精品网站| 91av福利视频| 亚洲无av在线中文字幕| …久久精品99久久香蕉国产| 国产精品日日摸夜夜添夜夜av| 国产欧美精品在线播放| 成人国产亚洲精品a区天堂华泰| 国产成人午夜视频网址| 亚洲精品美女免费| 欧美黄色小视频| 欧美日韩一区二区免费在线观看| 国产成人亚洲综合青青| 日韩av电影手机在线| 亚洲影院污污.| 色yeye香蕉凹凸一区二区av| 国产精品久久久久久久一区探花| 欧美黑人xxxx| 欧美激情免费视频| 欧美激情亚洲国产| 在线观看国产精品淫| 欧美日产国产成人免费图片| 亚洲高清免费观看高清完整版| 91欧美视频网站| 欧美丝袜一区二区三区| 日韩高清中文字幕| 国产在线观看精品| 国产精品永久免费视频| 91在线观看免费| 麻豆成人在线看| 精品综合久久久久久97| 欧洲亚洲免费视频| 欧美国产日韩精品| 国产97人人超碰caoprom| 亚洲网站在线观看| 欧美成人精品三级在线观看| 亚洲综合在线播放| 按摩亚洲人久久| 亚洲成av人片在线观看香蕉| 国产婷婷成人久久av免费高清| 成人两性免费视频| 77777少妇光屁股久久一区| 久久精品成人欧美大片古装| 欧美激情视频给我| 国产精品美女主播| 中文字幕日韩综合av| 色999日韩欧美国产| 日本精品中文字幕| 日韩av在线天堂网| 欧美韩国理论所午夜片917电影| 中文字幕日韩高清| 77777亚洲午夜久久多人| 懂色aⅴ精品一区二区三区蜜月| 成人伊人精品色xxxx视频| 国产日韩在线精品av| 亚洲天堂成人在线视频| 国内精品久久久久久中文字幕| 国产日韩在线视频| 成人深夜直播免费观看| 亚洲第一天堂av| 91精品国产777在线观看| 久久九九免费视频| 午夜精品久久久久久久男人的天堂| 欧美大片在线影院| 国产剧情久久久久久| 日本欧美中文字幕| 成人444kkkk在线观看| 欧美一级大片在线免费观看| 亚洲精品小视频| 久久久成人精品| 亚洲字幕在线观看| 狠狠躁天天躁日日躁欧美| 亚洲女人天堂网| 色偷偷9999www| 欧美在线影院在线视频| 日韩欧美精品中文字幕| 欧美精品福利在线|