ต่อไปนี้เป็นการสรุปและการจัดการจากแหล่งต่าง ๆ ในหัวข้อนี้รวมถึงตัวอย่างโค้ดและราคาจากโพสต์บล็อกที่เลือก รายการแนวทางปฏิบัติที่ดีที่สุดทั้งหมดสามารถดูได้ที่นี่
แนวทางปฏิบัติที่ดีที่สุดของการจัดการข้อผิดพลาด Node.JS
หมายเลข 1: ใช้สัญญาสำหรับการจัดการข้อผิดพลาด async
TL; DR: การจัดการข้อผิดพลาด async ในรูปแบบการโทรกลับอาจเป็นวิธีที่เร็วที่สุดในการตกนรก (หรือที่รู้จักกันในชื่อปิรามิดแห่งการลงโทษ) ของขวัญที่ดีที่สุดที่คุณสามารถมอบให้กับรหัสของคุณใช้แทนไลบรารี่สัญญาที่มีชื่อเสียงซึ่งให้รูปแบบโค้ดที่กะทัดรัดและคุ้นเคยเช่นลองจับ
มิฉะนั้น:ลักษณะการโทรกลับของ Node.JS ฟังก์ชัน (ข้อผิดพลาดการตอบกลับ) เป็นวิธีที่มีแนวโน้มในการยกเลิกการบำรุงรักษารหัสเนื่องจากการผสมผสานของการจัดการข้อผิดพลาดด้วยรหัสไม่เป็นทางการการวางซ้อนมากเกินไปและรูปแบบการเข้ารหัสที่น่าอึดอัดใจ
ตัวอย่างรหัส - ดี
doWork()
.then(doWork)
.then(doError)
.then(doWork)
.catch(errorHandler)
.then(verify);
โค้ดตัวอย่างรูปแบบการต่อต้าน - การจัดการข้อผิดพลาดสไตล์การติดต่อกลับ
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){
...
});
});
});
});
});
คำพูดบล็อก: "เรามีปัญหากับคำสัญญา"
(จาก pouchdb บล็อกอันดับ 11 สำหรับคำหลัก "สัญญาโหนด")
"... และในความเป็นจริงการเรียกกลับทำอะไรที่น่ากลัวกว่าเดิม: พวกมันกีดกันพวกเราในกองซ้อนซึ่งเป็นสิ่งที่เรามักจะให้สิทธิ์ในการเขียนโปรแกรมภาษาการเขียนโค้ดโดยไม่มีกองซ้อนนั้นเหมือนกับการขับรถโดยไม่มีแป้นเหยียบ: ไม่ทราบว่าคุณต้องการมันมากแค่ไหนจนกระทั่งมันมาถึงและมันไม่มีอยู่จุดรวมของสัญญาคือให้พื้นฐานภาษาที่เราสูญเสียไปเมื่อเราไป async: return, throw, and stack แต่คุณ ต้องรู้วิธีใช้สัญญาอย่างถูกต้องเพื่อใช้ประโยชน์จากพวกเขา "
หมายเลข 2: ใช้เฉพาะวัตถุข้อผิดพลาดในตัว
TL; DR:เป็นเรื่องปกติที่จะเห็นรหัสที่ทำให้เกิดข้อผิดพลาดเป็นสตริงหรือเป็นประเภทที่กำหนดเองซึ่งจะทำให้ตรรกะการจัดการข้อผิดพลาดและความสามารถในการทำงานร่วมกันระหว่างโมดูลนั้นซับซ้อน ไม่ว่าคุณจะปฏิเสธสัญญา, โยนข้อยกเว้นหรือปล่อยข้อผิดพลาด - การใช้ Node.JS วัตถุในตัวข้อผิดพลาดเพิ่มความสม่ำเสมอและป้องกันการสูญเสียข้อมูลผิดพลาด
ไม่เช่นนั้น:เมื่อเรียกใช้งานโมดูลบางตัวไม่แน่ใจว่าข้อผิดพลาดประเภทใดที่กลับมา - ทำให้ยากขึ้นมากที่จะให้เหตุผลเกี่ยวกับข้อยกเว้นที่จะเกิดขึ้นและจัดการมัน แม้จะคุ้มค่าการใช้ชนิดกำหนดเองเพื่ออธิบายข้อผิดพลาดอาจนำไปสู่การสูญเสียข้อมูลข้อผิดพลาดที่สำคัญเช่นการติดตามสแต็ก!
ตัวอย่างรหัส - ทำถูกต้อง
//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?"));
ตัวอย่างรหัสรูปแบบการต่อต้าน
//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?");
คำพูดบล็อก: "สตริงไม่ใช่ข้อผิดพลาด"
(จากบล็อกความคิดอันดับที่ 6 สำหรับคำค้นหา“ Node.JS error object”)
"... ผ่านสตริงแทนข้อผิดพลาดส่งผลให้ลดการทำงานร่วมกันระหว่างโมดูลมันแบ่งสัญญากับ API ที่อาจดำเนินการอินสแตนซ์ของการตรวจสอบข้อผิดพลาดหรือต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับข้อผิดพลาดวัตถุข้อผิดพลาดตามที่เราจะเห็น คุณสมบัติที่น่าสนใจในเครื่องมือ JavaScript สมัยใหม่นอกเหนือจากการเก็บข้อความที่ส่งไปยังตัวสร้าง .. "
หมายเลข 3: แยกแยะข้อผิดพลาดในการทำงานกับโปรแกรมเมอร์
TL; DR:ข้อผิดพลาดในการดำเนินงาน (เช่น API ได้รับอินพุตที่ไม่ถูกต้อง) อ้างถึงกรณีที่ทราบว่าผลกระทบของข้อผิดพลาดนั้นเป็นที่เข้าใจอย่างสมบูรณ์และสามารถจัดการได้อย่างรอบคอบ ในทางตรงกันข้ามข้อผิดพลาดของโปรแกรมเมอร์ (เช่นพยายามอ่านตัวแปรที่ไม่ได้กำหนด) หมายถึงความล้มเหลวของรหัสที่ไม่รู้จักซึ่งบอกให้เริ่มต้นแอปพลิเคชันใหม่
มิฉะนั้นคุณอาจเริ่มต้นแอปพลิเคชันใหม่ตลอดเวลาเมื่อมีข้อผิดพลาดปรากฏขึ้น แต่ทำไมผู้ใช้ออนไลน์ ~ 5,000 รายถึงล้มเหลวเนื่องจากข้อผิดพลาดเล็กน้อยและที่คาดการณ์ไว้ ในทางกลับกันก็ไม่เหมาะเช่นกันการรักษาแอปพลิเคชันไว้เมื่อเกิดปัญหาที่ไม่รู้จัก (ข้อผิดพลาดของโปรแกรมเมอร์) อาจทำให้เกิดพฤติกรรมที่ไม่คาดคิด การแยกความแตกต่างระหว่างสองอย่างนี้ช่วยให้แสดงอย่างมีไหวพริบและใช้วิธีการที่สมดุลตามบริบทที่กำหนด
ตัวอย่างรหัส - ทำถูกต้อง
//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?"));
ตัวอย่างรหัส - การทำเครื่องหมายข้อผิดพลาดเป็นการทำงาน (เชื่อถือได้)
//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);
});
อ้างอิงบล็อก : "มิฉะนั้นคุณเสี่ยงรัฐ" (จากบล็อก debugable อันดับ 3 สำหรับคำหลัก "Node.JS ข้อยกเว้น uncaught")
" ... ด้วยลักษณะของการขว้างปาใน JavaScript แทบจะไม่มีทางเลยที่จะ" รับที่ที่คุณทิ้ง "ได้อย่างปลอดภัยโดยไม่มีการอ้างอิงรั่วไหลหรือสร้างสถานะเปราะบางอื่น ๆ วิธีที่ปลอดภัยที่สุดในการตอบสนองต่อ ข้อผิดพลาดที่ส่งออกมาคือการปิดกระบวนการแน่นอนในเว็บเซิร์ฟเวอร์ปกติคุณอาจมีการเชื่อมต่อจำนวนมากเปิดอยู่และไม่เหมาะสมที่จะปิดการเชื่อมต่อเหล่านั้นในทันทีเนื่องจากมีข้อผิดพลาดเกิดขึ้นจากบุคคลอื่นแนวทางที่ดีกว่าคือ ส่งการตอบกลับข้อผิดพลาดไปยังคำขอที่ก่อให้เกิดข้อผิดพลาดในขณะที่ปล่อยให้คนอื่น ๆ เสร็จในเวลาปกติและหยุดฟังคำขอใหม่ในคนงาน "
Number4: จัดการข้อผิดพลาดจากส่วนกลางผ่าน แต่ไม่อยู่ในมิดเดิลแวร์
TL; DR:ข้อผิดพลาดในการจัดการตรรกะเช่นเมลไปยังผู้ดูแลระบบและการบันทึกควรถูกห่อหุ้มในวัตถุเฉพาะและเป็นศูนย์กลางที่จุดสิ้นสุดทั้งหมด (เช่นมิดเดิลแวร์ด่วนงาน cron การทดสอบหน่วย) เมื่อเกิดข้อผิดพลาด
มิฉะนั้น: การไม่จัดการข้อผิดพลาดภายในที่เดียวจะนำไปสู่การทำสำเนารหัสและอาจเกิดข้อผิดพลาดที่จัดการอย่างไม่เหมาะสม
ตัวอย่างโค้ด - โฟลว์ข้อผิดพลาดทั่วไป
//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);
});
});
คำพูดบล็อก: "บางครั้งระดับที่ต่ำกว่าไม่สามารถทำอะไรได้เลยนอกจากเผยแพร่ข้อผิดพลาดไปยังผู้โทร" (จากบล็อก Joyent อันดับ 1 สำหรับคำหลัก“ การจัดการข้อผิดพลาด Node.JS”)
"... คุณอาจจัดการข้อผิดพลาดเดียวกันในหลาย ๆ ระดับของสแต็กสิ่งนี้เกิดขึ้นเมื่อระดับต่ำกว่าไม่สามารถทำอะไรได้เลยนอกจากจะเผยแพร่ข้อผิดพลาดไปยังผู้โทรซึ่งแพร่กระจายข้อผิดพลาดไปยังผู้โทร ฯลฯ มีเพียงผู้โทรระดับสูงเท่านั้นที่รู้ว่าการตอบสนองที่เหมาะสมคืออะไรไม่ว่าจะเป็นการลองใหม่รายงานข้อผิดพลาดกับผู้ใช้หรืออย่างอื่น แต่นั่นไม่ได้หมายความว่าคุณควรพยายามรายงานข้อผิดพลาดทั้งหมดไปยังระดับบนสุดเดียว โทรกลับเนื่องจากการโทรกลับนั้นไม่สามารถรู้ได้ว่าเกิดข้อผิดพลาดในบริบทใด "
หมายเลข 5: ข้อผิดพลาด API เอกสารโดยใช้ Swagger
TL; DR: แจ้งให้ผู้โทร API ของคุณทราบว่าข้อผิดพลาดใดที่อาจกลับมาเพื่อให้พวกเขาสามารถจัดการกับสิ่งเหล่านี้ได้อย่างรอบคอบโดยไม่ต้องหยุดทำงาน สิ่งนี้มักจะทำกับกรอบเอกสาร REST API เช่น Swagger
มิฉะนั้น:ลูกค้า API อาจตัดสินใจผิดพลาดและรีสตาร์ทเพียงเพราะเขาได้รับข้อผิดพลาดที่เขาไม่เข้าใจ หมายเหตุ: ผู้เรียก API ของคุณอาจเป็นคุณ (โดยทั่วไปในสภาพแวดล้อมแบบไมโครไซต์)
คำพูดบล็อก: "คุณต้องแจ้งให้ผู้โทรทราบว่าเกิดข้อผิดพลาดเกิดขึ้นได้อย่างไร" (จากบล็อก Joyent อันดับ 1 สำหรับคำหลัก“ การบันทึก Node.JS”)
... เราได้พูดถึงวิธีจัดการกับข้อผิดพลาด แต่เมื่อคุณกำลังเขียนฟังก์ชั่นใหม่คุณจะส่งข้อผิดพลาดไปยังโค้ดที่เรียกว่าฟังก์ชันของคุณได้อย่างไร ... หากคุณไม่ทราบว่ามีข้อผิดพลาดเกิดขึ้นหรือไม่ทราบว่ามีความหมายอะไรโปรแกรมของคุณจะไม่สามารถแก้ไขได้เว้นแต่ว่าเกิดอุบัติเหตุ ดังนั้นหากคุณกำลังเขียนฟังก์ชั่นใหม่คุณต้องแจ้งให้ผู้โทรทราบว่าเกิดข้อผิดพลาดอะไรขึ้นบ้าง
Number6: ปิดกระบวนการอย่างสง่างามเมื่อมีคนแปลกหน้าเข้ามาในเมือง
TL; DR:เมื่อเกิดข้อผิดพลาดที่ไม่ทราบสาเหตุ (ข้อผิดพลาดของนักพัฒนาดูหมายเลขแนวทางปฏิบัติที่ดีที่สุด # 3) - มีความไม่แน่นอนเกี่ยวกับความสมบูรณ์ของแอปพลิเคชัน การปฏิบัติทั่วไปแนะนำให้รีสตาร์ทกระบวนการอย่างระมัดระวังโดยใช้เครื่องมือ 'restarter' เช่น Forever และ PM2
มิฉะนั้น:เมื่อพบข้อยกเว้นที่ไม่คุ้นเคยวัตถุบางอย่างอาจอยู่ในสถานะผิดพลาด (เช่นตัวปล่อยเหตุการณ์ซึ่งใช้ทั่วโลกและไม่ยิงเหตุการณ์อีกต่อไปเนื่องจากความล้มเหลวภายใน) และคำขอในอนาคตทั้งหมดอาจล้มเหลวหรือทำงานอย่างบ้าคลั่ง
ตัวอย่างโค้ด - การตัดสินใจว่าจะพังหรือไม่
//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;
}
คำพูดบล็อก: "มีสามโรงเรียนคิดในการจัดการข้อผิดพลาด" (จากบล็อก jsrecipes)
... มีโรงเรียนหลักสามแห่งเกี่ยวกับการจัดการข้อผิดพลาด: 1. ปล่อยให้แอปพลิเคชันหยุดทำงานและเริ่มต้นใหม่ 2. จัดการข้อผิดพลาดที่เป็นไปได้ทั้งหมดและไม่ผิดพลาด 3. วิธีการสมดุลระหว่างสอง
Number7: ใช้ logger ที่เป็นผู้ใหญ่เพื่อเพิ่มการมองเห็นข้อผิดพลาด
TL; DR:ชุดเครื่องมือการบันทึกสำหรับผู้ใหญ่อย่าง Winston, Bunyan หรือ Log4J จะช่วยให้การค้นหาและทำความเข้าใจข้อผิดพลาดได้เร็วขึ้น ดังนั้นลืมเกี่ยวกับ console.log
ไม่เช่นนั้น: การอ่านผ่าน console.logs หรือด้วยตนเองผ่านไฟล์ข้อความยุ่ง ๆ โดยไม่ต้องใช้เครื่องมือในการสอบถามหรือเครื่องมือดูไฟล์บันทึกที่ดีอาจทำให้คุณยุ่งอยู่กับการทำงานจนถึงปลายปี
ตัวอย่างโค้ด - การดำเนินการ Winston logger
//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' });
คำพูดบล็อก: "ช่วยระบุข้อกำหนดบางประการ (สำหรับคนตัดไม้):" (จากบล็อกที่แข็งแกร่ง)
…ให้ระบุข้อกำหนดบางประการ (สำหรับคนตัดไม้): 1. ประทับเวลาในแต่ละบรรทัดบันทึก อันนี้เป็นคำอธิบายที่ค่อนข้างดี - คุณควรบอกได้เมื่อเกิดรายการบันทึกแต่ละรายการ 2. รูปแบบการบันทึกควรย่อยได้ง่ายโดยมนุษย์และเครื่องจักร 3. อนุญาตสำหรับสตรีมปลายทางที่กำหนดค่าได้หลายรายการ ตัวอย่างเช่นคุณอาจกำลังเขียนบันทึกการติดตามไปยังไฟล์เดียว แต่เมื่อพบข้อผิดพลาดให้เขียนไปที่ไฟล์เดียวกันจากนั้นไปที่ไฟล์ข้อผิดพลาดและส่งอีเมลในเวลาเดียวกัน ...
หมายเลข 8: ค้นหาข้อผิดพลาดและการหยุดทำงานโดยใช้ผลิตภัณฑ์ APM
TL; DR:ผลิตภัณฑ์การตรวจสอบและประสิทธิภาพ (หรือที่เรียกว่า APM) ทำการวัด codebase หรือ API ของคุณในเชิงรุกเพื่อให้พวกเขาสามารถเน้นข้อผิดพลาดอัตโนมัติอย่างน่าอัศจรรย์ล่มและชิ้นส่วนที่ช้าซึ่งคุณหายไป
มิฉะนั้น:คุณอาจใช้ความพยายามอย่างมากในการวัดประสิทธิภาพ API และช่วงเวลาหยุดทำงานบางทีคุณอาจไม่เคยรู้เลยว่าส่วนใดของรหัสที่ช้าที่สุดของคุณภายใต้สถานการณ์ในโลกแห่งความเป็นจริงและสิ่งเหล่านี้ส่งผลต่อ UX อย่างไร
คำพูดบล็อก: "ส่วนผลิตภัณฑ์ APM" (จากบล็อก Yoni Goldberg)
"…ผลิตภัณฑ์ APM ประกอบด้วย 3 ส่วนหลัก: 1. การตรวจสอบเว็บไซต์หรือ API -บริการภายนอกที่ตรวจสอบสถานะการออนไลน์และประสิทธิภาพผ่านการร้องขอ HTTP อย่างต่อเนื่องสามารถตั้งค่าได้ในไม่กี่นาทีต่อไปนี้เป็นคู่แข่งที่เลือก: Pingdom, Uptime Robot และ New Relic
2 . เครื่องมือวัดรหัส -ตระกูลผลิตภัณฑ์ที่ต้องการฝังตัวแทนภายในแอปพลิเคชันเพื่อรับประโยชน์การตรวจจับโค้ดที่ช้า, สถิติการยกเว้น, การตรวจสอบประสิทธิภาพและอื่น ๆ อีกมากมายต่อไปนี้เป็นคู่แข่งที่เลือกไม่กี่: New Relic, App Dynamics
3กลุ่มผลิตภัณฑ์เหล่านี้มุ่งเน้นไปที่การอำนวยความสะดวกให้กับทีม ops ด้วยการวัดและเนื้อหาที่คัดสรรซึ่งจะช่วยให้สามารถอยู่ด้านบนสุดของประสิทธิภาพแอปพลิเคชันได้อย่างง่ายดาย ซึ่งมักจะเกี่ยวข้องกับการรวบรวมแหล่งข้อมูลหลายแห่ง (บันทึกแอปพลิเคชันบันทึกฐานข้อมูลบันทึกเซิร์ฟเวอร์ ฯลฯ ) และงานออกแบบแผงควบคุมด้านหน้า ต่อไปนี้เป็น contenders ที่เลือกไม่กี่: Datadog, Splunk "
ข้างต้นเป็นเวอร์ชั่นย่อ - ดูวิธีปฏิบัติที่ดีที่สุดและตัวอย่างเพิ่มเติมได้ที่นี่