Xử lý ngoại lệ thực hành tốt nhất của Node.js


755

Tôi mới bắt đầu dùng thử node.js vài ngày trước. Tôi đã nhận ra rằng Node bị chấm dứt bất cứ khi nào tôi có một ngoại lệ chưa được xử lý trong chương trình của mình. Điều này khác với bộ chứa máy chủ bình thường mà tôi đã tiếp xúc khi chỉ có Chủ đề Công nhân chết khi xảy ra trường hợp ngoại lệ chưa xử lý và bộ chứa vẫn có thể nhận được yêu cầu. Điều này đặt ra một số câu hỏi:

  • process.on('uncaughtException')cách hiệu quả duy nhất để bảo vệ chống lại nó?
  • Sẽ process.on('uncaughtException')bắt được ngoại lệ chưa được xử lý trong quá trình thực hiện các quy trình không đồng bộ?
  • Có một mô-đun đã được xây dựng (chẳng hạn như gửi email hoặc viết vào một tệp) mà tôi có thể tận dụng trong trường hợp ngoại lệ chưa được phát hiện?

Tôi sẽ đánh giá cao bất kỳ con trỏ / bài viết nào sẽ chỉ cho tôi các cách thực hành tốt nhất phổ biến để xử lý các trường hợp ngoại lệ chưa được phát hiện trong node.js


11
trường hợp ngoại lệ không nên xảy ra. Nếu họ sử dụng một chương trình khởi động lại toàn bộ ứng dụng của bạn khi nó bị sập (gật đầu, mãi mãi, người giám sát)
Raynos

116
Các trường hợp ngoại lệ không được phép luôn có thể xảy ra trừ khi bạn đặt mọi đoạn mã không đồng bộ của mình vào bên trong try .. catchvà kiểm tra xem điều này cũng được thực hiện cho tất cả các lib của bạn
Dan

13
+1 Dan Lúc đầu tôi nghĩ tất cả các lib của bạn là một chút cường điệu, vì bạn "chỉ" cần phải bọc tất cả "điểm nhập chủ đề" của mình trong mã trong các lần thử / bắt. Nhưng suy nghĩ về nó một cách cẩn thận hơn, bất kỳ lib có thể có một setTimeouthoặc setIntervalhoặc một cái gì đó thuộc loại mà chôn đâu đó sâu mà không thể được đánh bắt bằng cách mã của bạn.
Eugene Beresovsky

8
@EugeneBeresovksy Dan đã đúng nhưng điều đó không thay đổi thực tế là khi không có ngoại lệ xảy ra, tùy chọn an toàn duy nhất là khởi động lại ứng dụng. Nói cách khác, ứng dụng của bạn đã bị sập và bạn không thể làm gì hoặc nên làm gì về nó. Nếu bạn muốn thực hiện một cái gì đó mang tính xây dựng, hãy thực hiện tính năng miền v0.8 mới và vẫn đang thử nghiệm để bạn có thể ghi nhật ký sự cố và gửi phản hồi 5xx cho khách hàng của mình.
Ostergaard

1
@Dan Ngay cả việc bao gồm tất cả các chức năng gọi lại trong thử .. bắt không đảm bảo bắt lỗi. Trong trường hợp một mô-đun được yêu cầu sử dụng nhị phân của chính nó, chúng có thể bị sập một cách vô duyên. Tôi đã xảy ra điều này với nút ph Phantomjs, không thực hiện được các lỗi không thể bắt được (trừ khi tôi phải thực hiện một số loại kiểm tra quy trình trên các nhị phân bắt buộc, nhưng tôi không bao giờ theo đuổi điều đó).
Trindaz

Câu trả lời:


737

Cập nhật: Joyent hiện có hướng dẫn riêng . Các thông tin sau đây là một bản tóm tắt:

Lỗi "ném" an toàn

Lý tưởng nhất là chúng ta muốn tránh các lỗi chưa được phát hiện càng nhiều càng tốt, vì vậy, thay vì ném lỗi theo nghĩa đen, thay vào đó chúng ta có thể "ném" lỗi một cách an toàn bằng một trong các phương pháp sau tùy thuộc vào kiến ​​trúc mã của chúng tôi:

  • Đối với mã đồng bộ, nếu xảy ra lỗi, hãy trả về lỗi:

    // Define divider as a syncrhonous function
    var divideSync = function(x,y) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by returning it
            return new Error("Can't divide by zero")
        }
        else {
            // no error occured, continue on
            return x/y
        }
    }
    
    // Divide 4/2
    var result = divideSync(4,2)
    // did an error occur?
    if ( result instanceof Error ) {
        // handle the error safely
        console.log('4/2=err', result)
    }
    else {
        // no error occured, continue on
        console.log('4/2='+result)
    }
    
    // Divide 4/0
    result = divideSync(4,0)
    // did an error occur?
    if ( result instanceof Error ) {
        // handle the error safely
        console.log('4/0=err', result)
    }
    else {
        // no error occured, continue on
        console.log('4/0='+result)
    }
  • Đối với mã dựa trên cuộc gọi lại (tức là không đồng bộ), đối số đầu tiên của cuộc gọi lại là err, nếu xảy ra lỗi errlà lỗi, nếu lỗi không xảy ra thì đó errlà lỗi null. Bất kỳ đối số khác theo sau errđối số:

    var divide = function(x,y,next) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by calling the completion callback
            // with the first argument being the error
            next(new Error("Can't divide by zero"))
        }
        else {
            // no error occured, continue on
            next(null, x/y)
        }
    }
    
    divide(4,2,function(err,result){
        // did an error occur?
        if ( err ) {
            // handle the error safely
            console.log('4/2=err', err)
        }
        else {
            // no error occured, continue on
            console.log('4/2='+result)
        }
    })
    
    divide(4,0,function(err,result){
        // did an error occur?
        if ( err ) {
            // handle the error safely
            console.log('4/0=err', err)
        }
        else {
            // no error occured, continue on
            console.log('4/0='+result)
        }
    })
  • Đối với mã sự kiện , nơi lỗi có thể xảy ra ở bất cứ đâu, thay vì ném lỗi, errorthay vào đó hãy khởi động sự kiện :

    // Definite our Divider Event Emitter
    var events = require('events')
    var Divider = function(){
        events.EventEmitter.call(this)
    }
    require('util').inherits(Divider, events.EventEmitter)
    
    // Add the divide function
    Divider.prototype.divide = function(x,y){
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by emitting it
            var err = new Error("Can't divide by zero")
            this.emit('error', err)
        }
        else {
            // no error occured, continue on
            this.emit('divided', x, y, x/y)
        }
    
        // Chain
        return this;
    }
    
    // Create our divider and listen for errors
    var divider = new Divider()
    divider.on('error', function(err){
        // handle the error safely
        console.log(err)
    })
    divider.on('divided', function(x,y,result){
        console.log(x+'/'+y+'='+result)
    })
    
    // Divide
    divider.divide(4,2).divide(4,0)

Lỗi "bắt" an toàn

Tuy nhiên, đôi khi, vẫn có thể có mã gây ra lỗi ở đâu đó có thể dẫn đến ngoại lệ chưa được xử lý và sự cố có thể xảy ra đối với ứng dụng của chúng tôi nếu chúng tôi không nắm bắt được nó một cách an toàn. Tùy thuộc vào kiến ​​trúc mã của chúng tôi, chúng tôi có thể sử dụng một trong các phương pháp sau để bắt nó:

  • Khi chúng tôi biết lỗi xảy ra ở đâu, chúng tôi có thể gói phần đó trong miền node.js

    var d = require('domain').create()
    d.on('error', function(err){
        // handle the error safely
        console.log(err)
    })
    
    // catch the uncaught errors in this asynchronous or synchronous code block
    d.run(function(){
        // the asynchronous or synchronous code that we want to catch thrown errors on
        var err = new Error('example')
        throw err
    })
  • Nếu chúng ta biết lỗi xảy ra ở đâu là mã đồng bộ và vì bất kỳ lý do gì không thể sử dụng tên miền (có lẽ là phiên bản cũ của nút), chúng ta có thể sử dụng câu lệnh try Catch:

    // catch the uncaught errors in this synchronous code block
    // try catch statements only work on synchronous code
    try {
        // the synchronous code that we want to catch thrown errors on
        var err = new Error('example')
        throw err
    } catch (err) {
        // handle the error safely
        console.log(err)
    }

    Tuy nhiên, hãy cẩn thận không sử dụng try...catchmã không đồng bộ, vì lỗi ném không đồng bộ sẽ không bị bắt:

    try {
        setTimeout(function(){
            var err = new Error('example')
            throw err
        }, 1000)
    }
    catch (err) {
        // Example error won't be caught here... crashing our app
        // hence the need for domains
    }

    Nếu bạn muốn làm việc try..catchcùng với mã không đồng bộ, khi chạy Node 7.4 trở lên, bạn có thể sử dụngasync/await nguyên bản để viết các hàm không đồng bộ của mình.

    Một điều khác cần cẩn thận try...catchlà rủi ro bao bọc cuộc gọi lại hoàn thành của bạn bên trong trycâu lệnh như vậy:

    var divide = function(x,y,next) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by calling the completion callback
            // with the first argument being the error
            next(new Error("Can't divide by zero"))
        }
        else {
            // no error occured, continue on
            next(null, x/y)
        }
    }
    
    var continueElsewhere = function(err, result){
            throw new Error('elsewhere has failed')
    }
    
    try {
            divide(4, 2, continueElsewhere)
            // ^ the execution of divide, and the execution of 
            //   continueElsewhere will be inside the try statement
    }
    catch (err) {
            console.log(err.stack)
            // ^ will output the "unexpected" result of: elsewhere has failed
    }

    Gotcha này rất dễ thực hiện khi mã của bạn trở nên phức tạp hơn. Vì vậy, tốt nhất là sử dụng tên miền hoặc trả về lỗi để tránh (1) các ngoại lệ chưa được phát hiện trong mã không đồng bộ (2) thử bắt lỗi thực thi mà bạn không muốn. Trong các ngôn ngữ cho phép phân luồng phù hợp thay vì kiểu máy sự kiện không đồng bộ của JavaScript, đây không phải là vấn đề.

  • Cuối cùng, trong trường hợp xảy ra lỗi chưa được xử lý ở nơi không được bao bọc trong miền hoặc câu lệnh thử, chúng ta có thể làm cho ứng dụng của mình không bị sập bằng cách sử dụng trình uncaughtExceptionnghe (tuy nhiên làm như vậy có thể đặt ứng dụng ở trạng thái không xác định ):

    // catch the uncaught errors that weren't wrapped in a domain or try catch statement
    // do not use this in modules, but only in applications, as otherwise we could have multiple of these bound
    process.on('uncaughtException', function(err) {
        // handle the error safely
        console.log(err)
    })
    
    // the asynchronous or synchronous code that emits the otherwise uncaught error
    var err = new Error('example')
    throw err

5
Cảm ơn Raynos, đã cập nhật. Bạn có một nguồn giải thích các tệ nạn của try catch? Như tôi muốn ủng hộ điều đó với bằng chứng. Cũng sửa lỗi ví dụ đồng bộ.
balupton

2
Câu trả lời này không còn giá trị. Tên miền giải quyết vấn đề này (được khuyến nghị bởi node.js)
Gabriel Llamas

5
@balupton Lỗi nên được ném để xử lý lỗi. Họ chắc chắn KHÔNG nên tránh. Không có gì về họ phá vỡ việc thực thi ứng dụng hoặc bất cứ điều gì khác. Java và hầu hết các ngôn ngữ hiện đại khác có hỗ trợ tuyệt vời cho các trường hợp ngoại lệ. Kết luận duy nhất của tôi sau khi đọc một số bài viết sai ở đây là mọi người không hiểu rõ về họ và vì vậy họ rất sợ chúng. Sợ nghi ngờ không chắc chắn. Cuộc tranh luận này đã được quyết định kết luận có lợi cho các ngoại lệ ít nhất 20 năm trước.
ext8enmentnow

22
Bây giờ các tên miền không được io.js phản đối : " Mô-đun này đang chờ xử lý. Sau khi API thay thế đã được hoàn thành, mô-đun này sẽ bị phản đối hoàn toàn. Người dùng phải hoàn toàn có chức năng mà tên miền cung cấp có thể dựa vào nó trong thời gian này nhưng nên hy vọng sẽ phải chuyển sang một giải pháp khác trong tương lai. "
Timothy Gu

5
Các api tên miền bị phản đối bây giờ ? Họ đề cập đến một API thay thế - bất cứ ai cũng biết khi nào nó sẽ xuất hiện, và nó sẽ trông như thế nào?
UpTheCux

95

Sau đây là một bản tóm tắt và tuyển chọn từ nhiều nguồn khác nhau về chủ đề này bao gồm ví dụ mã và trích dẫn từ các bài đăng trên blog được chọn. Danh sách đầy đủ các thực hành tốt nhất có thể được tìm thấy ở đây


Thực hành tốt nhất về xử lý lỗi Node.JS


Số 1: Sử dụng lời hứa để xử lý lỗi không đồng bộ

TL; DR: Xử lý các lỗi không đồng bộ theo kiểu gọi lại có lẽ là cách nhanh nhất để xuống địa ngục (còn gọi là kim tự tháp của sự diệt vong). Món quà tốt nhất bạn có thể tặng cho mã của mình là sử dụng thay vào đó là thư viện lời hứa có uy tín cung cấp cú pháp mã nhỏ gọn và quen thuộc như thử bắt

Nếu không thì:Mặt Kiểu gọi lại của Node.JS, hàm (err, hồi đáp), là một cách đầy hứa hẹn cho mã không thể bảo trì do sự pha trộn xử lý lỗi với mã thông thường, các mẫu mã lồng nhau quá mức và lúng túng

Mã ví dụ - tốt

doWork()
.then(doWork)
.then(doError)
.then(doWork)
.catch(errorHandler)
.then(verify);

mã ví dụ chống mẫu - xử lý lỗi kiểu gọi lại

getData(someParameter, function(err, result){
    if(err != null)
      //do something like calling the given callback function and pass the error
    getMoreData(a, function(err, result){
          if(err != null)
            //do something like calling the given callback function and pass the error
        getMoreData(b, function(c){ 
                getMoreData(d, function(e){ 
                    ...
                });
            });
        });
    });
});

Trích dẫn trên blog: "Chúng tôi gặp vấn đề với lời hứa" (Từ blogbagdb, xếp hạng 11 cho từ khóa "Lời hứa của nút")

"Thực tế, các cuộc gọi lại làm một điều gì đó thậm chí còn độc ác hơn: chúng tước đoạt của chúng ta trong ngăn xếp, đó là điều mà chúng ta thường được cấp trong các ngôn ngữ lập trình. Viết mã mà không có ngăn xếp giống như lái xe không có bàn đạp phanh: bạn đừng nhận ra bạn cần nó tệ đến mức nào, cho đến khi bạn đạt được nó và nó không ở đó. Toàn bộ lời hứa là trả lại cho chúng tôi những nguyên tắc cơ bản về ngôn ngữ mà chúng tôi đã mất khi chúng tôi không đồng bộ: trả lại, ném và chồng. phải biết sử dụng lời hứa một cách chính xác để tận dụng lợi thế của chúng. "


Số2: Chỉ sử dụng đối tượng Lỗi tích hợp

TL; DR: Khá phổ biến khi thấy mã ném lỗi dưới dạng chuỗi hoặc dưới dạng tùy chỉnh - điều này làm phức tạp logic xử lý lỗi và khả năng tương tác giữa các mô-đun. Cho dù bạn từ chối lời hứa, ném ngoại lệ hoặc phát ra lỗi - sử dụng đối tượng Lỗi tích hợp Node.JS sẽ tăng tính đồng nhất và ngăn ngừa mất thông tin lỗi

Mặt khác: Khi thực hiện một số mô-đun, không chắc chắn loại lỗi nào sẽ quay trở lại - làm cho khó khăn hơn nhiều để lý do về ngoại lệ sắp tới và xử lý nó. Thậm chí giá trị, sử dụng các loại tùy chỉnh để mô tả lỗi có thể dẫn đến mất thông tin lỗi nghiêm trọng như theo dõi ngăn xếp!

Mã ví dụ - làm đúng

    //throwing an Error from typical function, whether sync or async
 if(!productToAdd)
 throw new Error("How can I add new product when no value provided?");

//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));

//'throwing' an Error from a Promise
 return new promise(function (resolve, reject) {
 DAL.getProduct(productToAdd.id).then((existingProduct) =>{
 if(existingProduct != null)
 return reject(new Error("Why fooling us and trying to add an existing product?"));

mã ví dụ chống mẫu

//throwing a String lacks any stack trace information and other important properties
if(!productToAdd)
    throw ("How can I add new product when no value provided?");

Trích dẫn trên blog: "Chuỗi không phải là lỗi" (Từ suy nghĩ của blog, xếp hạng 6 cho các từ khóa Từ Node.JS đối tượng lỗi đối tượng)

"Truyền qua chuỗi thay vì lỗi dẫn đến giảm khả năng tương tác giữa các mô-đun. Nó phá vỡ hợp đồng với các API có thể đang thực hiện kiểm tra lỗi, hoặc muốn biết thêm về lỗi . Các đối tượng lỗi, như chúng ta sẽ thấy, rất các thuộc tính thú vị trong các công cụ JavaScript hiện đại bên cạnh việc giữ thông điệp được chuyển đến hàm tạo .. "


Số 3: Phân biệt lỗi vận hành và lỗi lập trình viên

TL; DR: Lỗi hoạt động (ví dụ: API nhận được đầu vào không hợp lệ) đề cập đến các trường hợp đã biết trong đó tác động lỗi được hiểu đầy đủ và có thể được xử lý chu đáo. Mặt khác, lỗi lập trình viên (ví dụ: cố gắng đọc biến không xác định) đề cập đến các lỗi mã không xác định có nghĩa là khởi động lại ứng dụng một cách duyên dáng

Nếu không thì: Bạn luôn có thể khởi động lại ứng dụng khi có lỗi xuất hiện, nhưng tại sao lại để ~ 5000 người dùng trực tuyến ngừng hoạt động vì lỗi nhỏ và dự đoán (lỗi vận hành)? điều ngược lại cũng không lý tưởng - giữ ứng dụng khi xảy ra sự cố không xác định (lỗi lập trình viên) có thể dẫn đến hành vi không lường trước được. Phân biệt cả hai cho phép hành động khéo léo và áp dụng cách tiếp cận cân bằng dựa trên bối cảnh nhất định

Mã ví dụ - làm đúng

    //throwing an Error from typical function, whether sync or async
 if(!productToAdd)
 throw new Error("How can I add new product when no value provided?");

//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));

//'throwing' an Error from a Promise
 return new promise(function (resolve, reject) {
 DAL.getProduct(productToAdd.id).then((existingProduct) =>{
 if(existingProduct != null)
 return reject(new Error("Why fooling us and trying to add an existing product?"));

ví dụ mã - đánh dấu một lỗi là hoạt động (đáng tin cậy)

//marking an error object as operational 
var myError = new Error("How can I add new product when no value provided?");
myError.isOperational = true;

//or if you're using some centralized error factory (see other examples at the bullet "Use only the built-in Error object")
function appError(commonType, description, isOperational) {
    Error.call(this);
    Error.captureStackTrace(this);
    this.commonType = commonType;
    this.description = description;
    this.isOperational = isOperational;
};

throw new appError(errorManagement.commonErrors.InvalidInput, "Describe here what happened", true);

//error handling code within middleware
process.on('uncaughtException', function(error) {
    if(!error.isOperational)
        process.exit(1);
});

Blog trích dẫn : "Nếu không, bạn sẽ gặp rủi ro về trạng thái" (Từ blog có thể gỡ lỗi, xếp hạng 3 cho các từ khóa "Node.JS ngoại lệ không bị bắt")

"Về bản chất, cách ném hoạt động trong JavaScript, hầu như không có cách nào để nhận một cách an toàn trên mạng mà bạn rời khỏi, mà không bị rò rỉ tài liệu tham khảo hoặc tạo ra một số trạng thái giòn không xác định khác. Cách an toàn nhất để đáp ứng Một lỗi ném là làm tắt quá trình . Tất nhiên, trong một máy chủ web bình thường, bạn có thể có nhiều kết nối mở và không hợp lý khi tắt chúng đột ngột vì một lỗi khác được kích hoạt bởi người khác. gửi phản hồi lỗi đến yêu cầu gây ra lỗi, đồng thời để những người khác hoàn thành trong thời gian bình thường của họ và ngừng lắng nghe các yêu cầu mới trong nhân viên đó "


Số4: Xử lý lỗi tập trung, thông qua nhưng không nằm trong phần mềm trung gian

TL; DR: Lỗi xử lý logic như gửi thư đến quản trị viên và ghi nhật ký phải được gói gọn trong một đối tượng chuyên dụng và tập trung mà tất cả các điểm cuối (ví dụ: phần mềm trung gian Express, công việc định kỳ, kiểm tra đơn vị) gọi khi có lỗi xảy ra.

Mặt khác: Không xử lý lỗi trong một vị trí sẽ dẫn đến sao chép mã và có thể xảy ra lỗi được xử lý không đúng cách

Ví dụ mã - một luồng lỗi điển hình

//DAL layer, we don't handle errors here
DB.addDocument(newCustomer, (error, result) => {
    if (error)
        throw new Error("Great error explanation comes here", other useful parameters)
});

//API route code, we catch both sync and async errors and forward to the middleware
try {
    customerService.addNew(req.body).then(function (result) {
        res.status(200).json(result);
    }).catch((error) => {
        next(error)
    });
}
catch (error) {
    next(error);
}

//Error handling middleware, we delegate the handling to the centrzlied error handler
app.use(function (err, req, res, next) {
    errorHandler.handleError(err).then((isOperationalError) => {
        if (!isOperationalError)
            next(err);
    });
});

Trích dẫn trên blog: "Đôi khi các cấp thấp hơn không thể làm gì hữu ích ngoại trừ truyền lỗi cho người gọi của họ" (Từ blog Joyent, xếp hạng 1 cho các từ khóa Từ Node.JS xử lý lỗi lỗi)

"Tất cả các bạn có thể xử lý cùng một lỗi ở một số cấp độ của ngăn xếp. Điều này xảy ra khi các cấp thấp hơn không thể làm gì hữu ích ngoại trừ truyền lỗi cho người gọi của chúng, thông báo lỗi cho người gọi của nó, v.v. chỉ người gọi cấp cao nhất mới biết phản hồi phù hợp là gì, cho dù đó là thử lại thao tác, báo lỗi cho người dùng hay điều gì khác. Nhưng điều đó không có nghĩa là bạn nên cố gắng báo cáo tất cả các lỗi cho một cấp cao nhất gọi lại, vì chính cuộc gọi lại đó không thể biết lỗi xảy ra trong bối cảnh nào "


Số 5: Lỗi API tài liệu khi sử dụng Swagger

TL; DR: Hãy cho người gọi API của bạn biết lỗi nào có thể xảy ra để họ có thể xử lý những lỗi này một cách chu đáo mà không gặp sự cố. Điều này thường được thực hiện với các khung tài liệu API REST như Swagger

Mặt khác: Máy khách API có thể quyết định sự cố và chỉ khởi động lại vì anh ta đã nhận được lỗi mà anh ta không thể hiểu được. Lưu ý: người gọi API của bạn có thể là bạn (rất điển hình trong môi trường microservice)

Trích dẫn trên blog: "Bạn phải nói với người gọi của mình những lỗi nào có thể xảy ra" (Từ blog Joyent, xếp hạng 1 cho các từ khóa Nam Node.JS đăng nhập)

Mình đã nói về cách xử lý lỗi, nhưng khi bạn viết một hàm mới, làm thế nào để bạn chuyển lỗi đến mã được gọi là hàm của bạn? Nếu bạn không biết những lỗi nào có thể xảy ra hoặc không biết ý nghĩa của chúng, thì chương trình của bạn không thể chính xác trừ khi vô tình. Vì vậy, nếu bạn đang viết một chức năng mới, bạn phải nói với người gọi của bạn những lỗi nào có thể xảy ra và những gì họ có


Số 6: Đóng quy trình một cách duyên dáng khi có người lạ đến thị trấn

TL; DR: Khi xảy ra lỗi không xác định (lỗi nhà phát triển, xem phần thực hành tốt nhất số 3) - không chắc chắn về sức khỏe của ứng dụng. Một thực tiễn phổ biến đề nghị khởi động lại quá trình một cách cẩn thận bằng cách sử dụng công cụ 'restarter' như Mãi mãi và PM2

Mặt khác: Khi bị bắt ngoại lệ lạ, một số đối tượng có thể ở trạng thái bị lỗi (ví dụ: trình phát sự kiện được sử dụng trên toàn cầu và không kích hoạt sự kiện nữa do một số lỗi bên trong) và tất cả các yêu cầu trong tương lai có thể thất bại hoặc hành xử điên rồ

Ví dụ mã - quyết định xem có sụp đổ không

//deciding whether to crash when an uncaught exception arrives
//Assuming developers mark known operational errors with error.isOperational=true, read best practice #3
process.on('uncaughtException', function(error) {
 errorManagement.handler.handleError(error);
 if(!errorManagement.handler.isTrustedError(error))
 process.exit(1)
});


//centralized error handler encapsulates error-handling related logic 
function errorHandler(){
 this.handleError = function (error) {
 return logger.logError(err).then(sendMailToAdminIfCritical).then(saveInOpsQueueIfCritical).then(determineIfOperationalError);
 }

 this.isTrustedError = function(error)
 {
 return error.isOperational;
 }

Trích dẫn trên blog: "Có ba trường phái suy nghĩ về xử lý lỗi" (Từ blog jsrecipes)

Có rất nhiều trường phái suy nghĩ về xử lý lỗi: 1. Hãy để ứng dụng gặp sự cố và khởi động lại nó. 2. Xử lý tất cả các lỗi có thể và không bao giờ sụp đổ. 3. Cách tiếp cận cân bằng giữa hai


Số 7: Sử dụng trình ghi nhật ký trưởng thành để tăng khả năng hiển thị lỗi

TL; DR: Một tập hợp các công cụ ghi nhật ký trưởng thành như Winston, Bunyan hoặc Log4J, sẽ tăng tốc độ phát hiện và hiểu lỗi. Vì vậy, hãy quên console.log.

Mặt khác: Lướt qua console.logs hoặc thủ công qua tệp văn bản lộn xộn mà không có công cụ truy vấn hoặc trình xem nhật ký đàng hoàng có thể khiến bạn bận rộn đến công việc cho đến khuya

Mã ví dụ - Winston logger đang hoạt động

//your centralized logger object
var logger = new winston.Logger({
 level: 'info',
 transports: [
 new (winston.transports.Console)(),
 new (winston.transports.File)({ filename: 'somefile.log' })
 ]
 });

//custom code somewhere using the logger
logger.log('info', 'Test Log Message with some parameter %s', 'some parameter', { anything: 'This is metadata' });

Trích dẫn blog: "Hãy xác định một vài yêu cầu (đối với một trình ghi nhật ký):" (Từ blog strongblog)

Cho phép xác định một vài yêu cầu (đối với một logger): 1. Đóng dấu thời gian cho mỗi dòng nhật ký. Điều này là khá tự giải thích - bạn sẽ có thể nói khi mỗi mục nhập nhật ký xảy ra. 2. Định dạng ghi nhật ký nên dễ dàng tiêu hóa bởi con người cũng như máy móc. 3. Cho phép nhiều luồng đích cấu hình. Ví dụ: bạn có thể đang ghi nhật ký theo dõi vào một tệp nhưng khi gặp lỗi, hãy ghi vào cùng một tệp, sau đó vào tệp lỗi và gửi email cùng lúc


Số 8: Khám phá lỗi và thời gian chết khi sử dụng các sản phẩm APM

TL; DR: Các sản phẩm giám sát và hiệu suất (còn gọi là APM) chủ động đánh giá cơ sở mã hoặc API của bạn để chúng có thể tự động làm nổi bật các lỗi, sự cố và các phần chậm mà bạn đang thiếu

Mặt khác: Bạn có thể dành nhiều nỗ lực để đo hiệu suất và thời gian ngừng hoạt động của API, có lẽ bạn sẽ không bao giờ biết đâu là phần mã chậm nhất của bạn theo kịch bản trong thế giới thực và cách chúng ảnh hưởng đến UX

Trích dẫn blog: "Phân khúc sản phẩm APM" (Từ blog Yoni Goldberg)

"Các sản phẩm APM cấu thành 3 phân khúc chính: 1. Giám sát trang web hoặc API - các dịch vụ bên ngoài liên tục theo dõi thời gian hoạt động và hiệu suất thông qua các yêu cầu HTTP. Có thể được thiết lập trong vài phút. Sau đây là một vài ứng cử viên được chọn: Pingdom, Uptime Robot và New Relic 2 . Công cụ mã - họ sản phẩm cần nhúng một tác nhân trong ứng dụng để có lợi cho tính năng phát hiện mã chậm, thống kê ngoại lệ, giám sát hiệu suất và nhiều hơn nữa. Sau đây là một vài ứng cử viên được chọn: Relic mới, App Dynamics 3. Bảng điều khiển thông minh hoạt động -các dòng sản phẩm này được tập trung vào việc tạo điều kiện cho nhóm ops với các số liệu và nội dung được quản lý giúp dễ dàng đứng đầu về hiệu suất ứng dụng. Điều này thường liên quan đến việc tổng hợp nhiều nguồn thông tin (nhật ký ứng dụng, nhật ký DB, nhật ký máy chủ, v.v.) và công việc thiết kế bảng điều khiển trả trước. Sau đây là một vài ứng cử viên được chọn: Datadog, Splunk "


Trên đây là một phiên bản rút gọn - xem ở đây nhiều ví dụ và thực tiễn tốt nhất


30

Bạn có thể bắt gặp những ngoại lệ chưa được bắt gặp, nhưng đó là việc sử dụng hạn chế. Xem http://debuggable.com/posts/node-js-deals-with-uncaught-exceptions:4c933d54-1428-443c-928d-4e1ecbdd56cb

monit, foreverHoặc upstartcó thể được sử dụng để khởi động lại quá trình nút khi nó bị treo. Tắt máy duyên dáng là cách tốt nhất bạn có thể hy vọng (ví dụ: lưu tất cả dữ liệu trong bộ nhớ trong trình xử lý ngoại lệ chưa được lưu).


4
+1 Liên kết rất hữu ích, cảm ơn. Tôi vẫn đang tìm kiếm cách thực hành tốt nhất và ý nghĩa của "khởi động lại duyên dáng" trong ngữ cảnh của node.js
momo

Sự hiểu biết của tôi về "khởi động lại duyên dáng" trong bối cảnh này về cơ bản sẽ là điều mà nponeccop gợi ý: hãy để quá trình chết đi và để bất cứ điều gì đang chạy nó ở nơi đầu tiên khởi động lại nó.
Ilkka

Cảm ơn rất nhiều cho liên kết đó! Thực sự hữu ích!
SediteshJM

Đây là một câu trả lời tuyệt vời. Tuy nhiên, tôi không đồng ý về việc trả lại Lỗi trong ví dụ đầu tiên của bạn. Trả về một Errorlàm cho giá trị trả về đa hình làm sai lệch ngữ nghĩa của hàm một cách không cần thiết. Hơn nữa, lặn bằng 0 đã được xử lý JavaScript bằng cách đưa ra Infinity, -Infinityhoặc NaN, giá trị nơi typeof === 'number'. Họ có thể được kiểm tra với !isFinite(value). Nói chung, tôi khuyên bạn không bao giờ trả lại Lỗi từ một chức năng. Tốt hơn về mặt mức độ dễ đọc và bảo trì mã để ném hoặc trả về một giá trị không đa hình đặc biệt với ngữ nghĩa nhất quán.
wprl


13

tên miền nodejs là cách xử lý lỗi cập nhật nhất trong nodejs. Tên miền có thể nắm bắt cả lỗi / sự kiện khác cũng như các đối tượng ném truyền thống. Các miền cũng cung cấp chức năng xử lý các cuộc gọi lại với một lỗi được truyền dưới dạng đối số đầu tiên thông qua phương thức chặn.

Như với xử lý lỗi kiểu thử / bắt thông thường, tốt nhất là thường xuyên ném lỗi khi chúng xảy ra và chặn các khu vực mà bạn muốn cách ly lỗi khỏi ảnh hưởng đến phần còn lại của mã. Cách để "chặn" các khu vực này là gọi domain.run với chức năng là một khối mã bị cô lập.

Trong mã đồng bộ, ở trên là đủ - khi xảy ra lỗi, bạn có thể để nó bị ném qua hoặc bạn bắt nó và xử lý ở đó, hoàn nguyên bất kỳ dữ liệu nào bạn cần hoàn nguyên.

try {  
  //something
} catch(e) {
  // handle data reversion
  // probably log too
}

Khi xảy ra lỗi trong một cuộc gọi lại không đồng bộ, bạn cần có khả năng xử lý hoàn toàn việc khôi phục dữ liệu (trạng thái chia sẻ, dữ liệu ngoài như cơ sở dữ liệu, v.v.). HOẶC bạn phải đặt một cái gì đó để chỉ ra rằng một ngoại lệ đã xảy ra - bất cứ khi nào bạn quan tâm đến lá cờ đó, bạn phải đợi cuộc gọi lại hoàn tất.

var err = null;
var d = require('domain').create();
d.on('error', function(e) {
  err = e;
  // any additional error handling
}
d.run(function() { Fiber(function() {
  // do stuff
  var future = somethingAsynchronous();
  // more stuff

  future.wait(); // here we care about the error
  if(err != null) {
    // handle data reversion
    // probably log too
  }

})});

Một số mã ở trên là xấu, nhưng bạn có thể tạo các mẫu cho chính mình để làm cho nó đẹp hơn, ví dụ:

var specialDomain = specialDomain(function() {
  // do stuff
  var future = somethingAsynchronous();
  // more stuff

  future.wait(); // here we care about the error
  if(specialDomain.error()) {
    // handle data reversion
    // probably log too
  } 
}, function() { // "catch"
  // any additional error handling
});

CẬP NHẬT (2013-09):

Ở trên, tôi sử dụng một tương lai ngụ ý các ngữ nghĩa của sợi , cho phép bạn chờ đợi trong tương lai. Điều này thực sự cho phép bạn sử dụng các khối thử bắt truyền thống cho mọi thứ - mà tôi thấy là cách tốt nhất để đi. Tuy nhiên, bạn không thể luôn làm điều này (tức là trong trình duyệt) ...

Cũng có những tương lai không yêu cầu ngữ nghĩa sợi (sau đó hoạt động với JavaScript thông thường, lông mày). Chúng có thể được gọi là tương lai, lời hứa hoặc hoãn lại (tôi sẽ chỉ đề cập đến tương lai từ đây trở đi). Các thư viện tương lai đơn giản-JavaScript cho phép các lỗi được lan truyền giữa các tương lai. Chỉ một số các thư viện này cho phép mọi tương lai bị ném được xử lý chính xác, vì vậy hãy cẩn thận.

Một ví dụ:

returnsAFuture().then(function() {
  console.log('1')
  return doSomething() // also returns a future

}).then(function() {
  console.log('2')
  throw Error("oops an error was thrown")

}).then(function() {
  console.log('3')

}).catch(function(exception) {
  console.log('handler')
  // handle the exception
}).done()

Điều này bắt chước một thử bắt bình thường, mặc dù các mảnh không đồng bộ. Nó sẽ in:

1
2
handler

Lưu ý rằng nó không in '3' vì một ngoại lệ được ném làm gián đoạn dòng chảy đó.

Hãy xem những lời hứa của bluebird:

Lưu ý rằng tôi đã không tìm thấy nhiều thư viện khác ngoài những thư viện xử lý đúng các trường hợp ngoại lệ. Chẳng hạn, jQuery đã hoãn lại - trình xử lý "thất bại" sẽ không bao giờ có ngoại lệ ném một trình xử lý 'rồi', mà theo tôi là một công cụ giải quyết.


Đặc tả lời hứa phù hợp trong Javascript được gọi là Promise / A +. Bạn có thể thấy một danh sách các triển khai ở đây: github.com/promises-aplus/promises-spec/blob/master/ ,. Lưu ý rằng Lời hứa trần / A + không thể sử dụng được trong thực tế - Lời hứa / A + vẫn còn nhiều vấn đề thực tế để các thư viện tự giải quyết. Tuy nhiên, những thứ hoàn toàn cần thiết như lan truyền lỗi mà bạn hiển thị, thứ tự thực hiện xác định và an toàn từ tràn ngăn xếp được đảm bảo.
Esailija


11

Tôi đã viết về điều này gần đây tại http://snmaynard.com/2012/12/21/node-error-handling/ . Một tính năng mới của nút trong phiên bản 0.8 là các miền và cho phép bạn kết hợp tất cả các hình thức xử lý lỗi thành một hình thức quản lý dễ dàng hơn. Bạn có thể đọc về họ trong bài viết của tôi.

Bạn cũng có thể sử dụng một cái gì đó như Bugsnag để theo dõi các trường hợp ngoại lệ chưa được khai thác của bạn và được thông báo qua email, phòng chat hoặc có một vé được tạo cho một ngoại lệ chưa được bắt (tôi là người đồng sáng lập Bugsnag).


2
Các mô-đun tên miền bây giờ không còn chính thức. nodejs.org/api/domain.html
MattSidor

3

Tôi chỉ muốn thêm rằng thư viện Step.js giúp bạn xử lý các trường hợp ngoại lệ bằng cách luôn chuyển nó sang chức năng bước tiếp theo. Do đó, bạn có thể có bước cuối cùng là một chức năng kiểm tra bất kỳ lỗi nào trong bất kỳ bước nào trước đó. Cách tiếp cận này có thể đơn giản hóa rất nhiều việc xử lý lỗi của bạn.

Dưới đây là một trích dẫn từ trang github:

bất kỳ trường hợp ngoại lệ nào được ném đều được bắt và chuyển làm đối số đầu tiên cho hàm tiếp theo. Miễn là bạn không lồng các hàm gọi lại nội tuyến, các chức năng chính của bạn sẽ không có bất kỳ trường hợp ngoại lệ nào. Điều này rất quan trọng đối với các máy chủ node.JS chạy dài vì một ngoại lệ chưa được lưu có thể làm giảm toàn bộ máy chủ.

Hơn nữa, bạn có thể sử dụng Bước để kiểm soát việc thực thi các tập lệnh để có phần dọn dẹp làm bước cuối cùng. Ví dụ: nếu bạn muốn viết một tập lệnh xây dựng trong Node và báo cáo mất bao lâu để viết, bước cuối cùng có thể làm điều đó (thay vì cố gắng đào ra cuộc gọi lại cuối cùng).


3

Một trường hợp sử dụng tính năng bắt thử có thể phù hợp là khi sử dụng vòng lặp forEach. Nó là đồng bộ nhưng đồng thời bạn không thể chỉ sử dụng câu lệnh return trong phạm vi bên trong. Thay vào đó, một cách tiếp cận thử và bắt có thể được sử dụng để trả về một đối tượng Lỗi trong phạm vi thích hợp. Xem xét:

function processArray() {
    try { 
       [1, 2, 3].forEach(function() { throw new Error('exception'); }); 
    } catch (e) { 
       return e; 
    }
}

Nó là sự kết hợp của các phương pháp được mô tả bởi @balupton ở trên.


Thay vì ném lỗi, một số nhà phát triển khuyên bạn nên sử dụng khái niệm Kết quả từ Rust để trả về OK hoặc Fail , khi thất bại là một khả năng đã biết. Điều này giữ cho các thất bại tách biệt với các lỗi không mong muốn. Một triển khai JS của điều này là kết quả r .
joeytwiddle

Đó là một quyết định thiết kế trên toàn ứng dụng. Tôi nghĩ rằng khái niệm trả về lỗi của bạn gần tương đương và đơn giản để bắt đầu với (không phụ thuộc thêm), nhưng ít rõ ràng hơn ( Kết quả khiến bạn nhận thức sâu sắc khi thất bại có thể cần được xử lý) và kém hiệu quả hơn trong những trường hợp khi ngăn xếp xây dựng không cần thiết.
joeytwiddle

1

Sau khi đọc bài đăng này một thời gian trước, tôi đã tự hỏi liệu có an toàn khi sử dụng các tên miền để xử lý ngoại lệ ở cấp độ api / hàm không. Tôi muốn sử dụng chúng để đơn giản hóa mã xử lý ngoại lệ trong mỗi hàm async mà tôi đã viết. Mối quan tâm của tôi là việc sử dụng một tên miền mới cho mỗi chức năng sẽ giới thiệu chi phí đáng kể. Bài tập về nhà của tôi dường như chỉ ra rằng có chi phí tối thiểu và hiệu suất thực sự tốt hơn với các miền so với thử bắt trong một số tình huống.

http://www.lighthouselogic.com/#/USE-a-new-domain-for-each-async-feft-in-node/


1

Việc bắt lỗi đã được thảo luận rất kỹ ở đây, nhưng bạn nên nhớ ghi nhật ký lỗi ở đâu đó để bạn có thể xem và sửa lỗi.

Bunyan là một khung ghi nhật ký phổ biến cho NodeJS - nó hỗ trợ viết ra một loạt các vị trí đầu ra khác nhau, điều này hữu ích cho việc gỡ lỗi cục bộ, miễn là bạn tránh được console.log. Trong trình xử lý lỗi tên miền của bạn, bạn có thể gửi lỗi ra tệp nhật ký.

var log = bunyan.createLogger({
  name: 'myapp',
  streams: [
    {
      level: 'error',
      path: '/var/tmp/myapp-error.log'  // log ERROR to this file
    }
  ]
});

Điều này có thể gây tốn thời gian nếu bạn có nhiều lỗi và / hoặc máy chủ cần kiểm tra, vì vậy có thể đáng để xem xét một công cụ như Raygun (từ chối trách nhiệm, tôi làm việc tại Raygun) để nhóm các lỗi cùng nhau - hoặc sử dụng cả hai cùng nhau. Nếu bạn quyết định sử dụng Raygun làm công cụ, thì cũng khá dễ để thiết lập

var raygunClient = new raygun.Client().init({ apiKey: 'your API key' });
raygunClient.send(theError);

Kết hợp với việc sử dụng một công cụ như PM2 hoặc mãi mãi, ứng dụng của bạn sẽ có thể bị sập, đăng xuất những gì đã xảy ra và khởi động lại mà không gặp sự cố lớn nào.


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.