異常捕獲
得益于框架支持的異步編程模型,錯誤完全可以用 try catch 來捕獲。在編寫應用代碼時,所有地方都可以直接用 try catch 來捕獲異常。
// app/service/test.js try { const res = await this.ctx.curl('http://eggjs.com/api/echo', { dataType: 'json' }); if (res.status !== 200) throw new Error('response status is not 200'); return res.data; } catch (err) { this.logger.error(err); return {}; }
|
按照正常代碼寫法,所有的異常都可以用這個方式進行捕獲并處理,但是一定要注意一些特殊的寫法可能帶來的問題。打一個不太正式的比方,我們的代碼全部都在一個異步調用鏈上,所有的異步操作都通過 await 串接起來了,但是只要有一個地方跳出了異步調用鏈,異常就捕獲不到了。
// app/controller/home.js class HomeController extends Controller { async buy () { const request = {}; const config = await ctx.service.trade.buy(request); // 下單后需要進行一次核對,且不阻塞當前請求 setImmediate(() => { ctx.service.trade.check(request).catch(err => ctx.logger.error(err)); }); } }
|
在這個場景中,如果 service.trade.check 方法中代碼有問題,導致執(zhí)行時拋出了異常,盡管框架會在最外層通過 try catch 統(tǒng)一捕獲錯誤,但是由于 setImmediate 中的代碼『跳出』了異步鏈,它里面的錯誤就無法被捕捉到了。因此在編寫類似代碼的時候一定要注意。
當然,框架也考慮到了這類場景,提供了 ctx.runInBackground(scope) 輔助方法,通過它又包裝了一個異步鏈,所有在這個 scope 里面的錯誤都會統(tǒng)一捕獲。
class HomeController extends Controller { async buy () { const request = {}; const config = await ctx.service.trade.buy(request); // 下單后需要進行一次核對,且不阻塞當前請求 ctx.runInBackground(async () => { // 這里面的異常都會統(tǒng)統(tǒng)被 Backgroud 捕獲掉,并打印錯誤日志 await ctx.service.trade.check(request); }); } }
|
為了保證異??勺粉?,必須保證所有拋出的異常都是 Error 類型,因為只有 Error 類型才會帶上堆棧信息,定位到問題。
框架層統(tǒng)一異常處理
框架通過 onerror 插件提供了統(tǒng)一的錯誤處理機制。對一個請求的所有處理方法(Middleware、Controller、Service)中拋出的任何異常都會被它捕獲,并自動根據(jù)請求想要獲取的類型返回不同類型的錯誤(基于 Content Negotiation)。
請求需求的格式 | 環(huán)境 | errorPageUrl 是否配置 | 返回內容 |
---|
HTML & TEXT | local & unittest | - | onerror 自帶的錯誤頁面,展示詳細的錯誤信息 |
HTML & TEXT | 其他 | 是 | 重定向到 errorPageUrl |
HTML & TEXT | 其他 | 否 | onerror 自帶的沒有錯誤信息的簡單錯誤頁(不推薦) |
JSON & JSONP | local & unittest | - | JSON 對象或對應的 JSONP 格式響應,帶詳細的錯誤信息 |
JSON & JSONP | 其他 | - | JSON 對象或對應的 JSONP 格式響應,不帶詳細的錯誤信息 |
errorPageUrl
onerror 插件的配置中支持 errorPageUrl 屬性,當配置了 errorPageUrl 時,一旦用戶請求線上應用的 HTML 頁面異常,就會重定向到這個地址。
在 config/config.default.js 中
// config/config.default.js module.exports = { onerror: { // 線上頁面發(fā)生異常時,重定向到這個頁面上 errorPageUrl: '/50x.html', }, };
|
自定義統(tǒng)一異常處理
盡管框架提供了默認的統(tǒng)一異常處理機制,但是應用開發(fā)中經(jīng)常需要對異常時的響應做自定義,特別是在做一些接口開發(fā)的時候??蚣茏詭У?onerror 插件支持自定義配置錯誤處理方法,可以覆蓋默認的錯誤處理方法。
// config/config.default.js module.exports = { onerror: { all(err, ctx) { // 在此處定義針對所有響應類型的錯誤處理方法 // 注意,定義了 config.all 之后,其他錯誤處理方法不會再生效 ctx.body = 'error'; ctx.status = 500; }, html(err, ctx) { // html hander ctx.body = '<h3>error</h3>'; ctx.status = 500; }, json(err, ctx) { // json hander ctx.body = { message: 'error' }; ctx.status = 500; }, jsonp(err, ctx) { // 一般來說,不需要特殊針對 jsonp 進行錯誤定義,jsonp 的錯誤處理會自動調用 json 錯誤處理,并包裝成 jsonp 的響應格式 }, }, };
|
404
框架并不會將服務端返回的 404 狀態(tài)當做異常來處理,但是框架提供了當響應為 404 且沒有返回 body 時的默認響應。
- 當請求被框架判定為需要 JSON 格式的響應時,會返回一段 JSON:{ "message": "Not Found" }
- 當請求被框架判定為需要 HTML 格式的響應時,會返回一段 HTML:<h1>404 Not Found</h1>
框架支持通過配置,將默認的 HTML 請求的 404 響應重定向到指定的頁面。
// config/config.default.js module.exports = { notfound: { pageUrl: '/404.html', }, };
|
自定義 404 響應
在一些場景下,我們需要自定義服務器 404 時的響應,和自定義異常處理一樣,我們也只需要加入一個中間件即可對 404 做統(tǒng)一處理:
// app/middleware/notfound_handler.js module.exports = () => { return async function notFoundHandler(ctx, next) { await next(); if (ctx.status === 404 && !ctx.body) { if (ctx.acceptJSON) { ctx.body = { error: 'Not Found' }; } else { ctx.body = '<h1>Page Not Found</h1>'; } } }; };
|
在配置中引入中間件:
// config/config.default.js module.exports = { middleware: [ 'notfoundHandler' ], }; |
更多建議: