เป็นวิธีที่ดีในการขยายข้อผิดพลาดใน JavaScript คืออะไร?


369

ฉันต้องการที่จะโยนบางสิ่งในรหัส JS ของฉันและฉันต้องการให้พวกเขาเป็นอินสแตนซ์ของข้อผิดพลาด แต่ฉันยังต้องการให้พวกเขาเป็นอย่างอื่น

โดยทั่วไปแล้วใน Python จะมี subclass Exception

สิ่งที่ควรทำใน JS คืออะไร

คำตอบ:


218

วัตถุข้อผิดพลาดฟิลด์มาตรฐานเท่านั้นมี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 ทำเอง


19
คุณสามารถย้ายด้านนอกของฟังก์ชั่นและเปลี่ยนเป็นthis.name = 'MyError' MyError.prototype.name = 'MyError'
Daniel Beardsley

36
นี่เป็นคำตอบเดียวที่ถูกต้องที่นี่ถึงแม้จะเป็นเรื่องของสไตล์ฉันอาจจะเขียนแบบนี้ function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
kybernetikos

11
ฉันจะเพิ่มMyError.prototype.constructor = MyErrorด้วย
Bharat Khatri

3
ใน ES6 Error.call (นี่คือข้อความ); ควรเริ่มต้นthisใช่ไหม
4esn0k

4
MyError.prototype = Object.create (Error.prototype);
โรบินเหมือนนก

170

ใน ES6:

class MyError extends Error {
  constructor(message) {
    super(message);
    this.name = 'MyError';
  }
}

แหล่ง


53
น่าจะพูดถึงว่านี่ไม่ได้ผลหากคุณใช้คุณสมบัติ ES6 ผ่าน transpiler เช่น Babel เนื่องจากคลาสย่อยต้องขยายคลาส
aaaidan

6
หากคุณกำลังใช้ babel และอยู่บนโหนด> 5.x คุณไม่ควรใช้การตั้งค่าล่วงหน้าes2015แต่npmjs.com/package/babel-preset-node5จะช่วยให้คุณสามารถใช้ es6 แบบขยายได้และอื่น ๆ อีกมากมาย
Ace

2
นี่เป็นวิธีที่ดีที่สุดที่จะทำเมื่อเป็นไปได้ ข้อผิดพลาดที่กำหนดเองจะทำงานเหมือนข้อผิดพลาดทั่วไปทั้งใน Chrome และ Firefox (และอาจเป็นเบราว์เซอร์อื่น ๆ ด้วย)
Matt Browne

2
สำหรับเบราว์เซอร์โปรดทราบว่าคุณสามารถตรวจหาการสนับสนุนสำหรับคลาสที่รันไทม์และถอยกลับไปใช้เวอร์ชันที่ไม่ใช่คลาสได้ รหัสตรวจจับ:var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
Matt Browne

31
เพื่อความสะดวกในการบำรุงรักษาให้ใช้this.name = this.constructor.name;แทน
КонстантинВан

53

ในระยะสั้น:

  • หากคุณใช้ 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, หรือDOMError

ปัญหาอธิบายไว้ที่นี่:

แล้วคำตอบ 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

1
class CustomError extends Error { /* ... */}ไม่ได้จัดการข้อโต้แย้งเฉพาะของผู้ขายอย่างถูกต้อง ( lineNumber, ฯลฯ ), 'ข้อผิดพลาดการขยายใน Javascript ที่มีไวยากรณ์ ES6' เป็นแบบเฉพาะของ Babel โซลูชัน ES5 ของคุณใช้constและไม่จัดการอาร์กิวเมนต์ที่กำหนดเอง
พอใจ

คำตอบที่สมบูรณ์มาก
Daniele Orlando

นี่เป็นวิธีแก้ปัญหาที่ครอบคลุมที่สุดและอธิบายว่าทำไมจึงจำเป็นต้องใช้ชิ้นส่วนต่าง ๆ ขอบคุณมาก JBE!
AndrewSouthpaw

สิ่งนี้ช่วยฉันในการแก้ปัญหาโดยสืบทอดจาก "ข้อผิดพลาด" มันเป็นฝันร้ายสองชั่วโมง!
Iulian Pinzaru

2
เป็นที่น่าสังเกตว่าปัญหาที่เกิดconsole.log(new CustomError('test') instanceof CustomError);// falseขึ้นจริง ณ เวลาที่เขียน แต่ตอนนี้ได้รับการแก้ไขแล้ว ในความเป็นจริงปัญหาที่เชื่อมโยงในคำตอบได้รับการแก้ไขแล้วและเราสามารถทดสอบพฤติกรรมที่ถูกต้องที่นี่และโดยการวางโค้ดในREPLและดูว่ามันถูก transpiled อย่างถูกต้องเพื่อสร้างอินสแตนซ์ของโซ่ต้นแบบที่ถูกต้อง
โนบิตะ

44

แก้ไข:โปรดอ่านความคิดเห็น ปรากฎว่าสิ่งนี้ใช้งานได้ดีใน 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)
  • toStringErrorสิ้นสุดที่มีอยู่เมื่อคุณซับคลาส ดังนั้นคุณต้องเพิ่ม

    else
        this.toString = function () { return this.name + ': ' + this.message }
  • IE จะไม่ถือว่าคุณUserErrorเป็นinstanceof Errorนอกเสียจากว่าคุณจะเรียกใช้ต่อไปนี้ก่อนคุณthrow UserError

    UserError.prototype = Error.prototype

16
ฉันไม่คิดว่า Firefox มี captureStackTrace จริงๆ มันเป็นส่วนเสริม V8 และไม่ได้กำหนดใน Firefox สำหรับฉันฉันไม่สามารถค้นหาการอ้างอิงใด ๆ บนเว็บไปยัง Firefox ที่สนับสนุน (ขอบคุณแม้ว่า!)
Geoff

5
Error.call(this)แน่นอนไม่ได้ทำอะไรเพราะมันส่งกลับthisข้อผิดพลาดมากกว่าการแก้ไข
kybernetikos

1
ใช้งานได้ดีสำหรับ Node.js
Rudolf Meijering

1
UserError.prototype = Error.prototypeกำลังทำให้เข้าใจผิด นี้ไม่ได้ทำมรดกนี้ทำให้พวกเขาชั้นเดียวกัน
Halcyon

1
ฉันเชื่อว่าObject.setPrototypeOf(this.constructor.prototype, Error.prototype)เป็นที่ต้องการthis.constructor.prototype.__proto__ = Error.prototypeอย่างน้อยสำหรับเบราว์เซอร์ปัจจุบัน
ChrisV

29

เพื่อหลีกเลี่ยงความผิดพลาดที่เกิดขึ้นกับหม้อไอน้ำทุกประเภทฉันได้รวมภูมิปัญญาของการแก้ปัญหาบางอย่างไว้ใน  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;หรือไม่?
Peter Tseng

@PeterTseng เนื่องจากnameมีการตั้งค่าไว้บนต้นแบบแล้วจึงไม่จำเป็นอีกต่อไป ฉันลบมัน ขอบคุณ!
Ruben Verborgh

27

ในปี 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__คุณสมบัตินั้นเลิกใช้แล้วซึ่งใช้กันอย่างแพร่หลายในคำตอบอื่น ๆ


1
ทำไมคุณใช้setPrototypeOf()? อย่างน้อยตาม MDN มันก็ไม่ควรที่จะใช้มันหากคุณสามารถทำสิ่งเดียวกันได้โดยเพียงแค่ตั้งค่า.prototypeคุณสมบัติบน Constructor (เช่นเดียวกับที่คุณทำในelseบล็อกสำหรับการเรียกดูที่ไม่มีsetPrototypeOf)
Matt Browne

การเปลี่ยนต้นแบบของวัตถุนั้นไม่ได้ช่วยกันsetPrototypeOfหมด แต่ถ้าคุณยังต้องการมัน (ดังที่ OP ถาม) คุณควรใช้วิธีการในตัว ดังที่ MDN บ่งชี้ว่าเป็นวิธีที่เหมาะสมในการตั้งค่าต้นแบบของวัตถุ ในคำอื่น ๆ MDN กล่าวว่าไม่เปลี่ยนต้นแบบ (มันมีผลกระทบต่อประสิทธิภาพการทำงานและเพิ่มประสิทธิภาพ) setPrototypeOfแต่ถ้าคุณมีการใช้
Onur Yıldırım

ประเด็นของฉันคือฉันไม่คิดว่าคุณจะต้องเปลี่ยนต้นแบบที่นี่ คุณสามารถใช้บรรทัดที่ด้านล่าง ( CustomError.prototype = Object.create(Error.prototype)) นอกจากนี้คือการตั้งค่าคอนสตรัคต้นแบบของตัวเองมากกว่าที่ระบุต้นแบบสำหรับอินสแตนซ์ใหม่Object.setPrototypeOf(CustomError, Error.prototype) CustomErrorอย่างไรก็ตามในปี 2559 ฉันคิดว่ามีวิธีที่ดีกว่าในการขยายข้อผิดพลาดถึงแม้ว่าฉันยังคงหาวิธีใช้งานร่วมกับ Babel: github.com/loganfsmyth/babel-plugin-transform-builtin-extend/ ......
Matt Browne

CustomError.prototype = Object.create(Error.prototype)กำลังเปลี่ยนต้นแบบ คุณต้องเปลี่ยนเนื่องจากไม่มีตรรกะส่วนขยาย / รับในตัวใน ES5 ฉันแน่ใจว่าปลั๊กอินบาเบลที่คุณพูดถึงทำสิ่งที่คล้ายกัน
Onur Yıldırım

1
ฉันสร้างส่วนสำคัญแสดงให้เห็นถึงเหตุผลที่ใช้Object.setPrototypeOfไม่ได้ทำให้รู้สึกที่นี่อย่างน้อยไม่ได้ในทางที่คุณกำลังใช้มัน: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 บางทีคุณควรจะเขียนObject.setPrototypeOf(CustomError.prototype, Error.prototype)- ซึ่งจะสมเหตุสมผลกว่าเล็กน้อย (แม้ว่าจะยังไม่ได้ประโยชน์อะไรจากการตั้งค่าCustomError.prototype)
Matt Browne

19

เพื่อความสมบูรณ์ - เพียงเพราะไม่มีคำตอบก่อนหน้านี้ที่กล่าวถึงวิธีการนี้ - หากคุณกำลังทำงานกับ 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การสร้างอินสแตนซ์) สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการทำงานตรวจสอบเอกสารที่นี่


1
this.message = this.message;มันผิดหรือมีสิ่งที่บ้าที่ฉันไม่รู้เกี่ยวกับ JS หรือเปล่า?
อเล็กซ์

1
เฮ้ @Alex คุณพูดถูก! ตอนนี้ได้รับการแก้ไขแล้ว ขอบคุณ!
Victor Schröder

18

คำตอบของ 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 จริง


ฉันเข้าใจถูกต้องหรือไม่ - ถ้าคุณไม่ทำคลาสย่อยและใช้ข้อผิดพลาดใหม่ (... ) โดยตรงชื่อไฟล์และบรรทัดจะถูกรายงานอย่างถูกต้องหรือไม่? และคุณก็บอกว่าในทางปฏิบัติ (จริง ๆ แล้วไม่ใช่แค่ความเซ็กซี่หรือการตกแต่ง) ความผิดพลาดในการทำคลาสย่อยไม่มีเหตุผล?
jayarjo

6
คำตอบนี้เป็นความสับสนที่Crescent Fresh'sถูกลบไปแล้ว!
ปีเตอร์

ยังคงเป็นกรณีนี้หรือไม่? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Muhammad Umer

13

วิธีการแก้ปัญหานี้?

แทนที่จะทิ้งข้อผิดพลาดที่กำหนดเองโดยใช้:

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

1
ฉันได้เปิดตัวโซลูชันนี้เป็นแพคเกจnpm
JoWie

คำตอบที่สง่างามอย่างเหลือเชื่อขอบคุณสำหรับการแบ่งปัน! รูปแบบหนึ่ง: new MyErr (arg1, arg2, new Error())และในตัวสร้าง MyErr เราใช้Object.assignเพื่อกำหนดคุณสมบัติหาเรื่องล่าสุดให้กับthis
Peeyush Kushwaha

ฉันชอบสิ่งนี้. คุณข้ามข้อ จำกัด โดยใช้การห่อหุ้มแทนการสืบทอด
Jenny O'Reilly

13

อย่างที่บางคนพูดมันค่อนข้างง่ายกับ 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;
  }
}

10

โซลูชันของฉันนั้นง่ายกว่าคำตอบอื่น ๆ ที่ให้ไว้และไม่มีข้อเสีย

มันรักษาข้อผิดพลาดต้นแบบโซ่และคุณสมบัติทั้งหมดในข้อผิดพลาดโดยไม่จำเป็นต้องมีความรู้เฉพาะของพวกเขา ผ่านการทดสอบใน 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;
};

1
ตามที่ระบุไว้ในคำตอบของฉันการแก้ปัญหานี้อาจทำให้เกิดปัญหาใน Firefox หรือเบราว์เซอร์อื่น ๆ ที่บันทึกเฉพาะบรรทัดแรกของการติดตามสแต็กในคอนโซล
Jonathan Benn

นี่เป็นคำตอบเดียวที่ฉันพบว่าทำงานได้ดีกับ ES5 (การใช้คลาส ES6 ทำงานได้ดีเช่นกัน) ข้อผิดพลาดแสดงได้อย่างชัดเจนใน Chromium DevTools มากกว่าคำตอบอื่น ๆ
Ben Gubler

หมายเหตุ: หากคุณกำลังใช้โซลูชันนี้กับ TypeScript คุณต้องเรียกใช้throw CustomError('err')แทนthrow new CustomError('err')
Ben Gubler

8

ในตัวอย่างด้านบน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';

5

ในโหนดตามที่คนอื่นพูดมันง่าย:

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

3

ฉันแค่อยากจะเพิ่มสิ่งที่คนอื่นพูดไปแล้ว:

เพื่อให้แน่ใจว่าคลาสข้อผิดพลาดแบบกำหนดเองแสดงขึ้นอย่างถูกต้องในการติดตามสแต็กคุณต้องตั้งค่าคุณสมบัติชื่อต้นแบบของคลาสข้อผิดพลาดแบบกำหนดเองเป็นคุณสมบัติชื่อคลาสข้อผิดพลาดแบบกำหนดเอง นี่คือสิ่งที่ฉันหมายถึง:

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)

5
สิ่งนี้ไม่เขียนทับคุณสมบัติชื่อสำหรับอินสแตนซ์ข้อผิดพลาดทั้งหมดหรือไม่
panzi

@panzi คุณถูกต้อง ฉันได้แก้ไขข้อบกพร่องเล็ก ๆ ของฉันแล้ว ขอบคุณสำหรับหัวขึ้น!
Gautham C.

3

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__=ไม่


2

เนื่องจากข้อยกเว้น 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);
Eric

@Eric ฉันเห็นด้วย แต่นี่ดูเหมือนจะเป็นข้อ จำกัด เล็ก ๆ น้อย ๆ ฉันไม่เคยต้องการยกตัวอย่างวัตถุยกเว้นล่วงหน้า (ยกเว้นการเขียนโปรแกรมเมตาใช้เช่นรหัสตัวอย่างของฉันด้านบน) นี่เป็นปัญหาสำหรับคุณจริงๆเหรอ?
Jonathan Benn

ใช่พฤติกรรมดูเหมือนจะเหมือนกันดังนั้นฉันจะเปลี่ยนคำตอบของฉัน ฉันไม่ได้ 100% มีความพึงพอใจกับกองติดตามซึ่งนำคุณไปยังข้อผิดพลาด "var" สายบน Firefox และ Chrome
โจนาธานเบนน์

1
@ โจนาธานเบนน์ฉันมางานปาร์ตี้ช้าจริง ๆ บางทีคุณอาจเลือกมาแล้ว ฉันสร้างอินสแตนซ์ของออบเจ็กต์ข้อยกเว้นบ่อยครั้งเมื่อฉันใช้การเขียนโปรแกรมแบบอะซิงโครนัสและสัญญา ต่อไปนี้ @ ชื่อเอริคของผมมักจะใช้แล้วm = new ... Promise.reject(m)มันไม่จำเป็น แต่รหัสอ่านง่ายกว่า
BaldEagle

1
@ JonathanBenn: (เขาเขา) ในวันที่ 14 ตุลาคมคุณดูเหมือนจะคิดว่าเป็นวัตถุยกเว้นก่อนที่จะโยนมันจะหายาก ฉันยกตัวอย่างหนึ่งครั้งที่ฉันทำ ฉันจะไม่พูดว่ามันเป็นเรื่องธรรมดา แต่ก็มีประโยชน์เมื่อฉันต้องการมัน และโค้ดของฉันสามารถอ่านได้มากกว่าเนื่องจากอินสแตนซ์ทั้งหมดในบรรทัดเดียวและการปฏิเสธทั้งหมดในอีก ฉันหวังว่าจะเป็นเช่นนั้น!
BaldEagle

2

ตามที่ระบุไว้ในคำตอบของ 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 ได้โดยส่วนตัวฉันไม่แน่ใจว่าความซับซ้อนของวิธีการนั้นคุ้มค่าหรือไม่


1

วิธีการทำเช่นนี้คือการคืนค่าผลลัพธ์ของตัวสร้างจากตัวสร้างรวมถึงการตั้งค่าต้นแบบในวิธีที่ซับซ้อนตามปกติของจาวาสคริปต์:

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และ
  • stacktrace มีบรรทัดเพิ่มเติมที่ไม่จำเป็นจริงๆ

ปัญหาแรกสามารถแก้ไขได้ด้วยการวนซ้ำผ่านคุณสมบัติที่ไม่สามารถนับได้ทั้งหมดของข้อผิดพลาดโดยใช้เคล็ดลับในคำตอบนี้: เป็นไปได้ไหมที่จะได้รับชื่อคุณสมบัติที่สืบทอดไม่ได้นับของวัตถุ? แต่ไม่รองรับเช่น <9 ปัญหาที่สองสามารถแก้ไขได้โดยการลบบรรทัดนั้นในการติดตามสแต็ก แต่ฉันไม่แน่ใจว่าจะทำอย่างปลอดภัยได้อย่างไร (อาจแค่ลบบรรทัดที่สองของ e.stack.toString () ??)


ฉันสร้างโมดูลที่สามารถขยายวัตถุจาวาสคริปต์ที่ใช้งานบ่อยที่สุดรวมถึงข้อผิดพลาด เป็นผู้ใหญ่ที่สวย ณ จุดนี้github.com/fresheneesz/proto
BT

1

ตัวอย่างแสดงให้เห็นทุกอย่าง

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

0

ฉันจะย้อนกลับไปดูว่าทำไมคุณถึงอยากทำเช่นนั้น? ฉันคิดว่าประเด็นคือการจัดการกับข้อผิดพลาดที่แตกต่างกันแตกต่างกัน

ตัวอย่างเช่นใน 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
}

1
การขว้างสิ่งของไม่ใช่ความคิดที่ดี คุณไม่มีการerror.stackใช้เครื่องมือมาตรฐานไม่สามารถใช้งานได้ ฯลฯ วิธีที่ดีกว่าคือการเพิ่มคุณสมบัติให้กับอินสแตนซ์ข้อผิดพลาดเช่นvar e = new Error(); e.type = "validation"; ...
timruffles

0

Custom Decorator Error

สิ่งนี้มีพื้นฐานมาจากคำตอบของจอร์จเบลีย์แต่ขยายและทำให้แนวคิดดั้งเดิมง่ายขึ้น มันเขียนใน 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เป็นทางเลือกเมื่อสร้างข้อผิดพลาดที่กำหนดเอง


0

หากคุณไม่สนใจเกี่ยวกับการแสดงข้อผิดพลาดนี่เป็นสิ่งที่เล็กที่สุดที่คุณสามารถทำได้

Object.setPrototypeOf(MyError.prototype, Error.prototype)
function MyError(message) {
    const error = new Error(message)
    Object.setPrototypeOf(error, MyError.prototype);
    return error
}

คุณสามารถใช้มันได้โดยไม่ใหม่ MyError (ข้อความ)

ด้วยการเปลี่ยนต้นแบบหลังจากตัวสร้างข้อผิดพลาดเรียกว่าเราไม่จำเป็นต้องตั้งค่า callstack และข้อความ


0

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.