Khi lập trình, chúng ta thường sợ error. Nhưng bằng cách làm việc với nó, ta học được tại sao không nên làm cái này (và cái kia), cũng như làm thế nào để code ngon hơn.
Bài viết này chia thành 3 phần, đầu tiên là tổng quan về error. Sau đó chúng ta sẽ tập trung vào backend (Node.js + Express.js) và cuối cùng là cách xử lý error trong React.js.
I. JavaScript Errors và generic handling
throw new Error('something went wrong')
– sẽ tạo một thể hiện của Error trong JavaScript và ngừng thực thi đoạn code, cho đến khi bạn làm gì đó với Error. Khi mới lập trình JavaScript, thường thì bạn sẽ không làm như vậy, mà chỉ bắt gặp nó khi xài thư viện (hoặc khi thực thi code), chẳng hạn ReferenceError: fs is not defined
.
Error Object
Object Error có 2 thuộc tính để ta có thể sử dụng. Một là message, tức là đối số bạn truyền vào Error constructor, ví dụ new Error('This is the message')
. Bạn có thể truy cập message thông qua thuộc tính messsage
:
1 2 3 | <span class="token keyword">const</span> myError <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span>‘please improve your code’<span class="token punctuation">)</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>myError<span class="token punctuation">.</span>message<span class="token punctuation">)</span> <span class="token comment">// please improve your code</span> |
Thuộc tính thứ hai cực kỳ quan trọng chính là stack trace của Error. Bạn có thể truy xuất nó thông qua thuộc tính stack
. Stack sẽ cho bạn lịch sử những file chịu trách nhiệm gây ra lỗi đó. Stack cũng chứa message và theo sau chính là những điểm xảy ra lỗi và file tương ứng.
1 2 3 4 5 6 7 8 9 10 11 | Error<span class="token punctuation">:</span> please improve your code at Object<span class="token punctuation">.</span><span class="token operator"><</span>anonymous<span class="token operator">></span> <span class="token punctuation">(</span><span class="token operator">/</span>Users<span class="token operator">/</span>gisderdube<span class="token operator">/</span>Documents<span class="token operator">/</span>_projects<span class="token operator">/</span>hacking<span class="token punctuation">.</span>nosync<span class="token operator">/</span>error<span class="token operator">-</span>handling<span class="token operator">/</span>src<span class="token operator">/</span>general<span class="token punctuation">.</span>js<span class="token punctuation">:</span><span class="token number">1</span><span class="token punctuation">:</span><span class="token number">79</span><span class="token punctuation">)</span> at Module<span class="token punctuation">.</span><span class="token function">_compile</span> <span class="token punctuation">(</span>internal<span class="token operator">/</span>modules<span class="token operator">/</span>cjs<span class="token operator">/</span>loader<span class="token punctuation">.</span>js<span class="token punctuation">:</span><span class="token number">689</span><span class="token punctuation">:</span><span class="token number">30</span><span class="token punctuation">)</span> at Object<span class="token punctuation">.</span>Module<span class="token punctuation">.</span>_extensions<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token function">js</span> <span class="token punctuation">(</span>internal<span class="token operator">/</span>modules<span class="token operator">/</span>cjs<span class="token operator">/</span>loader<span class="token punctuation">.</span>js<span class="token punctuation">:</span><span class="token number">700</span><span class="token punctuation">:</span><span class="token number">10</span><span class="token punctuation">)</span> at Module<span class="token punctuation">.</span><span class="token function">load</span> <span class="token punctuation">(</span>internal<span class="token operator">/</span>modules<span class="token operator">/</span>cjs<span class="token operator">/</span>loader<span class="token punctuation">.</span>js<span class="token punctuation">:</span><span class="token number">599</span><span class="token punctuation">:</span><span class="token number">32</span><span class="token punctuation">)</span> at <span class="token function">tryModuleLoad</span> <span class="token punctuation">(</span>internal<span class="token operator">/</span>modules<span class="token operator">/</span>cjs<span class="token operator">/</span>loader<span class="token punctuation">.</span>js<span class="token punctuation">:</span><span class="token number">538</span><span class="token punctuation">:</span><span class="token number">12</span><span class="token punctuation">)</span> at Function<span class="token punctuation">.</span>Module<span class="token punctuation">.</span><span class="token function">_load</span> <span class="token punctuation">(</span>internal<span class="token operator">/</span>modules<span class="token operator">/</span>cjs<span class="token operator">/</span>loader<span class="token punctuation">.</span>js<span class="token punctuation">:</span><span class="token number">530</span><span class="token punctuation">:</span><span class="token number">3</span><span class="token punctuation">)</span> at Function<span class="token punctuation">.</span>Module<span class="token punctuation">.</span><span class="token function">runMain</span> <span class="token punctuation">(</span>internal<span class="token operator">/</span>modules<span class="token operator">/</span>cjs<span class="token operator">/</span>loader<span class="token punctuation">.</span>js<span class="token punctuation">:</span><span class="token number">742</span><span class="token punctuation">:</span><span class="token number">12</span><span class="token punctuation">)</span> at <span class="token function">startup</span> <span class="token punctuation">(</span>internal<span class="token operator">/</span>bootstrap<span class="token operator">/</span>node<span class="token punctuation">.</span>js<span class="token punctuation">:</span><span class="token number">266</span><span class="token punctuation">:</span><span class="token number">19</span><span class="token punctuation">)</span> at <span class="token function">bootstrapNodeJSCore</span> <span class="token punctuation">(</span>internal<span class="token operator">/</span>bootstrap<span class="token operator">/</span>node<span class="token punctuation">.</span>js<span class="token punctuation">:</span><span class="token number">596</span><span class="token punctuation">:</span><span class="token number">3</span><span class="token punctuation">)</span> |
Throwing và Handling Errors
Vì thể hiện của Error không có bất kỳ hiệu ứng, chẳng hạn new Error(‘…’) không làm bất cứ thứ gì. Khi Error throw
n, nó sẽ trở nên thú vị hơn. Đoạn mã sẽ ngừng thực thi cho đến khi bạn xử lý nó ở đâu đó. Hãy nhơ là dù bạn throw
lỗi thủ công hay là do thư viện hoặc runtime. Hãy xem cách chúng ta có thể làm việc với Error trong những kịch bản khác nhau như thế nào.
try ... catch
Đây là cách đơn giản nhất, nhưng thường bị bỏ quên khi xử lý lỗi – may là ngày nay nó được sử dụng thường xuyên hơn nhờ async / await. Nó cũng được dùng để bắt lỗi đồng bộ. Ví dụ:
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">const</span> a <span class="token operator">=</span> <span class="token number">5</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span> <span class="token comment">// b is not defined, so throws an error</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">err</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token comment">// will log the error with the error stack</span> <span class="token punctuation">}</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span> <span class="token comment">// still gets executed</span> |
Nếu chúng ta không đặt console.log(b)
trong try ... catch
, đoạn code sẽ ngừng thực thi.
… finally
Đôi lúc ta cần phải thực thi code dù bất cứ điều kiện nào xảy ra, lúc này bạn có thể dùng block thứ 3 là finally
, chỉ cần thêm một dòng sau try ... catch
.
1 2 3 4 5 6 7 8 9 10 | <span class="token keyword">const</span> a <span class="token operator">=</span> <span class="token number">5</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>b<span class="token punctuation">)</span> <span class="token comment">// b is not defined, so throws an error</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">err</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token comment">// will log the error with the error stack</span> <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>a<span class="token punctuation">)</span> <span class="token comment">// will always get executed</span> <span class="token punctuation">}</span> |
Xử lý bất đồng bộ — Callbacks
Bất đồng bộ là chủ đề mà bạn luôn phải lưu ý khi làm việc với JavaScript. Khi bạn có một hàm bất đồng bộ, và một Error xảy ra trong hàm đó, đoạn mã sẽ tiếp tục thực thi, bởi vậy không có lỗi nào được bắn ra ngay. Khi xử lý hàm bất đồng bộ với callback, bạn thường nhận 2 đối số trong callback, thường sẽ như này:
1 2 3 4 5 | <span class="token function">myAsyncFunc</span><span class="token punctuation">(</span>someInput<span class="token punctuation">,</span> <span class="token punctuation">(</span>err<span class="token punctuation">,</span> result<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token keyword">return</span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token comment">// we will see later what to do with the error object.</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> |
Nếu có Error, tham số err
sẽ tương đương với lỗi đó. Nếu không thì tham số này sẽ là undefined
hoặc null
.
Bất đồng bộ – Promises
Một cách tốt hơn để xử lý bất đồng bộ là dùng promises. Bên cạnh việc code dễ độc hơn, chúng ta cũng cải thiện việc xử lý lỗi. Không cần phải quan tâm quá nhiefu đến việc bắt chính xác Error, miễn là có block catch
là được. Khi chain promise, mỗi một block catch
sẽ bắt tất cả lỗi từ khi thực thi promise hoặc từ block catch
cuối cùng. Lưu ý rằng promise mà không có catch
sẽ không thể terminate
code, nhưng bạn sẽ nhận một message khó đọc hơn như sau:
1 2 3 | <span class="token punctuation">(</span>node<span class="token punctuation">:</span><span class="token number">7741</span><span class="token punctuation">)</span> UnhandledPromiseRejectionWarning<span class="token punctuation">:</span> Unhandled promise <span class="token function">rejection</span> <span class="token punctuation">(</span>rejection id<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Error<span class="token punctuation">:</span> something went <span class="token function">wrong</span> <span class="token punctuation">(</span>node<span class="token punctuation">:</span><span class="token number">7741</span><span class="token punctuation">)</span> DeprecationWarning<span class="token punctuation">:</span> Unhandled promise rejections are deprecated<span class="token punctuation">.</span> In the future<span class="token punctuation">,</span> promise rejections that are not handled will terminate the Node<span class="token punctuation">.</span>js process <span class="token keyword">with</span> a non<span class="token operator">-</span>zero exit code<span class="token punctuation">.</span> |
Bởi vậy hãy luôn thêm một block catch
cho promise. Như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>res <span class="token operator">=></span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span> <span class="token comment">// 1</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'something went wrong'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>res <span class="token operator">=></span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span> <span class="token comment">// will not get executed</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span>err <span class="token operator">=></span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token comment">// we will see what to do with it later</span> <span class="token keyword">return</span> Promise<span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>res <span class="token operator">=></span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>res<span class="token punctuation">)</span> <span class="token comment">// 3</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span>err <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// in case in the previous block occurs another error</span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> |
try … catch — again
Với việc xuất hiện async / await trong JavaScript, ta trở lại với cách bắt lỗi cổ điển, với try ... catch ... finally
, khá nhẹ nhàng:
1 2 3 4 5 6 7 8 9 10 | <span class="token punctuation">;</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">someFuncThatThrowsAnError</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">err</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token comment">// we will make sense of that later</span> <span class="token punctuation">}</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Easy!'</span><span class="token punctuation">)</span> <span class="token comment">// will get executed</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span> |
II. Xử lý lỗi trong Server
Giờ chúng ta đã có những công cụ để làm việc với Error, hãy xem trong thực tế thì ta sẽ thực hiện như thế nào. Xử lý lỗi ở backend là một phần trong ứng dụng của bạn. Có nhiều cách tiếp cận với vấn đề này, tôi sẽ chỉ cho bạn cách tiếp cận với việc custom Error constructor và Error codes, mà ta có thể dể dàng truyền đến frontend hoặc API.
Ta sẽ dùng Express.js framework làm routing. Thử nghĩ cấu trúc mà ta muốn bắt lỗi một cách hiệu quả nhất. Ta muốn:
- Xử lý lỗi chung, chẳng hạn:
Something went wrong, please try again or contact us.
. Cách làm này không phải quá hay, nhưng ít nhất là nó thông báo cho user có gì đó không đúng, thay vì màn hình loading vô hạn. - Xử lý lỗi cụ thể để đưa cho user thông tin chi tiết về lỗi và cách xử lý, chẳng hạn có một số thông tin đang thiếu, nội dung nhập vào đã có trong cơ sở dữ liệu, …
Building custom Error constructor
Chúng ta sẽ sử Error constructor và mở rộng nó. Kế thừa là phương án khá mạo hiểm trong JavaScript, nhưng lần này thì nó rất hữu ích. Tại sao chúng ta cần nó? Bởi vì ta vẫn muốn stack trace để debug hiệu quả hơn. Chúng ta chỉ cần thêm code
, sau này sẽ truy xuất thông qua err.code
, cũng như status để truyền đến frontend.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">class</span> <span class="token class-name">CustomError</span> <span class="token keyword">extends</span> <span class="token class-name">Error</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span>code <span class="token operator">=</span> <span class="token string">'GENERIC'</span><span class="token punctuation">,</span> status <span class="token operator">=</span> <span class="token number">500</span><span class="token punctuation">,</span> <span class="token operator">...</span>params<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token operator">...</span>params<span class="token punctuation">)</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Error<span class="token punctuation">.</span>captureStackTrace<span class="token punctuation">)</span> <span class="token punctuation">{</span> Error<span class="token punctuation">.</span><span class="token function">captureStackTrace</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> CustomError<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">this</span><span class="token punctuation">.</span>code <span class="token operator">=</span> code <span class="token keyword">this</span><span class="token punctuation">.</span>status <span class="token operator">=</span> status <span class="token punctuation">}</span> <span class="token punctuation">}</span> module<span class="token punctuation">.</span>exports <span class="token operator">=</span> CustomError |
Xử lý routing
Như đã nói, chúng ta muốn một single point of truth
để xử lý lỗi, nghĩa là mọi route đều có cách xử lý lỗi như nhau. Mặc định thì express không hỗ trợ vì route đã được đóng gói.
Để giải quyết vấn đề này, ta có thể cài đặt một route handler và định nghĩa logic như function thông thường. Bằng cách đó, khi function routing throw error, nó sẽ được trả lại route handler, rồi truyền đến frontend. Mỗi khi có lỗi xảy ra ở backend, chúng ta muốn truyền response đến frontend, giả sử là JSON API theo format sau:
1 2 3 4 5 | <span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">'SOME_ERROR_CODE'</span><span class="token punctuation">,</span> description<span class="token punctuation">:</span> <span class="token string">'Something bad happened. Please try again or contact support.'</span> <span class="token punctuation">}</span> |
Route handler trông như này:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | <span class="token keyword">const</span> express <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'express'</span><span class="token punctuation">)</span> <span class="token keyword">const</span> router <span class="token operator">=</span> express<span class="token punctuation">.</span><span class="token function">Router</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">const</span> CustomError <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../CustomError'</span><span class="token punctuation">)</span> router<span class="token punctuation">.</span><span class="token function">use</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>req<span class="token punctuation">,</span> res<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> route <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`.</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>req<span class="token punctuation">.</span>path<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">[</span>req<span class="token punctuation">.</span>method<span class="token punctuation">]</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token function">route</span><span class="token punctuation">(</span>req<span class="token punctuation">)</span> <span class="token comment">// We pass the request to the route function</span> res<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span> <span class="token comment">// We just send to the client what we get returned from the route function</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">err</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* This will be entered, if an error occurs inside the route function. */</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>err <span class="token keyword">instanceof</span> <span class="token class-name">CustomError</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* In case the error has already been handled, we just transform the error to our return object. */</span> <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span>err<span class="token punctuation">.</span>status<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token punctuation">{</span> error<span class="token punctuation">:</span> err<span class="token punctuation">.</span>code<span class="token punctuation">,</span> description<span class="token punctuation">:</span> err<span class="token punctuation">.</span>message<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token comment">// For debugging reasons</span> <span class="token comment">// It would be an unhandled error, here we can just return our generic error object.</span> <span class="token keyword">return</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">'GENERIC'</span><span class="token punctuation">,</span> description<span class="token punctuation">:</span> <span class="token string">'Something went wrong. Please try again or contact support.'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">err</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">/* This will be entered, if the require fails, meaning there is either no file with the name of the request path or no exported function with the given request method. */</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">404</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">'NOT_FOUND'</span><span class="token punctuation">,</span> description<span class="token punctuation">:</span> <span class="token string">'The resource you tried to access does not exist.'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> module<span class="token punctuation">.</span>exports <span class="token operator">=</span> router |
Hãy thử xem file routing sẽ trông như nào:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | <span class="token keyword">const</span> CustomError <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'../CustomError'</span><span class="token punctuation">)</span> <span class="token keyword">const</span> <span class="token function-variable function">GET</span> <span class="token operator">=</span> req <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// example for success</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> name<span class="token punctuation">:</span> <span class="token string">'Rio de Janeiro'</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">POST</span> <span class="token operator">=</span> req <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// example for unhandled error</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Some unexpected error, may also be thrown by a library or the runtime.'</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">DELETE</span> <span class="token operator">=</span> req <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// example for handled error</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">CustomError</span><span class="token punctuation">(</span><span class="token string">'CITY_NOT_FOUND'</span><span class="token punctuation">,</span> <span class="token number">404</span><span class="token punctuation">,</span> <span class="token string">'The city you are trying to delete could not be found.'</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> <span class="token function-variable function">PATCH</span> <span class="token operator">=</span> req <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// example for catching errors and using a CustomError</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token comment">// something bad happens here</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Some internal error'</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">err</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token comment">// decide what you want to do here</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">CustomError</span><span class="token punctuation">(</span> <span class="token string">'CITY_NOT_EDITABLE'</span><span class="token punctuation">,</span> <span class="token number">400</span><span class="token punctuation">,</span> <span class="token string">'The city you are trying to edit is not editable.'</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token constant">GET</span><span class="token punctuation">,</span> <span class="token constant">POST</span><span class="token punctuation">,</span> <span class="token constant">DELETE</span><span class="token punctuation">,</span> <span class="token constant">PATCH</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> |
Ở đây tôi không xử lý gì với request, tôi chỉ thêm một số kịch bản lỗi. Về cơ bản là bạn sẽ có một lỗi chưa xử lý, frontend sẽ nhận được:
1 2 3 4 5 | <span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">'GENERIC'</span><span class="token punctuation">,</span> description<span class="token punctuation">:</span> <span class="token string">'Something went wrong. Please try again or contact support.'</span> <span class="token punctuation">}</span> |
hoặc bạn sẽ throw một CustomError
:
1 2 | <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">CustomError</span><span class="token punctuation">(</span><span class="token string">'MY_CODE'</span><span class="token punctuation">,</span> <span class="token number">400</span><span class="token punctuation">,</span> <span class="token string">'Error description'</span><span class="token punctuation">)</span> |
sẽ trở thành
1 2 3 4 5 | <span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">'MY_CODE'</span><span class="token punctuation">,</span> description<span class="token punctuation">:</span> <span class="token string">'Error description'</span> <span class="token punctuation">}</span> |
III. Hiển thị lỗi đến người dùng
Bước cuối cùng là quản lý lỗi ở phía frontend. Bạn muốn xử lý lỗi ở cả frontend cũng như backend. Đầu tiên hãy xem cách chúng ta hiển thị lỗi. Như đã nối thì tôi sẽ dùng React để minh họa.
Lưu Errors trong React state
Errors và message có thể thay đổi, bởi vậy bạn cần đặt chúng trong component state. Mặc định thì error sẽ được reset để lần đầu tiên user view sẽ không thấy lỗi.
Tiếp theo, ta cần phân biệt ra những kiểu Error khác nhau. Có 3 loại:
- Global Error, ví dụ generic error ở backend hoặc user không đăng nhập …
- Lỗi cụ thể từ backend, chẳng hạn password không đúng …
- Lỗi cụ thể từ frontend, chẳng hạn lỗi validation chưa nhập input
Global Errors
Thường thì tôi lưu những lỗi này ở component cha và render static UI.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <span class="token keyword">import</span> React<span class="token punctuation">,</span> <span class="token punctuation">{</span> Component <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span> <span class="token keyword">import</span> GlobalError <span class="token keyword">from</span> <span class="token string">'./GlobalError'</span> <span class="token keyword">class</span> <span class="token class-name">Application</span> <span class="token keyword">extends</span> <span class="token class-name">Component</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_resetError <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_resetError<span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_setError <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_setError<span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>div className<span class="token operator">=</span><span class="token string">"container"</span><span class="token operator">></span> <span class="token operator"><</span>GlobalError error<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>error<span class="token punctuation">}</span> resetError<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>_resetError<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span>h1<span class="token operator">></span>Handling Errors<span class="token operator"><</span><span class="token operator">/</span>h1<span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">_resetError</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">''</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">_setError</span><span class="token punctuation">(</span>newError<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> error<span class="token punctuation">:</span> newError <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">default</span> Application |
Như bạn thấy, ta có error ở state. Chúng ta cũng có method để reset và thay đổi giá trị của error. Ta truyền giá trị và reset xuống component GlobalError
, nó sẽ hiển thị và reset error khi người dùng click dấu x:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <span class="token keyword">import</span> React<span class="token punctuation">,</span> <span class="token punctuation">{</span> Component <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span> <span class="token keyword">class</span> <span class="token class-name">GlobalError</span> <span class="token keyword">extends</span> <span class="token class-name">Component</span> <span class="token punctuation">{</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>error<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">null</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>div style<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> position<span class="token punctuation">:</span> <span class="token string">'fixed'</span><span class="token punctuation">,</span> top<span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">,</span> left<span class="token punctuation">:</span> <span class="token string">'50%'</span><span class="token punctuation">,</span> transform<span class="token punctuation">:</span> <span class="token string">'translateX(-50%)'</span><span class="token punctuation">,</span> padding<span class="token punctuation">:</span> <span class="token number">10</span><span class="token punctuation">,</span> backgroundColor<span class="token punctuation">:</span> <span class="token string">'#ffcccc'</span><span class="token punctuation">,</span> boxShadow<span class="token punctuation">:</span> <span class="token string">'0 3px 25px -10px rgba(0,0,0,0.5)'</span><span class="token punctuation">,</span> display<span class="token punctuation">:</span> <span class="token string">'flex'</span><span class="token punctuation">,</span> alignItems<span class="token punctuation">:</span> <span class="token string">'center'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token operator">></span> <span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>error<span class="token punctuation">}</span> <span class="token operator">&</span>nbsp<span class="token punctuation">;</span> <span class="token operator"><</span>i className<span class="token operator">=</span><span class="token string">"material-icons"</span> style<span class="token operator">=</span><span class="token punctuation">{</span><span class="token punctuation">{</span> cursor<span class="token punctuation">:</span> <span class="token string">'pointer'</span> <span class="token punctuation">}</span><span class="token punctuation">}</span> onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span>resetError<span class="token punctuation">}</span> <span class="token operator">></span> close <span class="token operator"><</span><span class="token operator">/</span>i<span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">default</span> GlobalError |
Ở dòng 5, ta không render vì không có error. Giờ bạn có thể sử dụng global error state bất cứ vị trí nào, chẳng hạn khi một request từ backend trả về với lỗi error: 'GENERIC'
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <span class="token keyword">import</span> React<span class="token punctuation">,</span> <span class="token punctuation">{</span> Component <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span> <span class="token keyword">import</span> axios <span class="token keyword">from</span> <span class="token string">'axios'</span> <span class="token keyword">class</span> <span class="token class-name">GenericErrorReq</span> <span class="token keyword">extends</span> <span class="token class-name">Component</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_callBackend <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_callBackend<span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>div<span class="token operator">></span> <span class="token operator"><</span>button onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>_callBackend<span class="token punctuation">}</span><span class="token operator">></span>Click me to call the backend<span class="token operator"><</span><span class="token operator">/</span>button<span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">_callBackend</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> axios <span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token string">'/api/city'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>result <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// do something with it, if the request is successful</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span>err <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">.</span>response<span class="token punctuation">.</span>data<span class="token punctuation">.</span>error <span class="token operator">===</span> <span class="token string">'GENERIC'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span><span class="token function">setError</span><span class="token punctuation">(</span>err<span class="token punctuation">.</span>response<span class="token punctuation">.</span>data<span class="token punctuation">.</span>description<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">default</span> GenericErrorReq |
Xử lý lỗi cụ thể
Tương tự global error, chúng ta có local error state trong component.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <span class="token keyword">import</span> React<span class="token punctuation">,</span> <span class="token punctuation">{</span> Component <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'react'</span> <span class="token keyword">import</span> axios <span class="token keyword">from</span> <span class="token string">'axios'</span> <span class="token keyword">import</span> InlineError <span class="token keyword">from</span> <span class="token string">'./InlineError'</span> <span class="token keyword">class</span> <span class="token class-name">SpecificErrorRequest</span> <span class="token keyword">extends</span> <span class="token class-name">Component</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>props<span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span>state <span class="token operator">=</span> <span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_callBackend <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_callBackend<span class="token punctuation">.</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">render</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>div<span class="token operator">></span> <span class="token operator"><</span>button onClick<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>_callBackend<span class="token punctuation">}</span><span class="token operator">></span>Delete your city<span class="token operator"><</span><span class="token operator">/</span>button<span class="token operator">></span> <span class="token operator"><</span>InlineError error<span class="token operator">=</span><span class="token punctuation">{</span><span class="token keyword">this</span><span class="token punctuation">.</span>state<span class="token punctuation">.</span>error<span class="token punctuation">}</span> <span class="token operator">/</span><span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">_callBackend</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> error<span class="token punctuation">:</span> <span class="token string">''</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> axios <span class="token punctuation">.</span><span class="token keyword">delete</span><span class="token punctuation">(</span><span class="token string">'/api/city'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>result <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// do something with it, if the request is successful</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token keyword">catch</span><span class="token punctuation">(</span>err <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">.</span>response<span class="token punctuation">.</span>data<span class="token punctuation">.</span>error <span class="token operator">===</span> <span class="token string">'GENERIC'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>props<span class="token punctuation">.</span><span class="token function">setError</span><span class="token punctuation">(</span>err<span class="token punctuation">.</span>response<span class="token punctuation">.</span>data<span class="token punctuation">.</span>description<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">setState</span><span class="token punctuation">(</span><span class="token punctuation">{</span> error<span class="token punctuation">:</span> err<span class="token punctuation">.</span>response<span class="token punctuation">.</span>data<span class="token punctuation">.</span>description<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">default</span> SpecificErrorRequest |
Error internationalisation với error code
Có thể bạn thắc mắc những error code như GENERIC
dùng để làm gì. Khi ứng dụng lớn dần, có thể bạn sẽ phải hỗ trợ đa ngôn ngữ, lúc đó trả về message dựa theo ngôn ngữ của user sẽ đơn giản hơn khi match với error code.
Hy vọng rằng bạn đã có thể một cách nhìn mới về việc xử lý lỗi. console.error(err)
đã trở thành quá khứ. Nó khá tiện khi debug nhưng lại mất thời gian khi build cho production, tốt nhất là nên dùng một thư viện logging.