ข้อผิดพลาดในการขยาย Javascript ด้วยไวยากรณ์ ES6 และ Babel


132

ฉันพยายามขยายข้อผิดพลาดด้วย ES6 และ Babel มันไม่ได้ผล

class MyError extends Error {
  constructor(m) {
    super(m);
  }
}

var error = new Error("ll");
var myerror = new MyError("ll");
console.log(error.message) //shows up correctly
console.log(myerror.message) //shows empty string

วัตถุข้อผิดพลาดไม่เคยได้รับชุดข้อความที่ถูกต้อง

ลองใน Babel REPL

ตอนนี้ฉันได้เห็นวิธีแก้ปัญหาบางอย่างใน SO แล้ว ( ตัวอย่างเช่นที่นี่ ) แต่ทั้งหมดดูเหมือนจะไม่เป็น ES6-y ทำยังไงให้สวยแบบ ES6? (ที่ทำงานใน Babel)


2
ตามลิงค์ของคุณไปยัง Babel REPL ดูเหมือนจะบ่งบอกว่าตอนนี้ทำงานได้ถูกต้อง ฉันคิดว่ามันเป็นจุดบกพร่องใน Babel ที่ได้รับการแก้ไขแล้ว
kybernetikos

คำตอบ:


188

จากคำตอบของ Karel Bílekฉันจะทำการเปลี่ยนแปลงเล็กน้อยกับconstructor:

class ExtendableError extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
    if (typeof Error.captureStackTrace === 'function') {
      Error.captureStackTrace(this, this.constructor);
    } else { 
      this.stack = (new Error(message)).stack; 
    }
  }
}    

// now I can extend

class MyError extends ExtendableError {}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

นี้จะพิมพ์ในกองและไม่ทั่วไปMyErrorError

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

นอกจากนี้ยังจะใช้captureStackTraceหากมี

ด้วย Babel 6 คุณต้องมีการขยายในตัว ( npm ) เพื่อให้สิ่งนี้ทำงานได้


1
@MichaelYounkin if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, this.constructor.name) } else { this.stack = (new Error(message)).stack; } . ฉันขอยืนยันว่าควรใช้ฟังก์ชันนี้หากมีให้ใช้งานจะดีกว่าเนื่องจากมีสแต็กการโทร 'เนทีฟ' มากกว่าและพิมพ์ชื่อของวัตถุข้อผิดพลาด แน่นอนว่าหากคุณใช้สิ่งนี้เพียงอย่างเดียวบนฝั่งเซิร์ฟเวอร์ด้วย (โหนด) ก็ไม่ใช่ปัญหาเช่นกัน
Lee Benson

4
@MichaelYounkin ฉันไม่คิดว่าสิ่งนี้สมควรได้รับการโหวตลด OP พูดถึงการขยายข้อผิดพลาดใน ES6 ตามตรรกะนั้นES6 เกือบทั้งหมดหายไปจากเบราว์เซอร์อย่างน้อยหนึ่งตัว โซลูชันของฉัน (พร้อมการตรวจสอบ func ที่เพิ่มเข้ามา) ให้ความครอบคลุมดั้งเดิมในเบราว์เซอร์ที่ใช้กันอย่างแพร่หลายถอยกลับในทุก ๆ ด้านและความครอบคลุม 100% ใน Node.js ฉันยอมรับว่าหากคุณใช้ชื่อคลาส error อย่างสม่ำเสมอthis.stack = (new Error(message)).stackคุณก็จะเข้าใจว่า ... แต่ในทางปฏิบัตินี่อาจไม่ใช่เรื่องใหญ่
Lee Benson

6
สิ่งนี้ใช้ไม่ได้ใน Babel 6:new MyError('foo') instanceof MyError === false
Sukima

5
โค้ดนี้รวบรวมล่วงหน้าด้วย babel เป็นโมดูล NPM: extendable-error-class npmjs.com/package/extendable-error-classซึ่งสะดวกในการหลีกเลี่ยงการพึ่งพา babel-plugin-transform-builtin-
expand

3
this.message = message;ซ้ำซ้อนกับsuper(message);
mathieug

39

รวมคำตอบนี้ , คำตอบนี้และรหัสนี้ผมได้ทำนี้มีขนาดเล็ก "ผู้ช่วย" ชั้นที่ดูเหมือนว่าจะทำงานได้ดี

class ExtendableError extends Error {
  constructor(message) {
    super();
    this.message = message; 
    this.stack = (new Error()).stack;
    this.name = this.constructor.name;
  }
}    

// now I can extend

class MyError extends ExtendableError {
  constructor(m) {   
    super(m);
  }
}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

ลองใน REPL


1
this.stack = (new Error(message)).stack;- มิฉะนั้นข้อความจะหายไปจาก stacktrace
Lee Benson

3
ฉันสงสัยว่าจะไม่ได้ผลตามที่ต้องการเพราะถ้าคุณทำ: console.log (myerror instanceof ExtendableError); มันยังพูดเท็จ ..
Mauno Vähä

4
ปัญหาเดียวกันการใช้ instance ของ CustomError ไม่ได้ผลอะไรคือจุดขยายหากคุณไม่สามารถใช้ instanceof
gre

สามารถปรับปรุงได้โดยเพิ่มmessageในตัวสร้างสแต็กข้อผิดพลาดดังนั้นจึงแสดงข้อความที่ถูกต้องที่ด้านบนของสแต็กเมื่อโยน:this.stack = (new Error(message)).stack;
เซบาสเตียน

1
myerror.nameตอนนี้ส่งคืน "ข้อผิดพลาด" ไม่แน่ใจว่าสิ่งนี้เกี่ยวข้องกับ babel รุ่นหลังหรือไม่ดูคำตอบของ @ sukima ด้านล่าง
Eric H.

27

เพื่อให้สิ่งนี้สงบลง ใน Babel 6 มันเป็นที่ชัดเจนว่านักพัฒนาไม่สนับสนุนการยื่นออกมาจากในตัว. แม้ว่าเคล็ดลับนี้จะไม่ช่วยเหลือเกี่ยวกับสิ่งที่ชอบMap, Setฯลฯ Errorมันไม่ทำงานสำหรับ สิ่งนี้มีความสำคัญเนื่องจากเป็นหนึ่งในแนวคิดหลักของภาษาที่สามารถทำให้เกิดข้อยกเว้นคือการอนุญาตให้มีข้อผิดพลาดที่กำหนดเอง นี้เป็นสิ่งสำคัญเป็นทวีคูณสัญญากลายเป็นประโยชน์มากขึ้นเนื่องจากพวกเขาจะได้รับการออกแบบมาเพื่อปฏิเสธข้อผิดพลาด

ความจริงที่น่าเศร้าคือคุณยังคงต้องทำแบบเดิมใน ES2015

ตัวอย่างใน Babel REPL

รูปแบบข้อผิดพลาดที่กำหนดเอง

class MyError {
  constructor(message) {
    this.name = 'MyError';
    this.message = message;
    this.stack = new Error().stack; // Optional
  }
}
MyError.prototype = Object.create(Error.prototype);

ในทางกลับกันมีปลั๊กอินสำหรับ Babel 6 เพื่ออนุญาตสิ่งนี้

https://www.npmjs.com/package/babel-plugin-transform-builtin-extend

อัปเดต: (ณ วันที่ 2016-09-29) หลังจากการทดสอบบางส่วนปรากฏว่า babel.io ไม่ได้บันทึกบัญชีสำหรับการยืนยันทั้งหมดอย่างถูกต้อง (ขยายจากข้อผิดพลาดเพิ่มเติมที่กำหนดเอง) แต่ใน Ember.JS การขยายข้อผิดพลาดทำงานตามที่คาดไว้: https://ember-twiddle.com/d88555a6f408174df0a4c8e0fd6b27ce


ใช่ด้วยปลั๊กอิน babel ที่เชื่อมโยงจะทำงานได้อย่างถูกต้องกับคำตอบที่ยอมรับ (อย่างไรก็ตามไฟล์ผลลัพธ์ไม่ทำงานใน Node เนื่องจากไม่มีการสะท้อนกลับ)
Karel Bílek

เช่นเดียวกับความอยากรู้อยากเห็นถ้าสเปค ES2016 บอกว่าบิวด์อินสามารถขยายได้ทำไม vms เช่น v8 และ es5 ของบาเบลจึงเกิดขึ้นกับมัน? ไม่ใช่ความคาดหวังที่สมเหตุสมผลที่คลาสสามารถขยายคลาสในลักษณะเดียวกับที่โซ่ต้นแบบสามารถมาจากต้นแบบอื่น ๆ ได้หรือไม่? ทำไมต้องมีเซราโมนีดังกล่าวซ่อนอยู่ในปลั๊กอิน?
Sukima

สิ่งนี้น่าผิดหวังอย่างยิ่งเมื่อกรณีการใช้งานส่วนใหญ่เพียงแค่ต้องการสร้างวัตถุง่ายๆที่มีพฤติกรรมร่วมกัน ข้อผิดพลาดที่กำหนดเองที่สามารถError.toString()ใช้ได้ ความจำเป็นในการทำห่วงและการหมุนพิเศษเพื่อให้บรรลุสิ่งนี้หมายความว่านักพัฒนาส่วนใหญ่จะหลีกเลี่ยงและหันไปใช้แนวทางปฏิบัติที่ไม่ดีเช่นการโยนสตริงแทนข้อผิดพลาด หรือสร้างแผนที่ของตัวเองให้เหมือนวัตถุ ทำไมต้องยับยั้งวิธีการ OOP ดังกล่าว?
Sukima

ในความคิดของฉันพวกเขาไม่ได้ต่อต้านมันเป็นเพียงปัญหาทางเทคนิคบางอย่าง ฉันไม่แน่ใจว่า! คุณสามารถถามพวกเขาได้ :) โครงการค่อนข้างเปิดกว้าง
Karel Bílek

จนถึงตอนนี้คำตอบทั้งหมดจากคำถามจำลองในหัวข้อนี้ถูกทิ้งไว้ที่ "babel ไม่สนับสนุน" ฉันคิดว่านั่นคือจุดสิ้นสุดของการสนทนา เนื้อของฉันคือการขาดการสนับสนุนทำให้สำนวน OOP ทั่วไปเป็นเรื่องยากและฉันยังต้องต่อสู้กับผู้ร่วมงานเพื่อให้พวกเขาข้ามผ่านหม้อต้ม ฉันแค่หวังว่านี่เป็นทางเลือกที่สะอาด ดูเหมือนว่าการเพิ่มปลั๊กอินจะเป็นทางเลือกที่ดีที่สุด
Sukima

15

แก้ไข : ทำลายการเปลี่ยนแปลงในtypescript 2.1

การขยายในตัวเช่น Error, Array และ Map อาจใช้ไม่ได้อีกต่อไป

ตามคำแนะนำคุณสามารถปรับต้นแบบด้วยตนเองได้ทันทีหลังจากการโทรขั้นสูง (... )

การแก้ไขคำตอบเดิมของ Lee Benson ใช้ได้ผลกับฉันเล็กน้อย นอกจากนี้ยังเพิ่มstackและวิธีการเพิ่มเติมของExtendableErrorคลาสให้กับอินสแตนซ์

class ExtendableError extends Error {
   constructor(message) {
       super(message);
       Object.setPrototypeOf(this, ExtendableError.prototype);
       this.name = this.constructor.name;
   }
   
   dump() {
       return { message: this.message, stack: this.stack }
   }
 }    

class MyError extends ExtendableError {
    constructor(message) {
        super(message);
        Object.setPrototypeOf(this, MyError.prototype);
    }
}

var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror.dump());
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);

1
คุณต้องเรียกObject.setPrototypeOfตัวMyErrorสร้างด้วย stackoverflow.com/a/41102306/186334 github.com/Microsoft/TypeScript-wiki/blob/master/…
CallMeLaNN

10

ด้วยการเปลี่ยนแปลงล่าสุดใน babel 6 ฉันพบว่าการเปลี่ยนแปลงในตัว - ขยายไม่ทำงานอีกต่อไป ฉันลงเอยด้วยการใช้วิธีการแบบผสมผสานนี้:

export default class MyError {
    constructor (message) {
        this.name = this.constructor.name;
        this.message = message;
        this.stack = (new Error(message)).stack;
    }
}

MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;

และ

import MyError from './MyError';

export default class MyChildError extends MyError {
    constructor (message) {
        super(message);
    }
}

ผลการทดสอบทั้งหมดนี้ผ่าน:

const sut = new MyError('error message');
expect(sut.message).toBe('error message');
expect(sut).toBeInstanceOf(Error);
expect(sut).toBeInstanceOf(MyError);
expect(sut.name).toBe('MyError');
expect(typeof sut.stack).toBe('string');

const sut = new MyChildError('error message');
expect(sut.message).toBe('error message');
expect(sut).toBeInstanceOf(Error);
expect(sut).toBeInstanceOf(MyError);
expect(sut).toBeInstanceOf(MyChildError);
expect(sut.name).toBe('MyChildError');
expect(typeof sut.stack).toBe('string');

6

quoting

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

ไม่จำเป็นต้องมีthis.stack = (new Error()).stack;เคล็ดลับขอบคุณที่super()โทร

แม้ว่ารหัสดังกล่าวไม่สามารถส่งออกกองติดตามเว้นแต่this.stack = (new Error()).stack;หรือError.captureStackTrace(this, this.constructor.name);ถูกเรียกในBabel IMO อาจเป็นปัญหาหนึ่งที่นี่

จริงๆแล้วการติดตามสแต็กสามารถส่งออกได้ภายใต้Chrome consoleและNode.js v4.2.1ด้วยข้อมูลโค้ดนี้

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

var myerr = new MyError("test");
console.log(myerr.stack);
console.log(myerr);

ผลลัพธ์ของChrome console.

MyError: test
    at MyError (<anonymous>:3:28)
    at <anonymous>:12:19
    at Object.InjectedScript._evaluateOn (<anonymous>:875:140)
    at Object.InjectedScript._evaluateAndWrap (<anonymous>:808:34)
    at Object.InjectedScript.evaluate (<anonymous>:664:21)

ผลลัพธ์ของ Node.js

MyError: test
    at MyError (/home/bsadmin/test/test.js:5:8)
    at Object.<anonymous> (/home/bsadmin/test/test.js:11:13)
    at Module._compile (module.js:435:26)
    at Object.Module._extensions..js (module.js:442:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:311:12)
    at Function.Module.runMain (module.js:467:10)
    at startup (node.js:134:18)
    at node.js:961:3

4

นอกจากคำตอบ @zangw แล้วคุณสามารถกำหนดข้อผิดพลาดได้ดังนี้:

'use strict';

class UserError extends Error {
  constructor(msg) {
    super(msg);
    this.name = this.constructor.name;
  }
}

// define errors
class MyError extends UserError {}
class MyOtherError extends UserError {}

console.log(new MyError instanceof Error); // true

throw new MyError('My message');

ซึ่งจะแสดงชื่อข้อความและ stacktrace ที่ถูกต้อง:

MyError: My message
    at UserError (/Users/honzicek/Projects/api/temp.js:5:10)
    at MyError (/Users/honzicek/Projects/api/temp.js:10:1)
    at Object.<anonymous> (/Users/honzicek/Projects/api/temp.js:14:7)
    at Module._compile (module.js:434:26)
    at Object.Module._extensions..js (module.js:452:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:475:10)
    at startup (node.js:117:18)
    at node.js:951:3

4
สิ่งนี้ใช้ไม่ได้: new MyError('foo') instanceof MyError === false.
Sukima

1
Node.js v7.7.3มันไม่เกี่ยวกับ
Gunar Gessner

2

ฉันกำลังพยายามขยายข้อผิดพลาดด้วย ES6

นั่นclass MyError extends Error {…}ไวยากรณ์ที่ถูกต้อง

สังเกตว่าทรานสไพเลอร์ยังคงมีปัญหากับการสืบทอดจากอ็อบเจ็กต์ในตัว ในกรณีของคุณ

var err = super(m);
Object.assign(this, err);

ดูเหมือนว่าจะแก้ไขปัญหาได้


ความจริง! แต่ยังไม่ได้ตั้งค่าข้อความ - ฉันจะเขียนตัวอย่างใหม่
Karel Bílek

ฉันได้เขียนตัวอย่างใหม่แล้ว
Karel Bílek


"super (m)" จะส่งคืนวัตถุว่างเปล่าอย่างเห็นได้ชัด ดังนั้น Object.assign ไม่ช่วย
Karel Bílek

@ KarelBílek: คุณใช้เบราว์เซอร์อะไร? Error.call()ส่งคืนอินสแตนซ์ข้อผิดพลาดใหม่ให้ฉัน
Bergi

2

ให้นี้เป็นคำตอบที่ได้รับการยอมรับไม่ทำงานคุณก็สามารถใช้โรงงานเป็นทางเลือก ( repl ):

function ErrorFactory(name) {
   return class AppError extends Error {
    constructor(message) {
      super(message);
      this.name = name;
      this.message = message; 
      if (typeof Error.captureStackTrace === 'function') {
        Error.captureStackTrace(this, this.constructor);
      } else { 
        this.stack = (new Error(message)).stack; 
      }
    }
  }     
}

// now I can extend
const MyError = ErrorFactory("MyError");


var myerror = new MyError("ll");
console.log(myerror.message);
console.log(myerror instanceof Error);
console.log(myerror.name);
console.log(myerror.stack);


คำตอบที่ยอมรับยังคงใช้ได้สำหรับฉันหากคุณมีปลั๊กอิน Babel ที่จำเป็น ขอบคุณสำหรับคำตอบนี้เช่นกัน!
Karel Bílek

2

ฉันชอบไวยากรณ์ที่แข็งแกร่งมากกว่าที่อธิบายไว้ข้างต้น วิธีการเพิ่มเติมในประเภทข้อผิดพลาดจะช่วยให้คุณสร้างสวยconsole.logหรืออย่างอื่นได้

export class CustomError extends Error {
    /**
     * @param {string} message
     * @param {number} [code = 0]
     */
    constructor(message, code = 0) {
        super();

        /**
         * @type {string}
         * @readonly
         */
        this.message = message;

        /**
         * @type {number}
         * @readonly
         */
        this.code = code;

        /**
         * @type {string}
         * @readonly
         */
        this.name = this.constructor.name;

        /**
         * @type {string}
         * @readonly
         */
        this.stack = CustomError.createStack(this);
    }

    /**
     * @return {string}
     */
    toString() {
        return this.getPrettyMessage();
    }

    /**
     * @return {string}
     */
    getPrettyMessage() {
        return `${this.message} Code: ${this.code}.`;
    }

    /**
     * @param {CustomError} error
     * @return {string}
     * @private
     */
    static createStack(error) {
        return typeof Error.captureStackTrace === 'function'
            ? Error.captureStackTrace(error, error.constructor)
            : (new Error()).stack;
    }
}

ในการทดสอบรหัสนี้คุณสามารถเรียกใช้สิ่งที่คล้ายกัน:

try {
    throw new CustomError('Custom error was thrown!');
} catch (e) {
    const message = e.getPrettyMessage();

    console.warn(message);
}

CustomErrorยินดีต้อนรับการขยายประเภท เป็นไปได้ที่จะเพิ่มฟังก์ชันการทำงานเฉพาะบางอย่างให้กับประเภทเพิ่มเติมหรือแทนที่ที่มีอยู่ ตัวอย่างเช่น.

export class RequestError extends CustomError {
    /**
     * @param {string} message
     * @param {string} requestUrl
     * @param {number} [code = 0]
     */
    constructor(message, requestUrl, code = 0) {
        super(message, code);

        /**
         * @type {string}
         * @readonly
         */
        this.requestUrl = requestUrl;
    }

    /**
     * @return {string}
     */
    getPrettyMessage() {
        const base = super.getPrettyMessage();

        return `${base} Request URL: ${this.requestUrl}.`;
    }
}

1

ดังที่ @sukima กล่าวถึงคุณไม่สามารถขยาย JS ดั้งเดิมได้ คำถามของ OP ไม่สามารถตอบได้

คล้ายกับคำตอบ Melbourne2991 ของผมไม่ใช้โรงงานค่อนข้าง แต่ตามคำแนะนำ MDN สำหรับประเภทการผิดพลาดของลูกค้า

function extendError(className){
  function CustomError(message){
    this.name = className;
    this.message = message;
    this.stack = new Error().stack; // Optional
  }
  CustomError.prototype = Object.create(Error.prototype);
  CustomError.prototype.constructor = CustomError;
  return CustomError;
}

1

สิ่งนี้ใช้ได้กับฉัน:

/**
 * @class AuthorizationError
 * @extends {Error}
 */
export class AuthorizationError extends Error {
    message = 'UNAUTHORIZED';
    name = 'AuthorizationError';
}

0

ไม่ได้ใช้ Babel แต่ใน ES6 ธรรมดาสิ่งต่อไปนี้ดูเหมือนจะใช้ได้ดีสำหรับฉัน:

class CustomError extends Error {
    constructor(...args) {
        super(...args);
        this.name = this.constructor.name;
    }
}

การทดสอบจาก REPL:

> const ce = new CustomError('foobar');
> ce.name
'CustomError'
> ce.message
'foobar'
> ce instanceof CustomError
true
> ce.stack
'CustomError: foobar\n    at CustomError (repl:3:1)\n ...'

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


0

ฉันปรับปรุงวิธีแก้ปัญหาของ @Lee Benson ด้วยวิธีนี้:

extendableError.js

class ExtendableError extends Error {
    constructor(message, errorCode) {
        super(message);
        this.name = this.constructor.name;
        this.errorCode = errorCode
        if (typeof Error.captureStackTrace === 'function') {
            Error.captureStackTrace(this, this.constructor);
        } else {
            this.stack = (new Error(message)).stack;
        }
    }


}

export default ExtendableError

ตัวอย่างของข้อผิดพลาด

import ExtendableError from './ExtendableError'

const AuthorizationErrors = {
    NOT_AUTHORIZED: 401,
    BAD_PROFILE_TYPE: 402,
    ROLE_NOT_ATTRIBUTED: 403
}

class AuthorizationError extends ExtendableError {
    static errors = AuthorizationErrors 
}

export default AuthorizationError 

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

new AuthorizationError ("The user must be a seller to be able to do a discount", AuthorizationError.errors.BAD_PROFILE_TYPE )
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.