Node.js أفضل الممارسات معالجة الاستثناء




exception-handling serverside-javascript (7)

أود فقط أن أضيف أن مكتبة Step.js تساعدك على التعامل مع الاستثناءات من خلال تمريرها دائمًا إلى وظيفة الخطوة التالية. Therefore you can have as a last step a function that check for any errors in any of the previous steps. This approach can greatly simplify your error handling.

Below is a quote from the github page:

any exceptions thrown are caught and passed as the first argument to the next function. As long as you don't nest callback functions inline your main functions this prevents there from ever being any uncaught exceptions. This is very important for long running node.JS servers since a single uncaught exception can bring the whole server down.

Furthermore, you can use Step to control execution of scripts to have a clean up section as the last step. For example if you want to write a build script in Node and report how long it took to write, the last step can do that (rather than trying to dig out the last callback).

لقد بدأت للتو تجربة node.js قبل بضعة أيام. لقد أدركت أنه يتم إنهاء العقدة عندما يكون لدي استثناء غير معالج في برنامجي. يختلف هذا عن حاوية الخادم العادية التي تعرضت لها حيث يموت مؤشر ترابط العامل فقط عند حدوث الاستثناءات غير المعالجة ولا تزال الحاوية قادرة على تلقي الطلب. هذا يثير بعض الأسئلة:

  • هل هي process.on('uncaughtException') الطريقة الفعالة الوحيدة للحماية من ذلك؟
  • هل process.on('uncaughtException') الاستثناء غير process.on('uncaughtException') أثناء تنفيذ العمليات غير المتزامنة أيضًا؟
  • هل هناك وحدة بنيت بالفعل (مثل إرسال البريد الإلكتروني أو الكتابة إلى ملف) يمكنني الاستفادة منها في حالة وجود استثناءات غير مقيدة؟

وسأكون ممتناً لأي مؤشر / مقالة توضح لي أفضل الممارسات الشائعة للتعامل مع الاستثناءات غير المعلنة في node.js


تحديث: لدى Joyent الآن دليل خاص بهم مذكور في هذه الإجابة . المعلومات التالية هي أكثر من ملخص:

بأمان "رمي" أخطاء

من الناحية المثالية ، نود تجنب الأخطاء غير المسجلة قدر الإمكان ، على هذا النحو ، بدلاً من رمي الخطأ حرفياً ، يمكننا بدلاً من ذلك "رمي" الخطأ بأمان باستخدام إحدى الطرق التالية وفقًا لبنية التعليمات البرمجية الخاصة بنا:

  • بالنسبة إلى التعليمة البرمجية المتزامنة ، في حالة حدوث خطأ ، قم بإرجاع الخطأ:

    // 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)
    }
    
  • بالنسبة للشفرة المستندة إلى رد الاتصال (أي غير متزامن) ، فإن الوسيطة الأولى لاستدعاء الاتصال هي err ، إذا حدث خطأ ما هو الخطأ ، إذا لم يحدث err فإن err يكون null . أي وسيطات أخرى تتبع الوسيطة err :

    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)
        }
    })
    
  • بالنسبة إلى الشفرة eventful ، حيث قد يحدث الخطأ في أي مكان ، بدلاً من رمي الخطأ ، يمكنك إطلاق حدث error بدلاً من ذلك :

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

بأمان "اصطياد" الأخطاء

في بعض الأحيان ، قد يكون هناك رمز لا يزال يلقي خطأ في مكان ما والذي يمكن أن يؤدي إلى استثناء غير ملاحظ وتحطم محتمل لتطبيقنا إذا كنا لا نمسك به بأمان. اعتمادًا على بنية الكود ، يمكننا استخدام إحدى الطرق التالية للقبض عليه:

  • عندما نعرف مكان حدوث الخطأ ، يمكننا أن نلف هذا القسم في نطاق 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
    })
    
  • إذا كنا نعلم من أين يحدث الخطأ هو رمز متزامن ، ولأي سبب كان لا يمكن استخدام المجالات (ربما الإصدار القديم من العقدة) ، يمكننا استخدام بيان تجريب المحاولة:

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

    ومع ذلك ، يجب الحرص على عدم استخدام try...catch في التعليمات البرمجية غير المتزامنة ، كما لن يتم اكتشاف خطأ الإلقاء غير المتزامن:

    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
    }
    

    هناك أمر آخر يجب توخي الحذر بشأنه من خلال try...catch هو خطر التفاف معاودة اتصالك داخل بيان try كما يلي:

    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
    }
    

    هذا أمر سهل للغاية ، حيث يصبح الرمز الخاص بك أكثر تعقيدًا. على هذا النحو ، من الأفضل استخدام المجالات أو إرجاع الأخطاء لتجنب (1) الاستثناءات غير المعلمة في التعليمات البرمجية غير المتزامنة (2) تنفيذ catch catch catch الذي لا تريده. في اللغات التي تسمح بالترابط الصحيح بدلاً من نمط جهاز الحدث غير المتزامن الخاص بجافا سكريبت ، هذه مشكلة أقل.

  • أخيرًا ، في حالة حدوث خطأ غير معلوم في مكان لم يكن ملفوفًا في نطاق أو بيان تجريب ، يمكننا أن نجعل تطبيقنا لا uncaughtException عن طريق استخدام المستمع uncaughtException (ولكن القيام بذلك يمكن وضع التطبيق في غير معروف الحالة ):

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

كتبت عن هذا مؤخرا في http://snmaynard.com/2012/12/21/node-error-handling/ . ميزة جديدة للعقدة في الإصدار 0.8 هي المجالات وتسمح لك بدمج كافة أشكال معالجة الأخطاء في نموذج إدارة أسهل واحد. يمكنك أن تقرأ عنها في منصبي.

يمكنك أيضًا استخدام شيء ما مثل Bugsnag لتتبع الاستثناءات غير المعلنة وإخطارك عبر البريد الإلكتروني أو غرفة الدردشة أو الحصول على تذكرة تم إنشاؤها لاستثناء غير مألوف (أنا المؤسس المشارك لـ Bugsnag).


مثيل واحد عند استخدام try-catch قد يكون مناسبًا عند استخدام حلقة forEach. إنه متزامن ولكن في نفس الوقت لا يمكنك فقط استخدام عبارة return في النطاق الداخلي. بدلاً من ذلك ، يمكن استخدام أسلوب المحاولة و الإرجاع لإرجاع كائن خطأ في النطاق المناسب. يعتبر:

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

إنه مزيج من الطرق التي وصفهاbalupton أعلاه.


تعد nodejs domains هي أحدث طريقة للتعامل مع الأخطاء في العقيدات. يمكن للنطاقات التقاط كل من الأخطاء / الأحداث الأخرى بالإضافة إلى الكائنات التي يتم إلقائها تقليديًا. توفر النطاقات أيضًا وظيفة لمعالجة طلبات الرد مع وجود خطأ تم تمريره باعتباره الوسيطة الأولى عبر طريقة التقاطع.

كما هو الحال مع معالجة الأخطاء العادية / تجربة نمط catch ، عادةً ما يكون من الأفضل رمي الأخطاء عند حدوثها ، وحظر المناطق التي تريد فيها عزل الأخطاء من التأثير على بقية الشفرة. إن طريقة "حجب" هذه المناطق هي استدعاء domain.run مع وظيفة ككتلة من التعليمات البرمجية المعزولة.

في التعليمات البرمجية المتزامنة ، ما سبق هو كافي - عندما يحدث خطأ إما تركته ، أو قبض عليه والتعامل معه ، يمكنك إرجاع أي بيانات تحتاجها للعودة.

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

عندما يحدث الخطأ في رد اتصال غير متزامن ، تحتاج إما إلى القدرة على معالجة التراجع الكامل للبيانات (الحالة المشتركة ، والبيانات الخارجية مثل قواعد البيانات ، وما إلى ذلك). أو يجب عليك تعيين شيء للإشارة إلى حدوث استثناء - في أي وقت تهتم فيه بالعلم ، عليك الانتظار حتى يكتمل رد الاتصال.

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
  }

})});

بعض هذه الشفرات المذكورة أعلاه قبيحة ، ولكن يمكنك إنشاء أنماط لنفسك لجعلها أجمل ، على سبيل المثال:

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

تحديث (2013-09):

أعلاه ، يمكنني استخدام مستقبل يتضمن دلالات الألياف ، والتي تسمح لك بالانتظار على العقود الآجلة في الخط. هذا في الواقع يسمح لك باستخدام كتل المحاولة التقليدية لكل شيء - والتي أجد أنها أفضل طريقة للذهاب. ومع ذلك ، لا يمكنك دائمًا القيام بذلك (أي في المستعرض) ...

وهناك أيضا العقود الآجلة التي لا تتطلب دلالات الألياف (التي تعمل بعد ذلك مع جافا سكريبت العادية ، الاستعراض). يمكن أن تسمى هذه العقود الآجلة أو الوعود أو التأجيلات (سأشير فقط إلى العقود الآجلة من هنا). تسمح مكتبات العقود المستقبلية لجافا سكريبت القديمة بنشر الأخطاء بين العقود الآجلة. تسمح بعض هذه المكتبات فقط بمعالجة أي مستقبل مستقبلي بشكل صحيح ، لذا احذر.

مثال:

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

هذا يقلد المحاولة العادية ، على الرغم من أن القطع غير متزامنة. ستطبع:

1
2
handler

لاحظ أنه لا يطبع '3' لأنه تم طرح استثناء يقاطع هذا التدفق.

إلقاء نظرة على وعود بلوبيرد:

لاحظ أني لم أجد العديد من المكتبات الأخرى بخلاف تلك التي تتعامل بشكل صحيح مع استثناءات القذف. على سبيل المثال ، لا يؤجل "jQuery" المؤجل - لن يتمكن معالج "الفشل" من الحصول على الاستثناء على معالج "ثم" ، والذي هو في رأيي أداة تكسير الصفقات.


After reading this post some time ago I was wondering if it was safe to use domains for exception handling on an api / function level. I wanted to use them to simplify exception handling code in each async function I wrote. My concern was that using a new domain for each function would introduce significant overhead. My homework seems to indicate that there is minimal overhead and that performance is actually better with domains than with try catch in some situations.

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






serverside-javascript