ฉันต้องการที่จะโยนบางสิ่งในรหัส JS ของฉันและฉันต้องการให้พวกเขาเป็นอินสแตนซ์ของข้อผิดพลาด แต่ฉันยังต้องการให้พวกเขาเป็นอย่างอื่น
โดยทั่วไปแล้วใน Python จะมี subclass Exception
สิ่งที่ควรทำใน JS คืออะไร
ฉันต้องการที่จะโยนบางสิ่งในรหัส JS ของฉันและฉันต้องการให้พวกเขาเป็นอินสแตนซ์ของข้อผิดพลาด แต่ฉันยังต้องการให้พวกเขาเป็นอย่างอื่น
โดยทั่วไปแล้วใน Python จะมี subclass Exception
สิ่งที่ควรทำใน JS คืออะไร
คำตอบ:
วัตถุข้อผิดพลาดฟิลด์มาตรฐานเท่านั้นมีmessage
คุณสมบัติ (ดูMDNหรือข้อกำหนดทางภาษา EcmaScript ส่วนที่ 15.11) ทุกอย่างเป็นเฉพาะแพลตฟอร์ม
สภาพแวดล้อมส่วนใหญ่ตั้งค่าstack
คุณสมบัติ แต่fileName
และlineNumber
ไม่มีประโยชน์จริง ๆ ที่จะใช้ในการสืบทอด
ดังนั้นวิธีการที่เรียบง่ายคือ:
function MyError(message) {
this.name = 'MyError';
this.message = message;
this.stack = (new Error()).stack;
}
MyError.prototype = new Error; // <-- remove this if you do not
// want MyError to be instanceof Error
คุณสามารถสูดดมสแต็กปลดองค์ประกอบที่ไม่ต้องการออกจากนั้นแยกข้อมูลเช่นชื่อไฟล์และ lineNumber แต่การทำเช่นนั้นต้องการข้อมูลเกี่ยวกับแพลตฟอร์ม JavaScript ที่กำลังทำงานอยู่ กรณีส่วนใหญ่ที่ไม่จำเป็น - และคุณสามารถทำได้ในภายหลังชันสูตรถ้าคุณต้องการจริงๆ
Safariเป็นข้อยกเว้นที่น่าสังเกต ไม่มีstack
คุณสมบัติ แต่throw
ชุดคำหลักsourceURL
และline
คุณสมบัติของวัตถุที่ถูกโยน สิ่งเหล่านั้นรับประกันว่าถูกต้อง
กรณีทดสอบที่ฉันใช้สามารถพบได้ที่นี่: การเปรียบเทียบวัตถุข้อผิดพลาดที่ JavaScript ทำเอง
function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
MyError.prototype.constructor = MyError
ด้วย
this
ใช่ไหม
ใน ES6:
class MyError extends Error {
constructor(message) {
super(message);
this.name = 'MyError';
}
}
var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
this.name = this.constructor.name;
แทน
ในระยะสั้น:
หากคุณใช้ ES6 โดยไม่มีทรานสปอนเดอร์ :
class CustomError extends Error { /* ... */}
หากคุณกำลังใช้Babel transpiler :
ตัวเลือก 1: ใช้babel-plugin-transform-builtin-expand
ตัวเลือกที่ 2: ทำด้วยตัวเอง (แรงบันดาลใจจากห้องสมุดเดียวกัน)
function CustomError(...args) {
const instance = Reflect.construct(Error, args);
Reflect.setPrototypeOf(instance, Reflect.getPrototypeOf(this));
return instance;
}
CustomError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true
}
});
Reflect.setPrototypeOf(CustomError, Error);
หากคุณใช้ES5 แท้ :
function CustomError(message, fileName, lineNumber) {
var instance = new Error(message, fileName, lineNumber);
Object.setPrototypeOf(instance, Object.getPrototypeOf(this));
return instance;
}
CustomError.prototype = Object.create(Error.prototype, {
constructor: {
value: Error,
enumerable: false,
writable: true,
configurable: true
}
});
if (Object.setPrototypeOf){
Object.setPrototypeOf(CustomError, Error);
} else {
CustomError.__proto__ = Error;
}
ทางเลือก: ใช้Classtrophobic framework
คำอธิบาย:
เหตุใดการขยายคลาสข้อผิดพลาดโดยใช้ ES6 และ Babel จึงเป็นปัญหา
เนื่องจากอินสแตนซ์ของ CustomError ไม่ได้รับการยอมรับเช่นนี้อีกต่อไป
class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false
ในความเป็นจริงจากเอกสารอย่างเป็นทางการของบาเบลที่คุณไม่สามารถขยายตัวใด ๆ ในชั้นเรียนของ JavaScriptเช่นDate
, Array
, หรือDOM
Error
ปัญหาอธิบายไว้ที่นี่:
แล้วคำตอบ SO อื่น ๆ ล่ะ?
คำตอบที่ให้ทั้งหมดแก้ไขinstanceof
ปัญหา แต่คุณสูญเสียข้อผิดพลาดปกติconsole.log
:
console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵ at CustomError (<anonymous>:4:19)↵ at <anonymous>:1:5"}
ในขณะที่ใช้วิธีการดังกล่าวข้างต้นไม่เพียง แต่คุณแก้ไขinstanceof
ปัญหา แต่คุณยังเก็บข้อผิดพลาดปกติconsole.log
:
console.log(new CustomError('test'));
// output:
// Error: test
// at CustomError (<anonymous>:2:32)
// at <anonymous>:1:5
class CustomError extends Error { /* ... */}
ไม่ได้จัดการข้อโต้แย้งเฉพาะของผู้ขายอย่างถูกต้อง ( lineNumber
, ฯลฯ ), 'ข้อผิดพลาดการขยายใน Javascript ที่มีไวยากรณ์ ES6' เป็นแบบเฉพาะของ Babel โซลูชัน ES5 ของคุณใช้const
และไม่จัดการอาร์กิวเมนต์ที่กำหนดเอง
console.log(new CustomError('test') instanceof CustomError);// false
ขึ้นจริง ณ เวลาที่เขียน แต่ตอนนี้ได้รับการแก้ไขแล้ว ในความเป็นจริงปัญหาที่เชื่อมโยงในคำตอบได้รับการแก้ไขแล้วและเราสามารถทดสอบพฤติกรรมที่ถูกต้องที่นี่และโดยการวางโค้ดในREPLและดูว่ามันถูก transpiled อย่างถูกต้องเพื่อสร้างอินสแตนซ์ของโซ่ต้นแบบที่ถูกต้อง
แก้ไข:โปรดอ่านความคิดเห็น ปรากฎว่าสิ่งนี้ใช้งานได้ดีใน V8 (Chrome / Node.JS) ฉันตั้งใจจะให้บริการโซลูชันข้ามเบราว์เซอร์ซึ่งจะทำงานในเบราว์เซอร์ทั้งหมดและให้การติดตามสแต็กที่สนับสนุนอยู่
แก้ไข:ฉันสร้าง Community Wiki นี้เพื่ออนุญาตให้แก้ไขเพิ่มเติมได้
โซลูชันสำหรับ V8 (Chrome / Node.JS) ทำงานใน Firefox และสามารถปรับเปลี่ยนให้ทำงานได้อย่างถูกต้องใน IE เป็นส่วนใหญ่ (ดูที่ท้ายโพสต์)
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype // Make this an instanceof Error.
Error.call(this) // Does not seem necessary. Perhaps remove this line?
Error.captureStackTrace(this, this.constructor) // Creates the this.stack getter
this.name = this.constructor.name; // Used to cause messages like "UserError: message" instead of the default "Error: message"
this.message = message; // Used to set the message
}
เวอร์ชั่นสั้น:
function UserError(message) {
this.constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace(this, this.constructor)
this.name = this.constructor.name
this.message = message
}
ฉันเก็บไว้this.constructor.prototype.__proto__ = Error.prototype
ข้างในฟังก์ชั่นเพื่อให้รหัสทั้งหมดเข้าด้วยกัน แต่คุณสามารถแทนที่this.constructor
ด้วยUserError
และอนุญาตให้คุณย้ายรหัสไปนอกฟังก์ชั่นดังนั้นมันจะถูกเรียกเพียงครั้งเดียว
ถ้าคุณจะไปที่เส้นทางให้แน่ใจว่าคุณโทรสายว่าก่อนที่จะUserError
เป็นครั้งแรกที่คุณโยน
ข้อแม้นั้นใช้ไม่ได้กับฟังก์ชั่นเนื่องจากฟังก์ชั่นจะถูกสร้างขึ้นก่อนไม่ว่าจะมีคำสั่งใด ดังนั้นคุณสามารถย้ายฟังก์ชั่นไปที่ท้ายไฟล์ได้โดยไม่มีปัญหา
ความเข้ากันได้ของเบราว์เซอร์
ทำงานได้ใน Firefox และ Chrome (และ Node.JS) และเติมเต็มทุกสัญญา
Internet Explorer ล้มเหลวดังต่อไปนี้
ข้อผิดพลาดไม่ต้องerr.stack
เริ่มด้วยดังนั้น "ไม่ใช่ความผิดของฉัน"
Error.captureStackTrace(this, this.constructor)
ไม่มีอยู่ดังนั้นคุณต้องทำอย่างอื่นเช่น
if(Error.captureStackTrace) // AKA if not IE
Error.captureStackTrace(this, this.constructor)
toString
Error
สิ้นสุดที่มีอยู่เมื่อคุณซับคลาส ดังนั้นคุณต้องเพิ่ม
else
this.toString = function () { return this.name + ': ' + this.message }
IE จะไม่ถือว่าคุณUserError
เป็นinstanceof Error
นอกเสียจากว่าคุณจะเรียกใช้ต่อไปนี้ก่อนคุณthrow UserError
UserError.prototype = Error.prototype
Error.call(this)
แน่นอนไม่ได้ทำอะไรเพราะมันส่งกลับthis
ข้อผิดพลาดมากกว่าการแก้ไข
UserError.prototype = Error.prototype
กำลังทำให้เข้าใจผิด นี้ไม่ได้ทำมรดกนี้ทำให้พวกเขาชั้นเดียวกัน
Object.setPrototypeOf(this.constructor.prototype, Error.prototype)
เป็นที่ต้องการthis.constructor.prototype.__proto__ = Error.prototype
อย่างน้อยสำหรับเบราว์เซอร์ปัจจุบัน
เพื่อหลีกเลี่ยงความผิดพลาดที่เกิดขึ้นกับหม้อไอน้ำทุกประเภทฉันได้รวมภูมิปัญญาของการแก้ปัญหาบางอย่างไว้ใน createErrorType
ฟังก์ชั่น:
function createErrorType(name, init) {
function E(message) {
if (!Error.captureStackTrace)
this.stack = (new Error()).stack;
else
Error.captureStackTrace(this, this.constructor);
this.message = message;
init && init.apply(this, arguments);
}
E.prototype = new Error();
E.prototype.name = name;
E.prototype.constructor = E;
return E;
}
จากนั้นคุณสามารถกำหนดประเภทข้อผิดพลาดใหม่ได้ง่ายดังนี้
var NameError = createErrorType('NameError', function (name, invalidChar) {
this.message = 'The name ' + name + ' may not contain ' + invalidChar;
});
var UnboundError = createErrorType('UnboundError', function (variableName) {
this.message = 'Variable ' + variableName + ' is not bound';
});
this.name = name;
หรือไม่?
name
มีการตั้งค่าไว้บนต้นแบบแล้วจึงไม่จำเป็นอีกต่อไป ฉันลบมัน ขอบคุณ!
ในปี 2018ฉันคิดว่านี่เป็นวิธีที่ดีที่สุด ที่รองรับ IE9 + และเบราว์เซอร์ที่ทันสมัย
UPDATE : ดูการทดสอบนี้และrepoเพื่อเปรียบเทียบการปรับใช้ที่แตกต่าง
function CustomError(message) {
Object.defineProperty(this, 'name', {
enumerable: false,
writable: false,
value: 'CustomError'
});
Object.defineProperty(this, 'message', {
enumerable: false,
writable: true,
value: message
});
if (Error.hasOwnProperty('captureStackTrace')) { // V8
Error.captureStackTrace(this, CustomError);
} else {
Object.defineProperty(this, 'stack', {
enumerable: false,
writable: false,
value: (new Error(message)).stack
});
}
}
if (typeof Object.setPrototypeOf === 'function') {
Object.setPrototypeOf(CustomError.prototype, Error.prototype);
} else {
CustomError.prototype = Object.create(Error.prototype, {
constructor: { value: CustomError }
});
}
นอกจากนี้ระวัง__proto__
คุณสมบัตินั้นเลิกใช้แล้วซึ่งใช้กันอย่างแพร่หลายในคำตอบอื่น ๆ
setPrototypeOf()
? อย่างน้อยตาม MDN มันก็ไม่ควรที่จะใช้มันหากคุณสามารถทำสิ่งเดียวกันได้โดยเพียงแค่ตั้งค่า.prototype
คุณสมบัติบน Constructor (เช่นเดียวกับที่คุณทำในelse
บล็อกสำหรับการเรียกดูที่ไม่มีsetPrototypeOf
)
setPrototypeOf
หมด แต่ถ้าคุณยังต้องการมัน (ดังที่ OP ถาม) คุณควรใช้วิธีการในตัว ดังที่ MDN บ่งชี้ว่าเป็นวิธีที่เหมาะสมในการตั้งค่าต้นแบบของวัตถุ ในคำอื่น ๆ MDN กล่าวว่าไม่เปลี่ยนต้นแบบ (มันมีผลกระทบต่อประสิทธิภาพการทำงานและเพิ่มประสิทธิภาพ) setPrototypeOf
แต่ถ้าคุณมีการใช้
CustomError.prototype = Object.create(Error.prototype)
) นอกจากนี้คือการตั้งค่าคอนสตรัคต้นแบบของตัวเองมากกว่าที่ระบุต้นแบบสำหรับอินสแตนซ์ใหม่Object.setPrototypeOf(CustomError, Error.prototype)
CustomError
อย่างไรก็ตามในปี 2559 ฉันคิดว่ามีวิธีที่ดีกว่าในการขยายข้อผิดพลาดถึงแม้ว่าฉันยังคงหาวิธีใช้งานร่วมกับ Babel: github.com/loganfsmyth/babel-plugin-transform-builtin-extend/ ......
CustomError.prototype = Object.create(Error.prototype)
กำลังเปลี่ยนต้นแบบ คุณต้องเปลี่ยนเนื่องจากไม่มีตรรกะส่วนขยาย / รับในตัวใน ES5 ฉันแน่ใจว่าปลั๊กอินบาเบลที่คุณพูดถึงทำสิ่งที่คล้ายกัน
Object.setPrototypeOf
ไม่ได้ทำให้รู้สึกที่นี่อย่างน้อยไม่ได้ในทางที่คุณกำลังใช้มัน: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 บางทีคุณควรจะเขียนObject.setPrototypeOf(CustomError.prototype, Error.prototype)
- ซึ่งจะสมเหตุสมผลกว่าเล็กน้อย (แม้ว่าจะยังไม่ได้ประโยชน์อะไรจากการตั้งค่าCustomError.prototype
)
เพื่อความสมบูรณ์ - เพียงเพราะไม่มีคำตอบก่อนหน้านี้ที่กล่าวถึงวิธีการนี้ - หากคุณกำลังทำงานกับ Node.js และไม่ต้องกังวลเกี่ยวกับความเข้ากันได้ของเบราว์เซอร์ผลที่ต้องการนั้นค่อนข้างง่ายinherits
ของutil
โมดูล ( เอกสารอย่างเป็นทางการที่นี่ )
ตัวอย่างเช่นสมมติว่าคุณต้องการสร้างคลาสข้อผิดพลาดที่กำหนดเองซึ่งรับรหัสข้อผิดพลาดเป็นอาร์กิวเมนต์แรกและข้อความแสดงข้อผิดพลาดเป็นอาร์กิวเมนต์ที่สอง:
ไฟล์custom-error.js :
'use strict';
var util = require('util');
function CustomError(code, message) {
Error.captureStackTrace(this, CustomError);
this.name = CustomError.name;
this.code = code;
this.message = message;
}
util.inherits(CustomError, Error);
module.exports = CustomError;
ตอนนี้คุณสามารถยกตัวอย่างและส่ง / โยนของคุณCustomError
:
var CustomError = require('./path/to/custom-error');
// pass as the first argument to your callback
callback(new CustomError(404, 'Not found!'));
// or, if you are working with try/catch, throw it
throw new CustomError(500, 'Server Error!');
โปรดทราบว่าด้วยส่วนย่อยนี้การติดตามสแต็กจะมีชื่อไฟล์และบรรทัดที่ถูกต้องและอินสแตนซ์ข้อผิดพลาดจะมีชื่อที่ถูกต้อง!
สิ่งนี้เกิดขึ้นเนื่องจากการใช้captureStackTrace
วิธีการซึ่งสร้างstack
คุณสมบัติบนวัตถุเป้าหมาย (ในกรณีนี้คือCustomError
การสร้างอินสแตนซ์) สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการทำงานตรวจสอบเอกสารที่นี่
this.message = this.message;
มันผิดหรือมีสิ่งที่บ้าที่ฉันไม่รู้เกี่ยวกับ JS หรือเปล่า?
คำตอบของ Crescent Fresh คำตอบที่โหวตอย่างสูงนั้นทำให้เข้าใจผิด แม้ว่าคำเตือนของเขาจะไม่ถูกต้อง แต่ก็มีข้อ จำกัด อื่น ๆ ที่เขาไม่ได้กล่าวถึง
ขั้นแรกการใช้เหตุผลในย่อหน้า "Caveats:" ของ Crescent ไม่สมเหตุสมผล คำอธิบายแสดงถึงการเข้ารหัสว่า "กลุ่ม if (ข้อผิดพลาดอินสแตนซ์ของ MyError) else ... " เป็นภาระหรือ verbose อย่างใดเมื่อเทียบกับหลายจับงบ ข้อความสั่งอินสแตนซ์หลายรายการในบล็อก catch เดียวมีความกระชับเท่ากับข้อความสั่ง catch หลายรายการ - โค้ดที่สะอาดและรัดกุมโดยไม่มีลูกเล่นใด ๆ นี่เป็นวิธีที่ยอดเยี่ยมในการเลียนแบบการจัดการข้อผิดพลาดเฉพาะประเภทย่อยได้
WRT "ปรากฏขึ้นข้อความคุณสมบัติของคลาสย่อยไม่ได้รับการตั้งค่า" ซึ่งไม่ใช่กรณีนี้หากคุณใช้คลาสย่อยข้อผิดพลาดที่สร้างขึ้นอย่างถูกต้อง ในการสร้างคลาสย่อย ErrorX Error ของคุณเองเพียงแค่คัดลอกบล็อครหัสที่ขึ้นต้นด้วย "var MyError =" โดยเปลี่ยนคำหนึ่งคำว่า "MyError" เป็น "ErrorX" (ถ้าคุณต้องการเพิ่มวิธีการที่กำหนดเองให้คลาสย่อยของคุณทำตามตัวอย่างข้อความ)
ข้อ จำกัด ที่แท้จริงและสำคัญของการทำซับคลาสของข้อผิดพลาดของ JavaScript คือสำหรับการใช้งาน JavaScript หรือตัวดีบั๊กที่ติดตามและรายงานเกี่ยวกับการติดตามสแต็กและตำแหน่งของการสร้างอินสแตนซ์เช่น FireFox ตำแหน่งในการใช้งานคลาสย่อยข้อผิดพลาดของคุณเอง คลาสในขณะที่ถ้าคุณใช้ข้อผิดพลาดโดยตรงมันจะเป็นตำแหน่งที่คุณเรียกใช้ "ข้อผิดพลาดใหม่ (... )") ผู้ใช้ IE อาจจะไม่เคยสังเกตเห็น แต่ผู้ใช้ Fire Bug บน FF จะเห็นชื่อไฟล์และหมายเลขบรรทัดที่รายงานไปพร้อมกับข้อผิดพลาดเหล่านี้และจะต้องเจาะลึกลงไปในการติดตามสแต็กไปยังองค์ประกอบ # 1 เพื่อค้นหาตำแหน่ง instantiation จริง
Crescent Fresh's
ถูกลบไปแล้ว!
วิธีการแก้ปัญหานี้?
แทนที่จะทิ้งข้อผิดพลาดที่กำหนดเองโดยใช้:
throw new MyError("Oops!");
คุณจะล้อมวัตถุข้อผิดพลาด (ชนิดเหมือน Decorator):
throw new MyError(Error("Oops!"));
สิ่งนี้ทำให้แน่ใจว่าแอ็ตทริบิวต์ทั้งหมดนั้นถูกต้องเช่นสแต็กชื่อไฟล์ lineNumber และอื่น ๆ
สิ่งที่คุณต้องทำคือคัดลอกคุณลักษณะหรือกำหนด getters สำหรับพวกเขา นี่คือตัวอย่างการใช้ getters (IE9):
function MyError(wrapped)
{
this.wrapped = wrapped;
this.wrapped.name = 'MyError';
}
function wrap(attr)
{
Object.defineProperty(MyError.prototype, attr, {
get: function()
{
return this.wrapped[attr];
}
});
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;
wrap('name');
wrap('message');
wrap('stack');
wrap('fileName');
wrap('lineNumber');
wrap('columnNumber');
MyError.prototype.toString = function()
{
return this.wrapped.toString();
};
new MyErr (arg1, arg2, new Error())
และในตัวสร้าง MyErr เราใช้Object.assign
เพื่อกำหนดคุณสมบัติหาเรื่องล่าสุดให้กับthis
อย่างที่บางคนพูดมันค่อนข้างง่ายกับ ES6:
class CustomError extends Error { }
ดังนั้นฉันจึงลองใช้แอพของฉัน (Angular, Typescript) และมันก็ใช้ไม่ได้ หลังจากระยะเวลาหนึ่งฉันพบว่าปัญหามาจาก Typescript: O
ดูhttps://github.com/Microsoft/TypeScript/issues/13965
มันน่ารำคาญมากเพราะถ้าคุณ:
class CustomError extends Error {}
try {
throw new CustomError()
} catch(e) {
if (e instanceof CustomError) {
console.log('Custom error');
} else {
console.log('Basic error');
}
}
ในโหนดหรือโดยตรงไปยังเบราว์เซอร์ของคุณมันจะแสดง: Custom error
ลองเรียกใช้ด้วย typescript ในโครงการของคุณบนบนสนามเด็กเล่น typescript มันจะแสดงBasic error
...
วิธีแก้ไขคือทำสิ่งต่อไปนี้:
class CustomError extends Error {
// we have to do the following because of: https://github.com/Microsoft/TypeScript/issues/13965
// otherwise we cannot use instanceof later to catch a given type
public __proto__: Error;
constructor(message?: string) {
const trueProto = new.target.prototype;
super(message);
this.__proto__ = trueProto;
}
}
โซลูชันของฉันนั้นง่ายกว่าคำตอบอื่น ๆ ที่ให้ไว้และไม่มีข้อเสีย
มันรักษาข้อผิดพลาดต้นแบบโซ่และคุณสมบัติทั้งหมดในข้อผิดพลาดโดยไม่จำเป็นต้องมีความรู้เฉพาะของพวกเขา ผ่านการทดสอบใน Chrome, Firefox, Node และ IE11
ข้อ จำกัด เพียงอย่างเดียวคือรายการพิเศษที่ด้านบนของสแตกการโทร แต่นั่นเป็นสิ่งที่ละเลยได้ง่าย
นี่คือตัวอย่างที่มีพารามิเตอร์ที่กำหนดเองสองตัว:
function CustomError(message, param1, param2) {
var err = new Error(message);
Object.setPrototypeOf(err, CustomError.prototype);
err.param1 = param1;
err.param2 = param2;
return err;
}
CustomError.prototype = Object.create(
Error.prototype,
{name: {value: 'CustomError', enumerable: false}}
);
ตัวอย่างการใช้งาน:
try {
throw new CustomError('Something Unexpected Happened!', 1234, 'neat');
} catch (ex) {
console.log(ex.name); //CustomError
console.log(ex.message); //Something Unexpected Happened!
console.log(ex.param1); //1234
console.log(ex.param2); //neat
console.log(ex.stack); //stacktrace
console.log(ex instanceof Error); //true
console.log(ex instanceof CustomError); //true
}
สำหรับสภาพแวดล้อมที่ต้องใช้ polyfil ของ setPrototypeOf:
Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
obj.__proto__ = proto;
return obj;
};
throw CustomError('err')
แทนthrow new CustomError('err')
ในตัวอย่างด้านบนError.apply
( เช่นError.call
) จะไม่ทำอะไรเลยสำหรับฉัน (Firefox 3.6 / Chrome 5) วิธีแก้ปัญหาที่ฉันใช้คือ:
function MyError(message, fileName, lineNumber) {
var err = new Error();
if (err.stack) {
// remove one stack level:
if (typeof(Components) != 'undefined') {
// Mozilla:
this.stack = err.stack.substring(err.stack.indexOf('\n')+1);
}
else if (typeof(chrome) != 'undefined' || typeof(process) != 'undefined') {
// Google Chrome/Node.js:
this.stack = err.stack.replace(/\n[^\n]*/,'');
}
else {
this.stack = err.stack;
}
}
this.message = message === undefined ? err.message : message;
this.fileName = fileName === undefined ? err.fileName : fileName;
this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
}
MyError.prototype = new Error();
MyError.prototype.constructor = MyError;
MyError.prototype.name = 'MyError';
ในโหนดตามที่คนอื่นพูดมันง่าย:
class DumbError extends Error {
constructor(foo = 'bar', ...params) {
super(...params);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, DumbError);
}
this.name = 'DumbError';
this.foo = foo;
this.date = new Date();
}
}
try {
let x = 3;
if (x < 10) {
throw new DumbError();
}
} catch (error) {
console.log(error);
}
ฉันแค่อยากจะเพิ่มสิ่งที่คนอื่นพูดไปแล้ว:
เพื่อให้แน่ใจว่าคลาสข้อผิดพลาดแบบกำหนดเองแสดงขึ้นอย่างถูกต้องในการติดตามสแต็กคุณต้องตั้งค่าคุณสมบัติชื่อต้นแบบของคลาสข้อผิดพลาดแบบกำหนดเองเป็นคุณสมบัติชื่อคลาสข้อผิดพลาดแบบกำหนดเอง นี่คือสิ่งที่ฉันหมายถึง:
CustomError.prototype = Error.prototype;
CustomError.prototype.name = 'CustomError';
ดังนั้นตัวอย่างเต็มรูปแบบจะเป็น:
var CustomError = function(message) {
var err = new Error(message);
err.name = 'CustomError';
this.name = err.name;
this.message = err.message;
//check if there is a stack property supported in browser
if (err.stack) {
this.stack = err.stack;
}
//we should define how our toString function works as this will be used internally
//by the browser's stack trace generation function
this.toString = function() {
return this.name + ': ' + this.message;
};
};
CustomError.prototype = new Error();
CustomError.prototype.name = 'CustomError';
เมื่อทุกคนพูดและทำคุณโยนข้อยกเว้นใหม่ของคุณและดูเหมือนว่านี้ (ฉันพยายามอย่างนี้ในเครื่องมือ dev dev ของ Chrome):
CustomError: Stuff Happened. GASP!
at Error.CustomError (<anonymous>:3:19)
at <anonymous>:2:7
at Object.InjectedScript._evaluateOn (<anonymous>:603:39)
at Object.InjectedScript._evaluateAndWrap (<anonymous>:562:52)
at Object.InjectedScript.evaluate (<anonymous>:481:21)
2 เซนต์ของฉัน:
a) เนื่องจากการเข้าถึงError.stack
คุณสมบัติ (ในบางคำตอบ) มีโทษประสิทธิภาพสูง
b) เพราะมันเป็นเพียงหนึ่งบรรทัด
c) เพราะการแก้ปัญหาที่https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Errorไม่ได้ดูเหมือนว่าจะรักษาข้อมูลสแต็ค
//MyError class constructor
function MyError(msg){
this.__proto__.__proto__ = Error.apply(null, arguments);
};
ตัวอย่างการใช้งาน
http://jsfiddle.net/luciotato/xXyeB/
this.__proto__.__proto__
เป็นMyError.prototype.__proto__
ดังนั้นมันคือการตั้งค่า__proto__
อินสแตนซ์สำหรับทุก MyError เฉพาะข้อผิดพลาดที่สร้างขึ้นใหม่ มันเก็บคุณสมบัติและวิธีการเรียน MyError และยังใส่คุณสมบัติข้อผิดพลาดใหม่ (รวมถึง. stack) ใน__proto__
ห่วงโซ่
คุณไม่สามารถมี MyError มากกว่าหนึ่งอินสแตนซ์ที่มีข้อมูลสแต็คที่มีประโยชน์
อย่าใช้วิธีนี้ถ้าคุณไม่เข้าใจสิ่งที่this.__proto__.__proto__=
ไม่
เนื่องจากข้อยกเว้น JavaScript ยากต่อการเรียนฉันจึงไม่เรียน ฉันเพิ่งสร้างคลาสยกเว้นใหม่และใช้ข้อผิดพลาดภายใน ฉันเปลี่ยนคุณสมบัติ Error.name เพื่อให้ดูเหมือนว่าข้อยกเว้นแบบกำหนดเองของฉันบนคอนโซล:
var InvalidInputError = function(message) {
var error = new Error(message);
error.name = 'InvalidInputError';
return error;
};
ข้อยกเว้นใหม่ข้างต้นสามารถโยนได้เช่นเดียวกับข้อผิดพลาดทั่วไปและจะทำงานตามที่คาดไว้ตัวอย่างเช่น:
throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string
Caveat:การติดตามสแต็กไม่สมบูรณ์เนื่องจากจะนำคุณไปยังตำแหน่งที่สร้างข้อผิดพลาดใหม่และไม่ใช่ตำแหน่งที่คุณโยน นี่ไม่ใช่เรื่องใหญ่สำหรับ Chrome เพราะให้การติดตามแบบเต็มสแต็กในคอนโซลโดยตรง แต่มันมีปัญหามากขึ้นใน Firefox เช่น
m = new InvalidInputError(); dontThrowMeYet(m);
m = new ...
Promise.reject(m)
มันไม่จำเป็น แต่รหัสอ่านง่ายกว่า
ตามที่ระบุไว้ในคำตอบของ Mohsen ใน ES6 เป็นไปได้ที่จะขยายข้อผิดพลาดโดยใช้คลาส มันง่ายกว่ามากและพฤติกรรมของพวกเขานั้นสอดคล้องกับข้อผิดพลาดดั้งเดิมมากกว่า ... แต่น่าเสียดายที่ไม่ใช่เรื่องง่ายที่จะใช้สิ่งนี้ในเบราว์เซอร์หากคุณต้องการสนับสนุนเบราว์เซอร์รุ่น ES6 ก่อน ดูด้านล่างสำหรับหมายเหตุบางส่วนเกี่ยวกับวิธีการใช้งานที่อาจเกิดขึ้น แต่ในระหว่างนี้ฉันขอแนะนำวิธีการที่ค่อนข้างง่ายซึ่งรวมคำแนะนำที่ดีที่สุดจากคำตอบอื่น ๆ :
function CustomError(message) {
//This is for future compatibility with the ES6 version, which
//would display a similar message if invoked without the
//`new` operator.
if (!(this instanceof CustomError)) {
throw new TypeError("Constructor 'CustomError' cannot be invoked without 'new'");
}
this.message = message;
//Stack trace in V8
if (Error.captureStackTrace) {
Error.captureStackTrace(this, CustomError);
}
else this.stack = (new Error).stack;
}
CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.name = 'CustomError';
ใน ES6 ง่ายเหมือน:
class CustomError extends Error {}
... และคุณสามารถตรวจหาการสนับสนุนสำหรับคลาส ES6 ด้วยtry {eval('class X{}')
แต่คุณจะได้รับข้อผิดพลาดทางไวยากรณ์หากคุณพยายามรวมรุ่น ES6 ในสคริปต์ที่โหลดโดยเบราว์เซอร์รุ่นเก่า ดังนั้นวิธีเดียวที่จะรองรับเบราว์เซอร์ทั้งหมดคือการโหลดสคริปต์แยกต่างหากแบบไดนามิก (เช่นผ่าน AJAX หรือeval()
) สำหรับเบราว์เซอร์ที่รองรับ ES6 ภาวะแทรกซ้อนอื่น ๆ ที่eval()
ไม่ได้รับการสนับสนุนในทุกสภาพแวดล้อม (เนื่องจากนโยบายความปลอดภัยของเนื้อหา) ซึ่งอาจมีหรือไม่มีการพิจารณาสำหรับโครงการของคุณ
ดังนั้นสำหรับตอนนี้ทั้งวิธีแรกข้างต้นหรือเพียงแค่ใช้Error
โดยตรงโดยไม่พยายามขยายดูเหมือนว่าจะดีที่สุดที่สามารถทำได้จริงสำหรับรหัสที่ต้องการสนับสนุนเบราว์เซอร์ที่ไม่ใช่ ES6
มีอีกวิธีหนึ่งที่บางคนอาจต้องการพิจารณาซึ่งใช้Object.setPrototypeOf()
เมื่อมีเพื่อสร้างวัตถุข้อผิดพลาดซึ่งเป็นตัวอย่างของประเภทข้อผิดพลาดที่กำหนดเองของคุณ แต่มีลักษณะและทำงานเหมือนข้อผิดพลาดดั้งเดิมในคอนโซล (ขอบคุณคำตอบของ Benสำหรับคำแนะนำ) ที่นี่ใช้เวลาของฉันเกี่ยวกับวิธีการที่: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 แต่เมื่อวันหนึ่งเราจะสามารถใช้ ES6 ได้โดยส่วนตัวฉันไม่แน่ใจว่าความซับซ้อนของวิธีการนั้นคุ้มค่าหรือไม่
วิธีการทำเช่นนี้คือการคืนค่าผลลัพธ์ของตัวสร้างจากตัวสร้างรวมถึงการตั้งค่าต้นแบบในวิธีที่ซับซ้อนตามปกติของจาวาสคริปต์:
function MyError() {
var tmp = Error.apply(this, arguments);
tmp.name = this.name = 'MyError'
this.stack = tmp.stack
this.message = tmp.message
return this
}
var IntermediateInheritor = function() {}
IntermediateInheritor.prototype = Error.prototype;
MyError.prototype = new IntermediateInheritor()
var myError = new MyError("message");
console.log("The message is: '"+myError.message+"'") // The message is: 'message'
console.log(myError instanceof Error) // true
console.log(myError instanceof MyError) // true
console.log(myError.toString()) // MyError: message
console.log(myError.stack) // MyError: message \n
// <stack trace ...>
ปัญหาที่เกิดขึ้นกับวิธีการทำเช่นนี้ ณ จุดนี้ (ฉันได้ทำซ้ำเล็กน้อย) คือว่า
stack
และmessage
ไม่รวมอยู่ในMyError
และปัญหาแรกสามารถแก้ไขได้ด้วยการวนซ้ำผ่านคุณสมบัติที่ไม่สามารถนับได้ทั้งหมดของข้อผิดพลาดโดยใช้เคล็ดลับในคำตอบนี้: เป็นไปได้ไหมที่จะได้รับชื่อคุณสมบัติที่สืบทอดไม่ได้นับของวัตถุ? แต่ไม่รองรับเช่น <9 ปัญหาที่สองสามารถแก้ไขได้โดยการลบบรรทัดนั้นในการติดตามสแต็ก แต่ฉันไม่แน่ใจว่าจะทำอย่างปลอดภัยได้อย่างไร (อาจแค่ลบบรรทัดที่สองของ e.stack.toString () ??)
ตัวอย่างแสดงให้เห็นทุกอย่าง
function add(x, y) {
if (x && y) {
return x + y;
} else {
/**
*
* the error thrown will be instanceof Error class and InvalidArgsError also
*/
throw new InvalidArgsError();
// throw new Invalid_Args_Error();
}
}
// Declare custom error using using Class
class Invalid_Args_Error extends Error {
constructor() {
super("Invalid arguments");
Error.captureStackTrace(this);
}
}
// Declare custom error using Function
function InvalidArgsError(message) {
this.message = `Invalid arguments`;
Error.captureStackTrace(this);
}
// does the same magic as extends keyword
Object.setPrototypeOf(InvalidArgsError.prototype, Error.prototype);
try{
add(2)
}catch(e){
// true
if(e instanceof Error){
console.log(e)
}
// true
if(e instanceof InvalidArgsError){
console.log(e)
}
}
ฉันจะย้อนกลับไปดูว่าทำไมคุณถึงอยากทำเช่นนั้น? ฉันคิดว่าประเด็นคือการจัดการกับข้อผิดพลาดที่แตกต่างกันแตกต่างกัน
ตัวอย่างเช่นใน Python คุณสามารถ จำกัด คำสั่ง catch ให้เป็น catch เท่านั้นMyValidationError
และบางทีคุณอาจต้องการทำสิ่งที่คล้ายกันใน javascript
catch (MyValidationError e) {
....
}
คุณไม่สามารถทำได้ใน javascript มีเพียงหนึ่ง catch catch เท่านั้น คุณควรจะใช้คำสั่ง if ในข้อผิดพลาดเพื่อกำหนดประเภทของมัน
catch(e) {
if(isMyValidationError(e)) {
...
} else {
// maybe rethrow?
throw e;
}
}
ฉันคิดว่าฉันควรโยนวัตถุดิบที่มีประเภทข้อความและคุณสมบัติอื่น ๆ ที่คุณเห็นว่าเหมาะสม
throw { type: "validation", message: "Invalid timestamp" }
และเมื่อคุณจับข้อผิดพลาด:
catch(e) {
if(e.type === "validation") {
// handle error
}
// re-throw, or whatever else
}
error.stack
ใช้เครื่องมือมาตรฐานไม่สามารถใช้งานได้ ฯลฯ วิธีที่ดีกว่าคือการเพิ่มคุณสมบัติให้กับอินสแตนซ์ข้อผิดพลาดเช่นvar e = new Error(); e.type = "validation"; ...
สิ่งนี้มีพื้นฐานมาจากคำตอบของจอร์จเบลีย์แต่ขยายและทำให้แนวคิดดั้งเดิมง่ายขึ้น มันเขียนใน CoffeeScript แต่ง่ายต่อการแปลงเป็น JavaScript แนวคิดนี้ขยายข้อผิดพลาดที่กำหนดเองของ Bailey ด้วยเครื่องมือตกแต่งที่ล้อมรอบซึ่งช่วยให้คุณสร้างข้อผิดพลาดที่กำหนดเองใหม่ได้อย่างง่ายดาย
หมายเหตุ: สิ่งนี้จะทำงานใน V8 เท่านั้น ไม่มีการรองรับError.captureStackTrace
ในสภาพแวดล้อมอื่น
มัณฑนากรใช้ชื่อประเภทข้อผิดพลาดและส่งคืนฟังก์ชันที่ใช้ข้อความแสดงข้อผิดพลาดและใส่ชื่อข้อผิดพลาด
CoreError = (@message) ->
@constructor.prototype.__proto__ = Error.prototype
Error.captureStackTrace @, @constructor
@name = @constructor.name
BaseError = (type) ->
(message) -> new CoreError "#{ type }Error: #{ message }"
ตอนนี้มันง่ายที่จะสร้างประเภทข้อผิดพลาดใหม่
StorageError = BaseError "Storage"
SignatureError = BaseError "Signature"
เพื่อความสนุกคุณสามารถกำหนดฟังก์ชั่นที่ส่ง a SignatureError
ถ้ามันถูกเรียกด้วย args มากเกินไป
f = -> throw SignatureError "too many args" if arguments.length
สิ่งนี้ได้รับการทดสอบแล้วค่อนข้างดีและดูเหมือนว่าจะทำงานได้อย่างสมบูรณ์บน V8 รักษาตำแหน่งการติดตามกลับ ฯลฯ
หมายเหตุ: การใช้new
เป็นทางเลือกเมื่อสร้างข้อผิดพลาดที่กำหนดเอง
หากคุณไม่สนใจเกี่ยวกับการแสดงข้อผิดพลาดนี่เป็นสิ่งที่เล็กที่สุดที่คุณสามารถทำได้
Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
const error = new Error(message)
Object.setPrototypeOf(error, MyError.prototype);
return error
}
คุณสามารถใช้มันได้โดยไม่ใหม่ MyError (ข้อความ)
ด้วยการเปลี่ยนต้นแบบหลังจากตัวสร้างข้อผิดพลาดเรียกว่าเราไม่จำเป็นต้องตั้งค่า callstack และข้อความ
Mohsen มีคำตอบที่ดีใน ES6 ที่ตั้งชื่อ แต่ถ้าคุณใช้ TypeScript หรือถ้าคุณใช้ชีวิตในอนาคตที่หวังว่าข้อเสนอนี้สำหรับฟิลด์คลาสสาธารณะและส่วนตัวได้ย้ายผ่านขั้นตอนที่ 3 เป็นข้อเสนอและทำให้มัน ลงในสเตจ 4 ซึ่งเป็นส่วนหนึ่งของ ECMAScript / JavaScript แล้วคุณอาจต้องการทราบว่านี่จะสั้นกว่านี้เล็กน้อย ขั้นตอนที่ 3 เป็นที่ที่เบราว์เซอร์เริ่มใช้งานคุณสมบัติดังนั้นหากเบราว์เซอร์ของคุณรองรับโค้ดด้านล่างอาจใช้งานได้ (ทดสอบในเบราว์เซอร์ Edge v81 ใหม่ดูเหมือนว่าจะทำงานได้ดี) ได้รับการเตือนว่านี่เป็นคุณสมบัติที่ไม่เสถียรในขณะนี้และควรใช้ความระมัดระวังและคุณควรตรวจสอบการสนับสนุนเบราว์เซอร์ในคุณสมบัติที่ไม่เสถียรเสมอ โพสต์นี้มีไว้สำหรับผู้อาศัยในอนาคตเป็นหลักเมื่อเบราว์เซอร์อาจสนับสนุน เพื่อตรวจสอบการสนับสนุนตรวจสอบMDNและฉันสามารถใช้ มันกำลังได้รับการสนับสนุน 66% ทั่วตลาดเบราว์เซอร์ซึ่งการเดินทางมี แต่ไม่ว่าดีดังนั้นถ้าคุณอยากจะใช้มันในขณะนี้และไม่ต้องการที่จะรอใช้ transpiler เหมือนทั้งบาเบลหรือสิ่งที่ต้องการtypescript
class EOFError extends Error {
name="EOFError"
}
throw new EOFError("Oops errored");
เปรียบเทียบสิ่งนี้กับข้อผิดพลาดนิรนามซึ่งเมื่อโยนจะไม่บันทึกมันเป็นชื่อ
class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");
this.name = 'MyError'
MyError.prototype.name = 'MyError'