Xử lý lỗi trong JavaScript: Hướng dẫn
Một dự án mẫu đầy đủ có sẵn trên github .
I. Lỗi JavaScript và xử lý chung
throw new Error('something went wrong')
- sẽ tạo một trường hợp Lỗi trong JavaScript và ngừng thực thi tập lệnh của bạn, trừ khi bạn làm điều gì đó với Lỗi. Khi bạn bắt đầu sự nghiệp của mình với tư cách là nhà phát triển JavaScript, bạn sẽ không tự làm điều đó, nhưng thay vào đó bạn đã thấy nó từ các thư viện khác (hoặc thời gian chạy) thực hiện nó, ví dụ `Tham chiếu: fs không được định nghĩa` hoặc tương tự.Đối tượng lỗi
Đối tượng Lỗi có hai thuộc tính được xây dựng để chúng ta sử dụng. Người đầu tiên là thông điệp, đó là những gì bạn vượt qua như là đối số cho các nhà xây dựng Lỗi, ví dụ
new Error('This is the message')
. Bạn có thể truy cập thư thông qua thuộc message
tính:const myError = Lỗi mới ('vui lòng cải thiện mã của bạn')
console.log (myError.message) // hãy cải thiện mã của bạn
Thứ hai, rất quan trọng là dấu vết ngăn xếp lỗi. Bạn có thể truy cập nó thông qua thuộc tính `stack`. Các ngăn xếp lỗi sẽ cung cấp cho bạn một lịch sử (gọi stack) của những gì các tập tin đã được 'chịu trách nhiệm' gây ra lỗi đó. Ngăn xếp cũng bao gồm thông báo ở trên cùng và sau đó theo sau là ngăn xếp thực tế bắt đầu với điểm bị cô lập / bị lỗi gần đây nhất và chuyển xuống tệp 'chịu trách nhiệm' bên ngoài nhất:
Lỗi: vui lòng cải thiện mã của bạn tại Object. <Anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79) tại Module._compile (internal / modules / cjs /loader.js:689:30) tại Object.Module._extensions..js (nội bộ / modules / cjs / loader.js: 700: 10) tại Module.load (internal / modules / cjs / loader.js: 599: 32) tại tryModuleLoad (nội bộ / modules / cjs / loader.js: 538: 12) tại Function.Module._load (internal / modules / cjs / loader.js: 530: 3) tại Function.Module.runMain (internal / modules /cjs/loader.js:742:12) khi khởi động (internal / bootstrap / node.js: 266: 19) tại bootstrapNodeJSCore (internal / bootstrap / node.js: 596: 3)
Ném và xử lý lỗi
Bây giờ trường hợp Lỗi một mình không gây ra bất cứ điều gì. Ví dụ:
new Error('...')
không làm gì cả. Khi lỗi được throw
n, nó sẽ thú vị hơn một chút. Sau đó, như đã nói, kịch bản của bạn sẽ ngừng thực thi, trừ khi bạn bằng cách nào đó xử lý nó trong quá trình của bạn. Hãy nhớ rằng, nó không quan trọng nếu bạn throw
một lỗi bằng tay, nó ném bởi một thư viện, hoặc thậm chí bản thân thời gian chạy (nút hoặc trình duyệt). Chúng ta hãy xem cách chúng ta có thể xử lý những lỗi đó trong các 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ị lãng quên để xử lý lỗi - nó được sử dụng ngày nay nhiều hơn một lần nữa, nhờ vào async / await, xem bên dưới. Điều này có thể được sử dụng để bắt bất kỳ loại lỗi đồng bộ nào . Thí dụ:
Nếu chúng ta không bọc
console.log(b)
trong một khối try… catch, việc thực thi script sẽ dừng lại.... cuối cùng
Đôi khi nó là cần thiết để thực thi mã trong cả hai trường hợp, cho dù có một lỗi hay không. Bạn có thể sử dụng khối thứ ba, tùy chọn
finally
cho điều đó. Thông thường, nó cũng giống như chỉ có một dòng sau câu lệnh try… catch, nhưng đôi khi nó có thể hữu ích.Nhập không đồng bộ - Gọi lại
Không đồng bộ, một chủ đề bạn luôn phải cân nhắc khi làm việc với JavaScript. Khi bạn có một hàm không đồng bộ và một lỗi xảy ra bên trong hàm đó, tập lệnh của bạn sẽ tiếp tục thực hiện xong, vì vậy sẽ không có bất kỳ lỗi nào ngay lập tức. Khi xử lý các hàm không đồng bộ với hàm callbacks (không được khuyến nghị bằng cách này), bạn thường nhận được hai tham số trong hàm gọi lại của bạn, trông giống như sau:
Nếu có Lỗi,
err
thông số sẽ bằng với Lỗi đó. Nếu không, tham số sẽ là `undefined` hoặc` null`. Điều quan trọng là trả về một thứ gì đó trong if(err)
block, hoặc để bọc lệnh khác của bạn trong một else
block, nếu không bạn có thể gặp lỗi khác, ví dụ như result
có thể không xác định và bạn cố gắng truy cập result.data
hoặc tương tự.Không đồng bộ - Hứa hẹn
Một cách tốt hơn để đối phó với sự không đồng bộ là sử dụng lời hứa. Ở đây, ngoài việc có mã dễ đọc hơn, chúng tôi cũng đã cải thiện việc xử lý lỗi. Chúng tôi không còn cần phải quan tâm nhiều đến việc bắt lỗi chính xác, miễn là chúng tôi có một
catch
khối. Khi chuỗi lời hứa, một catch
khối sẽ bắt tất cả các lỗi kể từ khi thực hiện lời hứa hoặc khối catch cuối cùng. Lưu ý rằng lời hứa không có catch
block sẽ không chấm dứt tập lệnh, nhưng sẽ cung cấp cho bạn một thông báo ít đọc hơn như(node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong
(node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */
Vì vậy, luôn luôn thêm một khối catch vào lời hứa của bạn. Chúng ta hãy có một cái nhìn:
cố gắng ... bắt - một lần nữa
Với sự ra đời của async / await trong JavaScript, chúng ta quay trở lại cách xử lý lỗi ban đầu, với try… catch… cuối cùng, điều này khiến chúng dễ dàng xử lý:
Vì nó cũng giống như cách chúng ta sử dụng để xử lý các lỗi đồng bộ 'bình thường', nên việc có các câu lệnh bắt phạm vi rộng hơn dễ dàng hơn - nếu muốn.
II. Tạo và xử lý lỗi trong máy chủ
Bây giờ chúng ta có các công cụ để xử lý lỗi, hãy xem những gì chúng ta có thể thực sự làm với chúng trong các tình huống thực tế. Tạo lỗi và xử lý chúng một cách chính xác trong chương trình phụ trợ là một phần quan trọng trong ứng dụng của bạn. Có nhiều cách tiếp cận khác nhau về cách xử lý lỗi. Tôi sẽ chỉ cho bạn một cách tiếp cận với một hàm tạo Lỗi và MãLỗi tùy chỉnh , mà chúng ta có thể dễ dàng chuyển đến giao diện người dùng hoặc bất kỳ người tiêu dùng API nào. Cách bạn cấu trúc chi tiết chương trình phụ trợ của mình không quan trọng đến mức nào, ý tưởng vẫn giữ nguyên.
Chúng tôi sẽ sử dụng Express.js làm khung định tuyến. Hãy suy nghĩ về cấu trúc mà chúng ta muốn xử lý Lỗi hiệu quả nhất. Chúng tôi muốn:
- Xử lý lỗi chung, một số loại dự phòng, về cơ bản chỉ nói: 'Đã xảy ra sự cố, vui lòng thử lại hoặc liên hệ với chúng tôi'. Điều này không đặc biệt thông minh, nhưng ít nhất thông báo cho người dùng rằng có điều gì đó sai - thay vì tải vô hạn hoặc tương tự.
- Xử lý lỗi cụ thể để cung cấp cho người dùng thông tin chi tiết về những gì là sai và cách khắc phục, ví dụ như thiếu một số thông tin, mục nhập đã tồn tại trong cơ sở dữ liệu, v.v.
Xây dựng một hàm tạo lỗi tùy chỉnh
Chúng tôi sẽ sử dụng hàm dựng Lỗi hiện có và mở rộng nó. Thừa kế trong JavaScript là một điều nguy hiểm để làm, nhưng trong trường hợp này, tôi đã trải nghiệm nó rất hữu ích. Tại sao chúng ta cần nó? Chúng tôi vẫn muốn có dấu vết ngăn xếp để chúng tôi có trải nghiệm gỡ lỗi tốt đẹp. Việc mở rộng hàm tạo Lỗi JavaScript gốc cho chúng ta dấu vết ngăn xếp miễn phí. Điều duy nhất chúng tôi đang thêm là
code
, mà sau này chúng tôi có thể truy cập err.code
, cũng như trạng thái (mã trạng thái http) để chuyển sang giao diện người dùng.Cách xử lý định tuyến
Với Lỗi tùy chỉnh của chúng tôi sẵn sàng sử dụng, chúng tôi cần thiết lập cấu trúc định tuyến. Như tôi đã chỉ ra, chúng tôi muốn một điểm duy nhất của sự thật để xử lý lỗi, có nghĩa là đối với mọi tuyến đường, chúng tôi muốn có cùng một hành vi xử lý lỗi. Theo mặc định, Express không thực sự hỗ trợ điều đó, vì tất cả các route đều được đóng gói.
Để giải quyết vấn đề đó, chúng ta có thể thực hiện một trình xử lý định tuyến và xác định logic tuyến đường thực tế của chúng ta như các hàm bình thường. Bằng cách đó, trong trường hợp hàm tuyến (hoặc bất kỳ hàm nào bên trong) ném một lỗi, nó sẽ được trả lại cho trình xử lý tuyến đường, sau đó có thể truyền nó tới giao diện người dùng. Bất cứ khi nào xảy ra lỗi trong phần cuối, chúng tôi muốn chuyển trả lời cho giao diện người dùng - giả sử API JSON - theo định dạng sau:
{ error: 'SOME_ERROR_CODE', mô tả: 'Đã xảy ra sự cố. Vui lòng thử lại hoặc liên hệ với bộ phận hỗ trợ. ' }
Chuẩn bị cho mình để bị choáng ngợp. Học sinh của tôi luôn giận tôi khi tôi nói:
Sẽ ổn nếu bạn không hiểu mọi thứ ngay từ cái nhìn đầu tiên. Chỉ cần sử dụng nó và sau một thời gian bạn sẽ tìm hiểu lý do tại sao nó có ý nghĩa.
Một lưu ý phụ, đây cũng được gọi là học tập từ trên xuống, mà tôi là một người hâm mộ lớn.
Đây là những gì mà trình xử lý tuyến đường chính nó trông giống như:
Tôi hy vọng bạn có thể đọc các ý kiến trong mã, tôi nghĩ điều này có ý nghĩa hơn là giải thích nó ở đây. Bây giờ chúng ta hãy xem xét một tệp tuyến đường thực tế trông như thế nào:
Trong những ví dụ này, tôi không làm bất cứ điều gì với yêu cầu thực tế, tôi chỉ giả mạo các tình huống lỗi khác nhau. Vì vậy, ví dụ,
GET /city
sẽ kết thúc trong dòng 3, POST /city
sẽ kết thúc trong dòng 8 và như vậy. Điều này cũng làm việc với params truy vấn, ví dụ GET /city?startsWith=R
. Về bản chất, Bạn sẽ có Lỗi không được xử lý, giao diện người dùng sẽ nhận được{ error: 'GENERIC', mô tả: 'Đã xảy ra sự cố. Vui lòng thử lại hoặc liên hệ với bộ phận hỗ trợ. ' }
hoặc bạn sẽ ném một `CustomError` theo cách thủ công, ví dụ
ném CustomError mới ('MY_CODE', 400, 'Mô tả lỗi')
biến thành
{ error: 'MY_CODE', mô tả: 'Mô tả lỗi' }
Bây giờ chúng ta có thiết lập phụ trợ tuyệt đẹp này, chúng tôi không còn có nhật ký lỗi bị rò rỉ cho giao diện người dùng và sẽ luôn trả về thông tin có thể sử dụng được về những gì đã xảy ra.
Hãy chắc chắn rằng bạn có một cái nhìn tại repo đầy đủ trên github . Hãy sử dụng nó cho bất kỳ dự án của bạn và sửa đổi nó để phù hợp với nhu cầu của bạn!
III. Hiển thị lỗi cho người dùng
Bước tiếp theo và cuối cùng là quản lý lỗi trong giao diện người dùng. Ở đây, bạn muốn xử lý các lỗi được tạo bởi logic giao diện người dùng của bạn với các công cụ được mô tả trong phần đầu tiên. Tuy nhiên, lỗi từ chương trình phụ trợ cũng phải được hiển thị. Trước tiên, hãy xem cách chúng tôi có thể hiển thị lỗi. Như đã đề cập trước đây, chúng tôi sẽ sử dụng React trong hướng dẫn của chúng tôi.
Lưu lỗi trong trạng thái React
Cũng giống như các dữ liệu khác, các thông báo Lỗi và Lỗi có thể thay đổi, do đó bạn muốn đặt chúng vào trạng thái các thành phần của bạn. Theo mặc định và trên gắn kết, bạn muốn đặt lại lỗi, để khi người dùng lần đầu tiên nhìn thấy trang, không có Lỗi nào hiển thị.
Điều tiếp theo chúng ta phải làm rõ là các loại lỗi khác nhau với biểu diễn trực quan phù hợp. Cũng giống như trong phần phụ trợ, có 3 loại:
- Lỗi toàn cầu, ví dụ: một trong các lỗi chung của chúng tôi trở lại từ chương trình phụ trợ hoặc người dùng không đăng nhập, v.v.
- Các lỗi cụ thể đến từ chương trình phụ trợ, ví dụ: người dùng gửi thông tin xác thực đăng nhập của mình tới chương trình phụ trợ. Phần phụ trợ trả lời rằng mật khẩu không khớp. Điều này không thể được xác minh bởi lối vào, vì vậy nó phải đến từ phần phụ trợ.
- Các lỗi cụ thể do chính giao diện người dùng gây ra, ví dụ: việc xác thực đầu vào e-mail không thành công.
2. và 3. là rất giống nhau và có thể được xử lý trong cùng một tiểu bang (nếu muốn), nhưng có một nguồn gốc khác nhau. Chúng ta sẽ thấy trong đoạn mã phát ra như thế nào.
Tôi sẽ sử dụng thực hiện trạng thái gốc của React, nhưng bạn cũng có thể sử dụng các hệ thống quản lý trạng thái như MobX hoặc Redux.
Lỗi toàn cầu
Thông thường, tôi lưu các lỗi này trong thành phần trạng thái ngoài cùng và hiển thị một phần tử giao diện người dùng tĩnh, đây có thể là biểu ngữ màu đỏ ở đầu màn hình của bạn, một phương thức hay bất kỳ thứ gì khác, việc triển khai thiết kế tùy thuộc vào bạn.
Hãy xem mã:
Như bạn có thể thấy, chúng tôi có Lỗi trong trạng thái của chúng tôi trong
Application.js
. Chúng tôi cũng có phương pháp để đặt lại và thay đổi giá trị của lỗi. Chúng tôi chuyển giá trị và phương thức đặt lại xuống thành phần `GlobalError`, sẽ xử lý nó và đặt lại khi nhấp vào 'x'. Hãy xem làm thế nào- GlobalError
component trông giống như:
Như bạn có thể thấy trong dòng 5, chúng tôi không hiển thị bất cứ điều gì, nếu không có Lỗi. Điều này ngăn cản chúng tôi không có hộp trống màu đỏ trên trang của chúng tôi mọi lúc. Tất nhiên bạn có thể thay đổi diện mạo và hành vi của thành phần này. Ví dụ, bạn có thể thay thế 'x' bằng cách
Timeout
đặt lại trạng thái lỗi sau một vài giây.
Bây giờ, bạn đã sẵn sàng sử dụng trạng thái lỗi toàn cục này ở bất cứ nơi nào bạn muốn, chỉ cần chuyển xuống
_setError
từ Application.js
và bạn có thể đặt Lỗi toàn cục, ví dụ khi yêu cầu từ chương trình phụ trợ trở lại với trường error: 'GENERIC'
. Thí dụ:
Nếu bạn lười biếng, bạn có thể dừng lại ở đây. Ngay cả khi bạn có lỗi cụ thể, bạn luôn có thể thay đổi trạng thái Lỗi toàn cục và hiển thị hộp Lỗi ở đầu trang. Tuy nhiên, tôi sẽ chỉ cho bạn cách xử lý và hiển thị các lỗi cụ thể. Tại sao? Đầu tiên, đây là hướng dẫn xác định về xử lý lỗi, vì vậy tôi không thể dừng lại ở đây. Thứ hai, những người UX có thể sẽ bung ra nếu bạn chỉ hiển thị tất cả các lỗi trên toàn cầu.
Xử lý lỗi yêu cầu cụ thể
Tương tự như các lỗi toàn cục, chúng ta cũng có thể có trạng thái Lỗi cục bộ bên trong các thành phần khác. Thủ tục là như nhau:
Một điều cần nhớ là việc xóa Lỗi thường có kích hoạt khác. Nó sẽ không có ý nghĩa để có một 'x' để loại bỏ các lỗi. Ở đây, nó sẽ có ý nghĩa hơn để xóa Lỗi khi thực hiện một yêu cầu mới. Bạn cũng có thể xóa Lỗi khi người dùng thực hiện thay đổi, ví dụ: khi giá trị đầu vào bị thay đổi.
Lỗi gốc của giao diện người dùng
Như đã đề cập trước đó, các lỗi này có thể được xử lý theo cùng một cách (trạng thái) như các lỗi cụ thể đến từ chương trình phụ trợ. Hãy sử dụng ví dụ với trường nhập lần này và chỉ cho phép người dùng xóa thành phố khi thực sự cung cấp đầu vào:
Lỗi quốc tế hóa bằng cách sử dụng mã Lỗi
Có lẽ bạn đã tự hỏi tại sao chúng tôi có các mã lỗi này, ví dụ
GENERIC
, chúng tôi chỉ hiển thị mô tả lỗi được chuyển từ phần cuối. Bây giờ, ngay sau khi ứng dụng của bạn phát triển, bạn hy vọng sẽ chinh phục các thị trường mới và tại một số điểm phải đối mặt với vấn đề phải hỗ trợ nhiều ngôn ngữ. Nếu bạn đang ở thời điểm đó, bạn có thể sử dụng mã Lỗi được đề cập để hiển thị chú thích chính xác bằng ngôn ngữ của người dùng.
Tôi hy vọng bạn đã đạt được một số thông tin chi tiết về cách xử lý lỗi. Nhanh chóng gõ và chỉ là một cách nhanh chóng bị lãng quên
console.error(err)
nên là một điều của quá khứ bây giờ. Nó là điều cần thiết để sử dụng để gỡ lỗi, nhưng nó không nên kết thúc trong xây dựng sản xuất của bạn. Để ngăn chặn điều đó, tôi khuyên bạn nên sử dụng một thư viện đăng nhập, tôi đã sử dụng loglevel trong quá khứ và tôi khá hài lòng với nó.