Làm cách nào để bạn xử lý phản hồi lỗi trong javascript?

Nút. js các nhà phát triển thường thấy mình làm việc với mã hỗn loạn, không rõ ràng. Điều này rõ ràng có thể gây ra các vấn đề về năng suất và lỗi hoàn toàn. Trong bài viết này, Toptal Full-stack Developer Jay Huang sẽ giới thiệu với các bạn cách xử lý lỗi trong Node. js và chứng minh cách bạn có thể tự mình xây dựng một hệ thống xử lý lỗi mạnh mẽ

Qua

Jay Hoàng

Jay là một nhà phát triển full-stack có nhiều kinh nghiệm về khoa học máy tính. Anh ấy chuyên về JavaScript nhưng cũng thành thạo Django, RoR, GraphQL và SQL

CHIA SẺ

CHIA SẺ

Không khó để thấy rằng một số người đang gặp khó khăn trong việc xử lý lỗi, và một số thậm chí còn hoàn toàn bỏ sót. Xử lý lỗi đúng cách có nghĩa là không chỉ giảm thời gian phát triển bằng cách dễ dàng tìm ra lỗi và lỗi mà còn phát triển một cơ sở mã mạnh mẽ cho các ứng dụng quy mô lớn

Đặc biệt, nút. js đôi khi thấy mình làm việc với mã không sạch trong khi xử lý các loại lỗi khác nhau, áp dụng sai cùng một logic ở mọi nơi để xử lý chúng. Họ cứ tự hỏi “Có phải Node. js kém trong việc xử lý lỗi?” . js không tệ chút nào. Điều đó phụ thuộc vào chúng tôi các nhà phát triển. ”

Đây là một trong những giải pháp yêu thích của tôi cho điều đó

Các loại lỗi trong nút. js

Trước hết, cần hiểu rõ về các lỗi trong Node. js. Nói chung, nút. lỗi js được chia thành hai loại riêng biệt. lỗi vận hành và lỗi lập trình viên

  • Lỗi hoạt động đại diện cho các sự cố thời gian chạy có kết quả được mong đợi và cần được xử lý theo cách thích hợp. Lỗi vận hành không có nghĩa là bản thân ứng dụng có lỗi, nhưng nhà phát triển cần xử lý chúng một cách chu đáo. Ví dụ về các lỗi vận hành bao gồm "hết bộ nhớ", "đầu vào không hợp lệ cho điểm cuối API", v.v.
  • Lỗi lập trình viên đại diện cho các lỗi không mong muốn trong mã được viết kém. Ý họ là bản thân mã có một số vấn đề cần giải quyết và đã bị mã hóa sai. Một ví dụ điển hình là cố gắng đọc một thuộc tính của “không xác định. ” Để khắc phục sự cố, mã phải được thay đổi. Đó là lỗi do nhà phát triển tạo ra, không phải lỗi vận hành

Với ý nghĩ đó, bạn sẽ không gặp vấn đề gì khi phân biệt giữa hai loại lỗi này. Lỗi vận hành là một phần tự nhiên của ứng dụng và lỗi lập trình viên là lỗi do nhà phát triển gây ra. Một câu hỏi hợp lý sau đây là. “Tại sao lại hữu ích khi chia chúng thành hai loại và xử lý chúng?”

Nếu không hiểu rõ về lỗi, bạn có thể cảm thấy muốn khởi động lại ứng dụng bất cứ khi nào xảy ra lỗi. Việc khởi động lại một ứng dụng do lỗi “Không tìm thấy tệp” có hợp lý không khi hàng nghìn người dùng đang sử dụng ứng dụng này?

Nhưng còn lỗi của lập trình viên thì sao?

Đã đến lúc xử lý lỗi đúng cách

Giả sử bạn có một số kinh nghiệm với JavaScript và Node không đồng bộ. js, bạn có thể gặp phải những hạn chế khi sử dụng lệnh gọi lại để xử lý lỗi. Chúng buộc bạn phải kiểm tra tất cả các lỗi cho đến các lỗi lồng nhau, gây ra các sự cố "gọi lại địa ngục" khét tiếng khiến bạn khó theo dõi luồng mã

Sử dụng lời hứa hoặc async/await là một sự thay thế tốt cho các cuộc gọi lại. Luồng mã điển hình của async/await trông giống như sau

const doAsyncJobs = async () => {
 try {
   const result1 = await job1();
   const result2 = await job2(result1);
   const result3 = await job3(result2);
   return await job4(result3);
 } catch (error) {
   console.error(error);
 } finally {
   await anywayDoThisJob();
 }
}

Sử dụng nút. js là một phương pháp hay vì nó bao gồm thông tin trực quan và rõ ràng về các lỗi như StackTrace, mà hầu hết các nhà phát triển phụ thuộc vào để theo dõi gốc rễ của lỗi. Và các thuộc tính có ý nghĩa bổ sung như mã trạng thái HTTP và mô tả bằng cách mở rộng lớp Lỗi sẽ làm cho nó có nhiều thông tin hơn

class BaseError extends Error {
 public readonly name: string;
 public readonly httpCode: HttpStatusCode;
 public readonly isOperational: boolean;
 
 constructor(name: string, httpCode: HttpStatusCode, description: string, isOperational: boolean) {
   super(description);
   Object.setPrototypeOf(this, new.target.prototype);
 
   this.name = name;
   this.httpCode = httpCode;
   this.isOperational = isOperational;
 
   Error.captureStackTrace(this);
 }
}

//free to extend the BaseError
class APIError extends BaseError {
 constructor(name, httpCode = HttpStatusCode.INTERNAL_SERVER, isOperational = true, description = 'internal server error') {
   super(name, httpCode, isOperational, description);
 }
}

Tôi chỉ triển khai một số mã trạng thái HTTP vì mục đích đơn giản, nhưng bạn có thể bổ sung thêm sau

export enum HttpStatusCode {
 OK = 200,
 BAD_REQUEST = 400,
 NOT_FOUND = 404,
 INTERNAL_SERVER = 500,
}

Không cần mở rộng BaseError hoặc APIError, nhưng bạn có thể mở rộng nó cho các lỗi phổ biến theo nhu cầu và sở thích cá nhân của bạn

class HTTP400Error extends BaseError {
 constructor(description = 'bad request') {
   super('NOT FOUND', HttpStatusCode.BAD_REQUEST, true, description);
 }
}

Vì vậy, làm thế nào để bạn sử dụng nó?

...
const user = await User.getUserById(1);
if (user === null)
 throw new APIError(
   'NOT FOUND',
   HttpStatusCode.NOT_FOUND,
   true,
   'detailed explanation'
 );

Nút tập trung. js Xử lý lỗi

Bây giờ, chúng tôi đã sẵn sàng để xây dựng thành phần chính của Nút của chúng tôi. hệ thống xử lý lỗi js. thành phần xử lý lỗi tập trung

Thông thường, nên xây dựng một thành phần xử lý lỗi tập trung để tránh các bản sao mã có thể xảy ra khi xử lý lỗi. Thành phần xử lý lỗi chịu trách nhiệm làm cho các lỗi bị phát hiện trở nên dễ hiểu, chẳng hạn như gửi thông báo tới quản trị viên hệ thống (nếu cần), chuyển sự kiện sang dịch vụ giám sát như Sentry. io và đăng nhập chúng

Đây là quy trình công việc cơ bản để xử lý lỗi

Error handling in Node.js: basic workflow

Trong một số phần của mã, lỗi được bắt để chuyển sang phần mềm trung gian xử lý lỗi

...
try {
 userService.addNewUser(req.body).then((newUser: User) => {
   res.status(200).json(newUser);
 }).catch((error: Error) => {
   next(error)
 });
} catch (error) {
 next(error);
}
...

Phần mềm trung gian xử lý lỗi là nơi tốt để phân biệt giữa các loại lỗi và gửi chúng đến thành phần xử lý lỗi tập trung. Biết những điều cơ bản về xử lý lỗi trong Express. js chắc chắn sẽ giúp ích

app.use(async (err: Error, req: Request, res: Response, next: NextFunction) => {
 if (!errorHandler.isTrustedError(err)) {
   next(err);
 }
 await errorHandler.handleError(err);
});

Bây giờ, người ta có thể tưởng tượng thành phần tập trung sẽ trông như thế nào vì chúng ta đã sử dụng một số chức năng của nó. Hãy nhớ rằng cách thực hiện nó hoàn toàn phụ thuộc vào bạn, nhưng nó có thể giống như sau

class ErrorHandler {
 public async handleError(err: Error): Promise {
   await logger.error(
     'Error message from the centralized error-handling component',
     err,
   );
   await sendMailToAdminIfCritical();
   await sendEventsToSentry();
 }
 
 public isTrustedError(error: Error) {
   if (error instanceof BaseError) {
     return error.isOperational;
   }
   return false;
 }
}
export const errorHandler = new ErrorHandler();

Đôi khi, đầu ra của “bảng điều khiển mặc định. log” khiến việc theo dõi lỗi trở nên khó khăn. Thay vào đó, sẽ tốt hơn nhiều nếu in các lỗi theo cách được định dạng để các nhà phát triển có thể nhanh chóng hiểu các vấn đề và đảm bảo rằng chúng đã được khắc phục.

Nhìn chung, điều này sẽ tiết kiệm thời gian cho các nhà phát triển, giúp dễ dàng theo dõi các lỗi và xử lý chúng bằng cách tăng khả năng hiển thị của chúng. Đó là một quyết định đúng đắn khi sử dụng một trình ghi nhật ký có thể tùy chỉnh như winston hoặc morgan

Đây là một bộ ghi Winston tùy chỉnh

________số 8

Những gì nó cung cấp về cơ bản là ghi nhật ký ở nhiều cấp độ khác nhau theo cách được định dạng, với màu sắc rõ ràng và đăng nhập vào các phương tiện đầu ra khác nhau tùy theo môi trường thời gian chạy. Điều tốt với điều này là bạn có thể xem và truy vấn nhật ký bằng cách sử dụng API tích hợp của Winston. Hơn nữa, bạn có thể sử dụng công cụ phân tích nhật ký để phân tích các tệp nhật ký được định dạng để có thêm thông tin hữu ích về ứng dụng. Thật tuyệt phải không?

Cho đến thời điểm này, chúng tôi chủ yếu thảo luận về việc xử lý các lỗi vận hành. Làm thế nào về lỗi lập trình viên?

process.on('uncaughtException', (error: Error) => {
 errorHandler.handleError(error);
 if (!errorHandler.isTrustedError(error)) {
   process.exit(1);
 }
});

Cuối cùng nhưng không kém phần quan trọng, tôi sẽ đề cập đến việc xử lý các trường hợp ngoại lệ và từ chối lời hứa chưa được xử lý

Bạn có thể thấy mình dành nhiều thời gian để xử lý các lời hứa khi làm việc trên Node. js/Ứng dụng nhanh. Không khó để thấy các thông báo cảnh báo về các từ chối lời hứa chưa được xử lý khi bạn quên xử lý các từ chối

Các thông báo cảnh báo không có tác dụng gì nhiều ngoài việc ghi nhật ký, nhưng bạn nên sử dụng một phương án dự phòng hợp lý và đăng ký vào

class BaseError extends Error {
 public readonly name: string;
 public readonly httpCode: HttpStatusCode;
 public readonly isOperational: boolean;
 
 constructor(name: string, httpCode: HttpStatusCode, description: string, isOperational: boolean) {
   super(description);
   Object.setPrototypeOf(this, new.target.prototype);
 
   this.name = name;
   this.httpCode = httpCode;
   this.isOperational = isOperational;
 
   Error.captureStackTrace(this);
 }
}

//free to extend the BaseError
class APIError extends BaseError {
 constructor(name, httpCode = HttpStatusCode.INTERNAL_SERVER, isOperational = true, description = 'internal server error') {
   super(name, httpCode, isOperational, description);
 }
}
1

Luồng xử lý lỗi điển hình có thể giống như sau

class BaseError extends Error {
 public readonly name: string;
 public readonly httpCode: HttpStatusCode;
 public readonly isOperational: boolean;
 
 constructor(name: string, httpCode: HttpStatusCode, description: string, isOperational: boolean) {
   super(description);
   Object.setPrototypeOf(this, new.target.prototype);
 
   this.name = name;
   this.httpCode = httpCode;
   this.isOperational = isOperational;
 
   Error.captureStackTrace(this);
 }
}

//free to extend the BaseError
class APIError extends BaseError {
 constructor(name, httpCode = HttpStatusCode.INTERNAL_SERVER, isOperational = true, description = 'internal server error') {
   super(name, httpCode, isOperational, description);
 }
}
0

kết thúc

Khi tất cả đã được nói và làm xong, bạn nên nhận ra rằng xử lý lỗi không phải là một tùy chọn bổ sung mà là một phần thiết yếu của ứng dụng, cả trong giai đoạn phát triển và sản xuất

Chiến lược xử lý lỗi trong một thành phần duy nhất trong Node. js sẽ đảm bảo các nhà phát triển tiết kiệm thời gian quý báu và viết mã rõ ràng và có thể bảo trì bằng cách tránh sao chép mã và thiếu ngữ cảnh lỗi

Tôi hy vọng bạn thích đọc bài viết này và thấy quy trình triển khai và xử lý lỗi đã thảo luận hữu ích cho việc xây dựng một cơ sở mã mạnh mẽ trong Node. js


Đọc thêm trên Blog Kỹ thuật Toptal

  • Sử dụng Express. js Các tuyến để xử lý lỗi dựa trên lời hứa

Hiểu những điều cơ bản

Tại sao xử lý lỗi lại quan trọng?

Xử lý lỗi đúng cách giúp ứng dụng trở nên mạnh mẽ, mang lại trải nghiệm người dùng vượt trội và năng suất được cải thiện

Các chiến lược hoặc kỹ thuật được sử dụng để xử lý lỗi là gì?

Sử dụng lời hứa hoặc không đồng bộ/chờ đợi, xử lý lỗi trong một thành phần tập trung, xử lý các ngoại lệ chưa được phát hiện

Tại sao chúng ta cần xử lý lỗi?

Xử lý lỗi là một phần bắt buộc phải có của tất cả các ứng dụng. Nó ngăn các ứng dụng dễ bị lỗi và tiết kiệm thời gian phát triển quý giá

Làm thế nào để bạn xử lý các trường hợp ngoại lệ không được kiểm soát?

Bằng cách đăng ký quá trình. on('unhandledRejection'), xử lý. on('uncaughtException')

là nút. js kém trong việc xử lý lỗi?

Không có nó không phải là. Điều đó phụ thuộc vào các nhà phát triển và cách họ chọn để xử lý chúng

thẻ

LỗiXử LýNút. js

Người làm việc tự do? Tìm công việc tiếp theo của bạn.

Nút. Việc làm lập trình viên js

Xem thông tin đầy đủ

Jay Hoàng

Người phát triển phần mềm

Thông tin về các Tác giả

Jay là một nhà phát triển full-stack hàng đầu với nhiều năm kinh nghiệm trong lĩnh vực khoa học máy tính. Anh ấy chuyên về JavaScript cũng như các framework và thư viện của nó, cung cấp các giải pháp sáng tạo, tinh vi, hiệu quả nhưng rõ ràng bằng cách sử dụng React, Angular, Vue. js và nút. js. Jay cũng thành thạo cơ sở dữ liệu Django, Ruby on Rails, GraphQL, NoSQL/SQL với kiến ​​thức chuyên sâu về các phương pháp hay nhất cho các ứng dụng web hiện đại

Thuê Jay

Bình luận

Marcos Henrique da Silva

bài báo rất hay. Giải thích rõ ràng và chi tiết tuyệt vời, cảm ơn vì đã chia sẻ kiến ​​thức của bạn

Gonzalo De Benito

Nice article! All the concepts you explain are very interesting and I already use it in my work 🙂 The BaseError saves you lot of time 😎 I use a asyncCatch middleware that save me up all the try catch code in my controllers: export const asyncCatch = (fn: RequestHandler) => async ( req: Request, res: Response, next: NextFunction ): Promise => { try { await fn(req, res, next); } catch (error) { next(error); } }; // Sample controller code: export const getUsers: RequestHandler = asyncCatch( async (req, res, next) => { // Your code })

Gerardo Lima

này, Jay, tôi thích bài viết của bạn; . Xin lưu ý, ở khối thứ 6, bạn kết hợp gọi lại với async/await

Bobby Donchev

Operational errors don’t mean the application itself has bugs, but developers need to handle them thoughtfully. Examples of operational errors include “out of memory”
-> show me code where developers handle this "thoughtfully" :) Also your code does not make sense, why would you have two catch? try { userService.addNewUser(req.body).then((newUser: User) => { res.status(200).json(newUser); }).catch((error: Error) => { next(error) }); } catch (error) { next(error); } And this is not graceful handling of an error :) process.on('uncaughtException', (error: Error) => { errorHandler.handleError(error); if (!errorHandler.isTrustedError(error)) { process.exit(1); } });

zniesmaczony

Phiên bản xử lý lỗi của tôi. const errorHandling = (lỗi. Lỗi, yêu cầu. yêu cầu, đáp lại. Phản hồi, tiếp theo. Chức năng tiếp theo) => { if (res. tiêu đề đã gửi) { trả về tiếp theo (lỗi); . trạng thái(500). json({lỗi. 'Lỗi máy chủ nội bộ. ', }); . handleError(lỗi);

Przemek Nowicki

Cảm ơn Jay vì bài viết này, gần đây tôi đã triển khai một số ý tưởng xử lý lỗi của bạn trong ứng dụng web express đơn giản của mình. https. //github. com/przemek-nowicki/node-express-template. ts Mọi phản hồi đều được chào đón

Jahan

Hi Jay, You didn't write any code for isDevEnvironment(). Could you please add it?

Garen Vartanian

Dòng này trong lớp lỗi tùy chỉnh không có ý nghĩa.

class BaseError extends Error {
 public readonly name: string;
 public readonly httpCode: HttpStatusCode;
 public readonly isOperational: boolean;
 
 constructor(name: string, httpCode: HttpStatusCode, description: string, isOperational: boolean) {
   super(description);
   Object.setPrototypeOf(this, new.target.prototype);
 
   this.name = name;
   this.httpCode = httpCode;
   this.isOperational = isOperational;
 
   Error.captureStackTrace(this);
 }
}

//free to extend the BaseError
class APIError extends BaseError {
 constructor(name, httpCode = HttpStatusCode.INTERNAL_SERVER, isOperational = true, description = 'internal server error') {
   super(name, httpCode, isOperational, description);
 }
}
2 Đầu tiên, vì cú pháp "Lớp" với phần mở rộng đặt chuỗi nguyên mẫu cho bạn. Thứ hai, cuối cùng bạn sẽ tự thiết lập nguyên mẫu của riêng mình. Nếu bạn muốn thiết lập Error proto trên hàm tạo hàm (oldschool JS), bạn sẽ
class BaseError extends Error {
 public readonly name: string;
 public readonly httpCode: HttpStatusCode;
 public readonly isOperational: boolean;
 
 constructor(name: string, httpCode: HttpStatusCode, description: string, isOperational: boolean) {
   super(description);
   Object.setPrototypeOf(this, new.target.prototype);
 
   this.name = name;
   this.httpCode = httpCode;
   this.isOperational = isOperational;
 
   Error.captureStackTrace(this);
 }
}

//free to extend the BaseError
class APIError extends BaseError {
 constructor(name, httpCode = HttpStatusCode.INTERNAL_SERVER, isOperational = true, description = 'internal server error') {
   super(name, httpCode, isOperational, description);
 }
}
3 Thứ ba, bạn đang gọi một phương thức tĩnh trên chính lớp Error khi gọi Error. captureStackTrace, vì vậy trên thực tế, bạn sẽ cần viết
class BaseError extends Error {
 public readonly name: string;
 public readonly httpCode: HttpStatusCode;
 public readonly isOperational: boolean;
 
 constructor(name: string, httpCode: HttpStatusCode, description: string, isOperational: boolean) {
   super(description);
   Object.setPrototypeOf(this, new.target.prototype);
 
   this.name = name;
   this.httpCode = httpCode;
   this.isOperational = isOperational;
 
   Error.captureStackTrace(this);
 }
}

//free to extend the BaseError
class APIError extends BaseError {
 constructor(name, httpCode = HttpStatusCode.INTERNAL_SERVER, isOperational = true, description = 'internal server error') {
   super(name, httpCode, isOperational, description);
 }
}
4 để nắm bắt các phương thức tĩnh. Nhưng một lần nữa, tất cả đều không cần thiết vì bạn đang sử dụng cú pháp mở rộng Class. Nếu không, bài viết tốt đẹp

Làm thế nào là xử lý lỗi được thực hiện trong JavaScript giải thích với ví dụ?

Câu lệnh try định nghĩa một khối mã để chạy (để thử). Câu lệnh bắt định nghĩa một khối mã để xử lý bất kỳ lỗi nào. Câu lệnh cuối cùng xác định một khối mã để chạy bất kể kết quả như thế nào. Câu lệnh ném xác định lỗi tùy chỉnh

Làm cách nào để trả về thông báo lỗi trong JavaScript?

Trong JavaScript thuộc tính thông báo lỗi được sử dụng để đặt hoặc trả về thông báo lỗi. Giá trị trả về. Nó trả về một chuỗi, đại diện cho các chi tiết của lỗi. Các mã ví dụ khác cho thuộc tính trên như sau. ví dụ 1. Ví dụ này không chứa bất kỳ lỗi nào nên nó không hiển thị thông báo lỗi.

Chúng ta có thể sử dụng phương pháp nào trong JavaScript để thông báo lỗi?

Thuộc tính message kết hợp với thuộc tính name được sử dụng bởi Lỗi. nguyên mẫu. Phương thức toString() để tạo chuỗi biểu diễn Lỗi.