Penanganan Eksepsi Praktik Terbaik Node.js

755

Saya baru saja mulai mencoba node.js beberapa hari yang lalu. Saya menyadari bahwa Node diakhiri setiap kali saya memiliki pengecualian yang tidak tertangani dalam program saya. Ini berbeda dari kontainer server normal yang telah saya hadapi di mana hanya Worker Thread yang mati ketika terjadi pengecualian yang tidak tertangani dan kontainer tersebut masih dapat menerima permintaan tersebut. Ini menimbulkan beberapa pertanyaan:

  • Apakah process.on('uncaughtException')satu-satunya cara efektif untuk mencegahnya?
  • Akankah process.on('uncaughtException')menangkap pengecualian yang tidak ditangani selama eksekusi proses asinkron juga?
  • Apakah ada modul yang sudah dibangun (seperti mengirim email atau menulis ke file) yang dapat saya manfaatkan dalam kasus pengecualian yang tidak tertangkap?

Saya akan menghargai setiap pointer / artikel yang akan menunjukkan kepada saya praktik terbaik umum untuk menangani pengecualian yang tidak tertangkap di node.js

momo
sumber
11
pengecualian yang tidak tertangkap seharusnya tidak terjadi. Jika mereka menggunakan program yang me-restart seluruh aplikasi Anda ketika crash (nodemon, forever, supervisor)
Raynos
116
Pengecualian tanpa tertangkap selalu dapat terjadi kecuali Anda memasukkan setiap bagian dari kode asinkron Anda ke dalam try .. catch, dan periksa ini juga dilakukan untuk semua lib Anda
Dan
13
+1 Dan Pada awalnya saya pikir semua lib Anda sedikit berlebihan, karena Anda "hanya" perlu membungkus semua "titik masuk utas" Anda dalam kode di coba / tangkap. Tetapi memikirkannya lebih hati-hati, lib apa pun dapat memiliki setTimeoutatau setIntervalatau sesuatu semacam itu terkubur di suatu tempat yang dalam yang tidak dapat ditangkap oleh kode Anda.
Eugene Beresovsky
8
@EugeneBeresovksy Dan benar tetapi itu tidak mengubah fakta bahwa ketika uncaughtExceptions terjadi, satu-satunya pilihan aman adalah memulai ulang aplikasi. Dengan kata lain aplikasi Anda rusak dan tidak ada yang dapat Anda lakukan atau harus Anda lakukan. Jika Anda ingin melakukan sesuatu yang konstruktif mengimplementasikan fitur domain v0.8 yang baru dan masih eksperimental sehingga Anda dapat mencatat crash dan mengirim respons 5xx ke klien Anda.
ostergaard
1
@Dan Bahkan melampirkan semua fungsi panggilan balik dalam try .. catch tidak menjamin kesalahan penangkapan. Jika modul yang diperlukan menggunakan binari-binernya sendiri, mereka dapat crash dengan tidak aman. Saya pernah mengalami hal ini dengan phantomjs-node, gagal pada kesalahan yang tidak mungkin ditangkap (kecuali saya harus melakukan semacam proses pemeriksaan pada biner yang diperlukan, tapi saya tidak pernah mengejarnya).
Trindaz

Jawaban:

738

Pembaruan: Joyent sekarang memiliki panduan sendiri . Informasi berikut ini lebih merupakan ringkasan:

Kesalahan "melempar" dengan aman

Idealnya kami ingin menghindari kesalahan yang tidak tertangkap sebanyak mungkin, dengan demikian, alih-alih benar-benar melempar kesalahan, kami dapat dengan aman "membuang" kesalahan menggunakan salah satu metode berikut tergantung pada arsitektur kode kami:

  • Untuk kode sinkron, jika terjadi kesalahan, kembalikan kesalahan:

    // 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)
    }
  • Untuk kode berbasis callback (mis. Asinkron), argumen pertama dari callback adalah err, jika kesalahan terjadi erradalah kesalahan, jika kesalahan tidak terjadi maka erradalah null. Argumen lain mengikuti errargumen:

    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)
        }
    })
  • Untuk kode kejadian , di mana kesalahan dapat terjadi di mana saja, alih-alih melempar kesalahan, jalankan erroracara sebagai gantinya :

    // 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)

Kesalahan "menangkap" dengan aman

Namun terkadang, mungkin masih ada kode yang melempar kesalahan di suatu tempat yang dapat menyebabkan pengecualian tanpa tertangkap dan kemungkinan crash aplikasi kita jika kita tidak menangkapnya dengan aman. Bergantung pada arsitektur kode kami, kami dapat menggunakan salah satu metode berikut untuk menangkapnya:

  • Ketika kita tahu di mana kesalahan terjadi, kita bisa membungkus bagian itu dalam domain 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
    })
  • Jika kita tahu di mana kesalahan terjadi adalah kode sinkron, dan untuk alasan apa pun tidak dapat menggunakan domain (mungkin versi lama dari node), kita dapat menggunakan pernyataan 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)
    }

    Namun, berhati-hatilah untuk tidak menggunakan try...catchkode asinkron, karena kesalahan yang dilemparkan secara asinkron tidak akan ditangkap:

    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
    }

    Jika Anda ingin bekerja try..catchbersama dengan kode asinkron, saat menjalankan Node 7.4 atau lebih tinggi Anda dapat menggunakan async/awaitsecara native untuk menulis fungsi asinkron Anda.

    Hal lain yang harus diperhatikan try...catchadalah risiko membungkus penyelesaian panggilan balik Anda di dalam trypernyataan seperti:

    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 ini sangat mudah dilakukan karena kode Anda menjadi lebih kompleks. Dengan demikian, yang terbaik adalah menggunakan domain atau mengembalikan kesalahan untuk menghindari (1) pengecualian tanpa kode kodrat dalam sinkronisasi (2) mencoba menangkap pelaksanaan yang tidak Anda inginkan. Dalam bahasa yang memungkinkan untuk melakukan threading dengan benar alih-alih gaya mesin-peristiwa JavaScript yang tidak sinkron, ini kurang menjadi masalah.

  • Akhirnya, dalam kasus di mana kesalahan yang tidak tertangkap terjadi di tempat yang tidak terbungkus dalam domain atau pernyataan coba tangkap, kita dapat membuat aplikasi kita tidak macet dengan menggunakan uncaughtExceptionpendengar (namun melakukan hal itu dapat menempatkan aplikasi dalam keadaan yang tidak diketahui ):

    // 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
balupton
sumber
5
Terima kasih Raynos, diperbarui. Apakah Anda memiliki sumber yang menjelaskan kejahatan try catch? Karena saya ingin mendukung itu dengan bukti. Juga perbaiki contoh sinkronisasi.
balupton
2
Jawaban ini tidak lagi valid. Domain memecahkan masalah ini (direkomendasikan oleh node.js)
Gabriel Llamas
5
@balupton Kesalahan harus dilemparkan untuk penanganan kesalahan. Mereka pasti TIDAK harus dihindari. Tidak ada apa-apa tentang mereka yang merusak pelaksanaan aplikasi atau apa pun. Java dan sebagian besar bahasa modern lainnya memiliki dukungan pengecualian yang sangat baik. Satu-satunya kesimpulan saya setelah membaca beberapa posting yang salah informasi di sini adalah bahwa orang tidak memahaminya dengan sangat baik. Ketakutan Keraguan Tidak Pasti. Debat ini diputuskan secara konklusif yang mendukung pengecualian setidaknya 20 tahun yang lalu.
enl8enmentnow
22
Sekarang domain tidak lagi digunakan oleh io.js : " Modul ini sedang menunggu penghentian. Setelah API pengganti diselesaikan, modul ini akan sepenuhnya ditinggalkan ... Pengguna yang benar-benar harus memiliki fungsionalitas yang disediakan domain mungkin bergantung padanya untuk sementara waktu tetapi harus berharap harus bermigrasi ke solusi yang berbeda di masa mendatang. "
Timothy Gu
5
The domain api sudah ditinggalkan sekarang ? Mereka menyebutkan API pengganti - ada yang tahu kapan ini akan keluar, dan seperti apa bentuknya?
UpTheCreek
95

Berikut ini adalah ringkasan dan kurasi dari berbagai sumber tentang topik ini termasuk contoh kode dan kutipan dari posting blog yang dipilih. Daftar lengkap praktik terbaik dapat ditemukan di sini


Praktik terbaik penanganan kesalahan Node.JS


Nomor 1: Gunakan janji untuk penanganan kesalahan async

TL; DR: Menangani kesalahan async dalam gaya panggilan balik mungkin merupakan cara tercepat menuju neraka (alias piramida malapetaka). Hadiah terbaik yang dapat Anda berikan kepada kode Anda adalah menggunakan perpustakaan janji yang memiliki reputasi baik yang menyediakan banyak sintaksis kode yang ringkas dan akrab seperti try-catch

Sebaliknya: gaya panggilan balik Node.JS, fungsi (err, response), adalah cara yang menjanjikan untuk kode yang tidak dapat dipelihara karena campuran penanganan kesalahan dengan kode kasual, pola pengkodean sarang yang berlebihan dan canggung

Contoh kode - bagus

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

contoh kode anti pola - penanganan kesalahan gaya panggilan balik

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){ 
                    ...
                });
            });
        });
    });
});

Kutipan blog: "Kami memiliki masalah dengan janji" (Dari blog pouchdb, peringkat 11 untuk kata kunci "Node Promises")

"... Dan pada kenyataannya, panggilan balik melakukan sesuatu yang bahkan lebih menyeramkan: mereka membuat kita kehilangan tumpukan, yang biasanya kita anggap remeh dalam bahasa pemrograman. Menulis kode tanpa tumpukan sama seperti mengendarai mobil tanpa pedal rem: Anda tidak menyadari betapa Anda sangat membutuhkannya, sampai Anda meraihnya dan itu tidak ada di sana. Inti dari janji-janji itu adalah memberi kita kembali dasar-dasar bahasa yang kita hilangkan ketika kita pergi ke async: kembali, lempar, dan tumpukan. harus tahu cara menggunakan janji dengan benar untuk memanfaatkannya. "


Nomor2: Gunakan hanya objek Galat bawaan

TL; DR: Cukup umum untuk melihat kode yang melempar kesalahan sebagai string atau sebagai tipe khusus - ini mempersulit logika penanganan kesalahan dan interoperabilitas antar modul. Apakah Anda menolak janji, melempar pengecualian atau memancarkan kesalahan - menggunakan objek Galat bawaan Node.JS meningkatkan keseragaman dan mencegah hilangnya informasi kesalahan

Kalau tidak: Ketika menjalankan beberapa modul, menjadi tidak pasti jenis kesalahan apa yang akan kembali - membuatnya lebih sulit untuk alasan tentang pengecualian yang akan datang dan menanganinya. Bahkan layak, menggunakan tipe khusus untuk menggambarkan kesalahan dapat menyebabkan hilangnya informasi kesalahan kritis seperti jejak tumpukan!

Contoh kode - melakukannya dengan benar

    //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?"));

contoh kode anti pola

//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?");

Kutipan blog: "Sebuah string bukan kesalahan" (Dari blog renungkan, peringkat 6 untuk kata kunci "objek kesalahan Node.JS")

"... meneruskan string alih-alih hasil kesalahan mengurangi interoperabilitas antar modul. Ini memutus kontrak dengan API yang mungkin melakukan instance dari pemeriksaan Kesalahan, atau yang ingin tahu lebih banyak tentang kesalahan . Objek kesalahan, seperti yang akan kita lihat, memiliki sangat properti menarik di mesin JavaScript modern selain menahan pesan yang diteruskan ke konstruktor .. "


Nomor 3: Bedakan kesalahan operasional vs programmer

TL; DR: Kesalahan operasi (mis. API menerima input yang tidak valid) merujuk pada kasus yang diketahui di mana dampak kesalahan dipahami sepenuhnya dan dapat ditangani dengan bijaksana. Di sisi lain, kesalahan pemrogram (misalnya mencoba membaca variabel yang tidak terdefinisi) mengacu pada kegagalan kode yang tidak diketahui yang mengharuskan untuk me-restart aplikasi dengan anggun

Jika tidak: Anda selalu dapat memulai ulang aplikasi ketika kesalahan muncul, tetapi mengapa membiarkan ~ 5000 pengguna online down karena kesalahan kecil dan yang diperkirakan (kesalahan operasional)? sebaliknya juga tidak ideal - menjaga aplikasi tetap aktif ketika masalah yang tidak diketahui (kesalahan pemrogram) terjadi dapat menyebabkan perilaku yang tidak terduga. Membedakan keduanya memungkinkan bertindak bijaksana dan menerapkan pendekatan yang seimbang berdasarkan konteks yang diberikan

Contoh kode - melakukannya dengan benar

    //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?"));

contoh kode - menandai kesalahan sebagai operasional (tepercaya)

//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);
});

Kutipan Blog : "Kalau tidak, Anda berisiko negara" (Dari blog debugable, peringkat 3 untuk kata kunci "Node.JS pengecualian tanpa tertangkap")

" ... Karena sifat dari cara melempar bekerja dalam JavaScript, hampir tidak pernah ada cara untuk dengan aman" mengambil di mana Anda tinggalkan ", tanpa membocorkan referensi, atau menciptakan semacam negara rapuh yang tidak terdefinisi. Cara paling aman untuk merespons kesalahan yang dilemparkan adalah untuk mematikan proses . Tentu saja, di server web normal, Anda mungkin memiliki banyak koneksi terbuka, dan itu tidak masuk akal untuk secara tiba-tiba mematikannya karena kesalahan dipicu oleh orang lain. Pendekatan yang lebih baik adalah dengan mengirim respons kesalahan ke permintaan yang memicu kesalahan, sambil membiarkan yang lain selesai dalam waktu normal mereka, dan berhenti mendengarkan permintaan baru pada pekerja itu "


Nomor 4: Menangani kesalahan secara terpusat, melalui tetapi tidak di dalam middleware

TL; DR: Logika penanganan kesalahan seperti surat ke admin dan pencatatan harus dienkapsulasi dalam objek khusus dan terpusat yang dipanggil oleh semua titik akhir (mis. Middleware, pekerjaan cron, pengujian unit) ketika ada kesalahan.

Kalau tidak: Tidak menangani kesalahan dalam satu tempat akan menyebabkan duplikasi kode dan mungkin kesalahan yang ditangani dengan tidak tepat

Contoh kode - aliran kesalahan yang khas

//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);
    });
});

Kutipan blog: "Kadang-kadang level yang lebih rendah tidak bisa melakukan apa pun kecuali menyebarkan kesalahan ke penelepon mereka" (Dari blog Joyent, peringkat 1 untuk kata kunci "Node.JS error handling")

"... Anda mungkin akhirnya menangani kesalahan yang sama di beberapa level stack. Ini terjadi ketika level yang lebih rendah tidak bisa melakukan apa pun yang berguna kecuali menyebarkan kesalahan ke penelepon mereka, yang menyebarkan kesalahan ke peneleponnya, dan sebagainya. Seringkali, hanya penelepon tingkat atas yang tahu apa tanggapan yang sesuai, apakah itu untuk mencoba kembali operasi, melaporkan kesalahan kepada pengguna, atau sesuatu yang lain.Tapi itu tidak berarti Anda harus mencoba melaporkan semua kesalahan ke tingkat atas tunggal panggilan balik, karena panggilan balik itu sendiri tidak dapat mengetahui dalam konteks apa kesalahan terjadi "


Nomor5: Kesalahan dokumen API menggunakan Swagger

TL; DR: Biarkan penelepon API Anda tahu kesalahan mana yang mungkin terjadi sehingga mereka dapat menangani ini dengan serius tanpa menabrak. Ini biasanya dilakukan dengan kerangka dokumentasi REST API seperti Swagger

Kalau tidak: Klien API mungkin memutuskan untuk mogok dan memulai kembali hanya karena ia menerima kembali kesalahan yang tidak dapat ia pahami. Catatan: penelepon API Anda mungkin Anda (sangat tipikal dalam lingkungan layanan mikro)

Kutipan blog: "Anda harus memberi tahu penelepon Anda kesalahan apa yang bisa terjadi" (Dari blog Joyent, peringkat 1 untuk kata kunci “Node.JS logging”)

... Kami sudah bicara tentang cara menangani kesalahan, tetapi ketika Anda sedang menulis fungsi baru, bagaimana Anda mengirim kesalahan ke kode yang disebut fungsi Anda? ... Jika Anda tidak tahu kesalahan apa yang bisa terjadi atau tidak tahu apa artinya, maka program Anda tidak dapat dikoreksi kecuali karena kecelakaan. Jadi, jika Anda sedang menulis fungsi baru, Anda harus memberi tahu penelepon Anda kesalahan apa yang bisa terjadi dan apa yang mereka lakukan


Nomor 6: Tutup proses dengan anggun ketika orang asing datang ke kota

TL; DR: Ketika terjadi kesalahan yang tidak diketahui (kesalahan pengembang, lihat praktik terbaik nomor 3) - ada ketidakpastian tentang kesehatan aplikasi. Praktik umum menyarankan memulai kembali proses dengan hati-hati menggunakan alat 'restarter' seperti Forever dan PM2

Jika tidak: Ketika pengecualian yang tidak dikenal ditangkap, beberapa objek mungkin berada dalam kondisi rusak (mis. Emitor peristiwa yang digunakan secara global dan tidak memecat peristiwa lagi karena beberapa kegagalan internal) dan semua permintaan di masa depan mungkin gagal atau berperilaku gila-gilaan

Contoh kode - memutuskan apakah akan mogok

//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;
 }

Kutipan blog: "Ada tiga aliran pemikiran tentang penanganan kesalahan" (Dari blog jsrecipes)

... Terutama ada tiga aliran pemikiran tentang penanganan kesalahan: 1. Biarkan aplikasi macet dan mulai ulang. 2. Tangani semua kemungkinan kesalahan dan jangan pernah crash. 3. Pendekatan yang seimbang antara keduanya


Nomor7: Gunakan logger dewasa untuk meningkatkan visibilitas kesalahan

TL; DR: Satu set alat logging yang matang seperti Winston, Bunyan atau Log4J, akan mempercepat penemuan dan pemahaman kesalahan. Jadi, lupakan tentang console.log.

Kalau tidak: Melacak melalui console.logs atau secara manual melalui file teks yang berantakan tanpa alat kueri atau penampil log yang baik mungkin membuat Anda sibuk di tempat kerja hingga larut

Contoh kode - Winston logger sedang beraksi

//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' });

Kutipan blog: "Mari mengidentifikasi beberapa persyaratan (untuk logger):" (Dari blog blog kuat)

... Mari kita mengidentifikasi beberapa persyaratan (untuk logger): 1. Cap waktu setiap baris log. Yang ini cukup jelas - Anda harus bisa mengetahui kapan setiap entri log terjadi. 2. Format logging harus mudah dicerna oleh manusia dan juga mesin. 3. Memungkinkan untuk beberapa stream tujuan yang dapat dikonfigurasi. Misalnya, Anda mungkin menulis log jejak ke satu file tetapi ketika kesalahan ditemukan, menulis ke file yang sama, kemudian ke file kesalahan dan mengirim email pada saat yang sama ...


Nomor 8: Temukan kesalahan dan waktu henti menggunakan produk APM

TL; DR: Pemantauan dan produk kinerja (alias APM) secara proaktif mengukur basis kode atau API Anda sehingga mereka dapat secara otomatis menyoroti kesalahan, kerusakan, dan bagian lambat yang Anda lewatkan

Jika tidak: Anda mungkin berusaha keras untuk mengukur kinerja dan waktu henti API, mungkin Anda tidak akan pernah menyadari bagian kode mana yang paling lambat dalam skenario dunia nyata dan bagaimana hal ini mempengaruhi UX

Kutipan blog: "Segmen produk APM" (Dari blog Yoni Goldberg)

"... Produk APM terdiri dari 3 segmen utama: 1. Pemantauan situs web atau API - layanan eksternal yang secara konstan memonitor uptime dan kinerja melalui permintaan HTTP. Dapat diatur dalam beberapa menit. Berikut adalah beberapa pesaing yang dipilih: Pingdom, Uptime Robot, dan New Relic 2 Instrumentasi kode - rangkaian produk yang perlu menyematkan agen di dalam aplikasi untuk memanfaatkan fitur deteksi kode lambat, statistik pengecualian, pemantauan kinerja, dan banyak lagi. Berikut adalah beberapa pesaing terpilih: Relik Baru, Dinamika Aplikasi 3. Dasbor intelijen operasional -lini produk ini difokuskan untuk memfasilitasi tim ops dengan metrik dan konten yang dikuratori yang membantu dengan mudah tetap di atas kinerja aplikasi. Ini biasanya melibatkan pengumpulan berbagai sumber informasi (log aplikasi, log DB, server log, dll) dan pekerjaan desain dashboard dimuka. Berikut adalah beberapa pesaing terpilih: Datadog, Splunk "


Versi di atas adalah versi singkat - lihat di sini lebih banyak praktik dan contoh terbaik

Yonatan
sumber
30

Anda dapat menangkap pengecualian tanpa tertangkap, tetapi penggunaannya terbatas. Lihat http://debuggable.com/posts/node-js-dealing-with-uncrupt-exeptionions:4c933d54-1428-443c-928d-4e1ecbdd56cb

monit, foreveratau upstartdapat digunakan untuk memulai kembali proses simpul saat macet. Shutdown yang anggun adalah yang terbaik yang dapat Anda harapkan (mis. Menyimpan semua data dalam memori dalam handler pengecualian yang tidak tertangkap).

nponeccop
sumber
4
+1 Tautan bermanfaat, terima kasih. Saya masih mencari praktik terbaik dan arti "memulai kembali dengan anggun" dalam konteks node.js
momo
Pemahaman saya tentang "restart dengan anggun" dalam konteks ini pada dasarnya akan menjadi apa yang disarankan nponeccop: biarkan proses mati, dan biarkan apa pun yang menjalankannya di tempat pertama restart itu.
Ilkka
Terima kasih banyak untuk tautan itu! Sangat berguna!
SatheeshJM
Ini jawaban yang bagus. Saya tidak setuju tentang mengembalikan Kesalahan dalam contoh pertama Anda. Mengembalikan Errormembuat nilai kembali polimorfik yang mengacaukan semantik fungsi yang tidak perlu. Selanjutnya, diving oleh 0 sudah ditangani JavaScript dengan memberikan Infinity, -Infinityatau NaN, nilai-nilai mana typeof === 'number'. Mereka dapat diperiksa dengan !isFinite(value). Secara umum saya akan merekomendasikan untuk tidak pernah mengembalikan Kesalahan dari suatu fungsi. Lebih baik dalam hal keterbacaan kode dan pemeliharaan untuk melempar atau mengembalikan nilai non-polimorfik khusus dengan semantik yang konsisten.
wprl
13

domain nodejs adalah cara paling terbaru untuk menangani kesalahan dalam nodejs. Domain dapat menangkap kesalahan / peristiwa lainnya serta benda yang dilempar secara tradisional. Domain juga menyediakan fungsionalitas untuk menangani panggilan balik dengan kesalahan yang diteruskan sebagai argumen pertama melalui metode intersepsi.

Seperti halnya penanganan kesalahan gaya coba / tangkapan biasa, biasanya yang terbaik adalah membuang kesalahan saat terjadi, dan memblokir area di mana Anda ingin mengisolasi kesalahan agar tidak mempengaruhi sisa kode. Cara untuk "memblokir" area ini adalah dengan memanggil domain.run dengan fungsi sebagai blok kode yang terisolasi.

Dalam kode sinkron, hal di atas sudah cukup - ketika terjadi kesalahan, Anda dapat membiarkannya dilemparkan, atau Anda menangkapnya dan menanganinya, mengembalikan data apa pun yang perlu Anda kembalikan.

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

Ketika kesalahan terjadi dalam callback asinkron, Anda juga harus dapat sepenuhnya menangani rollback data (keadaan bersama, data eksternal seperti database, dll). ATAU Anda harus menetapkan sesuatu untuk menunjukkan bahwa pengecualian telah terjadi - di mana pun Anda peduli tentang bendera itu, Anda harus menunggu sampai panggilan balik selesai.

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
  }

})});

Beberapa dari kode di atas jelek, tetapi Anda dapat membuat pola sendiri agar lebih cantik, misalnya:

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
});

UPDATE (2013-09):

Di atas, saya menggunakan masa depan yang menyiratkan serat semantik , yang memungkinkan Anda untuk menunggu di masa depan sejalan. Ini sebenarnya memungkinkan Anda untuk menggunakan blok try-catch tradisional untuk semuanya - yang saya temukan sebagai cara terbaik untuk melakukannya. Namun, Anda tidak dapat selalu melakukan ini (yaitu di browser) ...

Ada juga futures yang tidak memerlukan serat semantik (yang kemudian berfungsi dengan normal, JavaScript JavaScript). Ini bisa disebut berjangka, janji, atau ditangguhkan (saya akan merujuk ke berjangka mulai dari sini). Pustaka futures JavaScript-lama-polos memungkinkan kesalahan disebarkan di antara futures. Hanya beberapa perpustakaan ini yang memungkinkan masa depan yang dilempar untuk ditangani dengan benar, jadi waspadalah.

Sebuah contoh:

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()

Ini meniru try-catch normal, meskipun potongannya tidak sinkron. Itu akan mencetak:

1
2
handler

Perhatikan bahwa itu tidak mencetak '3' karena pengecualian dilemparkan yang mengganggu aliran itu.

Lihatlah janji-janji bluebird:

Perhatikan bahwa saya belum menemukan banyak perpustakaan lain selain ini yang benar-benar menangani pengecualian. jQuery ditangguhkan, misalnya, jangan - penangan "gagal" tidak akan pernah mendapatkan pengecualian dilemparkan penangan 'maka', yang menurut saya adalah pemecah kesepakatan.

BT
sumber
Spesifikasi janji yang tepat dalam Javascript dikenal sebagai Janji / A +. Anda dapat melihat daftar implementasi di sini: github.com/promises-aplus/promises-spec/blob/master/… . Perhatikan bahwa Janji yang telanjang / A + tidak dapat digunakan dalam praktiknya - Janji / A + masih menyisakan banyak masalah praktis bagi perpustakaan untuk menyelesaikannya sendiri. Namun hal-hal yang sangat penting seperti propagasi kesalahan yang Anda perlihatkan, urutan eksekusi deterministik dan keamanan dari stack overflow dijamin.
Esailija
11

Saya menulis tentang ini baru-baru ini di http://snmaynard.com/2012/12/21/node-error-handling/ . Fitur baru node dalam versi 0.8 adalah domain dan memungkinkan Anda untuk menggabungkan semua bentuk penanganan kesalahan ke dalam satu formulir pengelolaan yang lebih mudah. Anda dapat membaca tentang mereka di posting saya.

Anda juga dapat menggunakan sesuatu seperti Bugsnag untuk melacak pengecualian Anda yang tidak tertangkap dan diberitahukan melalui email, chatroom atau membuat tiket yang dibuat untuk pengecualian yang tidak tertangkap (Saya adalah salah satu pendiri Bugsnag).

Simon Maynard
sumber
2
Modul domain sekarang sudah tidak digunakan lagi. nodejs.org/api/domain.html
MattSidor
3

Saya hanya ingin menambahkan bahwa perpustakaan Step.js membantu Anda menangani pengecualian dengan selalu meneruskannya ke fungsi langkah berikutnya. Oleh karena itu, Anda dapat memiliki sebagai langkah terakhir fungsi yang memeriksa kesalahan pada langkah-langkah sebelumnya. Pendekatan ini dapat sangat menyederhanakan penanganan kesalahan Anda.

Di bawah ini adalah kutipan dari halaman github:

setiap pengecualian yang dilemparkan ditangkap dan dilewatkan sebagai argumen pertama ke fungsi berikutnya. Selama Anda tidak membuat fungsi panggilan balik sesuai dengan fungsi utama Anda, hal ini mencegah adanya pengecualian yang tidak tertangkap. Ini sangat penting untuk node yang berjalan lama. JS server karena satu pengecualian tanpa tertangkap dapat membawa seluruh server.

Selanjutnya, Anda dapat menggunakan Langkah untuk mengontrol eksekusi skrip untuk memiliki bagian pembersihan sebagai langkah terakhir. Misalnya jika Anda ingin menulis skrip build di Node dan melaporkan berapa lama waktu yang dibutuhkan untuk menulis, langkah terakhir dapat melakukannya (daripada mencoba menggali callback terakhir).

Michael Yagudaev
sumber
3

Salah satu contoh di mana menggunakan try-catch mungkin tepat adalah ketika menggunakan forEach loop. Ini sinkron tetapi pada saat yang sama Anda tidak bisa hanya menggunakan pernyataan kembali dalam lingkup dalam. Sebagai gantinya pendekatan coba dan tangkap dapat digunakan untuk mengembalikan objek Galat dalam cakupan yang sesuai. Mempertimbangkan:

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

Ini adalah kombinasi dari pendekatan yang dijelaskan oleh @balupton di atas.

Michael Yagudaev
sumber
Alih-alih melempar kesalahan, beberapa pengembang merekomendasikan menggunakan konsep Hasil dari Rust untuk mengembalikan OK atau Gagal , ketika kegagalan adalah kemungkinan yang diketahui. Ini membuat kegagalan terpisah dari kesalahan yang tidak terduga. Salah satu implementasi JS ini adalah r-hasil .
joeytwiddle
Ini adalah keputusan desain aplikasi-lebar. Saya pikir konsep pengembalian kesalahan kira-kira sama, dan mudah untuk memulai (tanpa ketergantungan tambahan), tetapi kurang eksplisit ( Hasil membuat Anda sangat sadar ketika kegagalan mungkin perlu ditangani) dan kurang efisien dalam kasus-kasus ketika tumpukan adalah dibangun secara tidak perlu.
joeytwiddle
1

Setelah membaca posting ini beberapa waktu lalu saya bertanya-tanya apakah aman menggunakan domain untuk penanganan pengecualian pada tingkat api / fungsi. Saya ingin menggunakannya untuk menyederhanakan penanganan kode pengecualian di setiap fungsi async yang saya tulis. Kekhawatiran saya adalah bahwa menggunakan domain baru untuk setiap fungsi akan menghasilkan overhead yang signifikan. Pekerjaan rumah saya tampaknya menunjukkan bahwa ada overhead minimal dan bahwa kinerja sebenarnya lebih baik dengan domain daripada dengan coba tangkap dalam beberapa situasi.

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

Berbusa
sumber
1

Menangkap kesalahan telah dibahas dengan sangat baik di sini, tetapi perlu diingat untuk mencatat kesalahan di suatu tempat sehingga Anda dapat melihatnya dan memperbaiki masalahnya.

Bunyan adalah kerangka kerja pencatatan populer untuk NodeJS - ini mendukung penulisan ke banyak tempat keluaran berbeda yang membuatnya berguna untuk debugging lokal, selama Anda menghindari konsol.log. Di penangan kesalahan domain Anda, Anda bisa meludahkan kesalahan ke file log.

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

Ini bisa memakan waktu lama jika Anda memiliki banyak kesalahan dan / atau server untuk memeriksa, jadi mungkin ada baiknya melihat ke alat seperti Raygun (penafian, saya bekerja di Raygun) untuk mengelompokkan kesalahan bersama - atau menggunakannya bersama-sama. Jika Anda memutuskan untuk menggunakan Raygun sebagai alat, itu juga cukup mudah untuk diatur

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

Dilintasi dengan menggunakan alat seperti PM2 atau selamanya, aplikasi Anda seharusnya bisa macet, keluar apa yang terjadi dan reboot tanpa masalah besar.

K. Craven
sumber