Apa cara yang baik untuk memperpanjang Kesalahan dalam JavaScript?

369

Saya ingin membuang beberapa hal dalam kode JS saya dan saya ingin mereka menjadi contoh Kesalahan, tetapi saya juga ingin mereka menjadi sesuatu yang lain.

Dalam Python, biasanya, seseorang akan subkelas Pengecualian.

Apa hal yang tepat untuk dilakukan di JS?

Josh Gibson
sumber

Jawaban:

218

Satu-satunya objek Galat bidang standar miliki adalah messageproperti. (Lihat MDN , atau Spesifikasi Bahasa EcmaScript, bagian 15.11) Yang lainnya adalah platform spesifik.

Sebagian besar lingkungan mengatur stackproperti, tetapi fileNamedan lineNumberpraktis tidak berguna untuk digunakan dalam warisan.

Jadi, pendekatan minimalis adalah:

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

Anda dapat mengendus tumpukan, melepaskan elemen yang tidak diinginkan darinya dan mengekstrak informasi seperti fileName dan lineNumber, tetapi untuk melakukannya diperlukan informasi tentang platform yang saat ini dijalankan JavaScript. Sebagian besar kasus yang tidak perlu - dan Anda dapat melakukannya di rumah sakit jika Anda benar-benar menginginkannya.

Safari adalah pengecualian penting. Tidak ada stackproperti, tetapi throwset kata kunci sourceURLdan lineproperti objek yang dilemparkan. Hal-hal itu dijamin benar.

Kasus pengujian yang saya gunakan dapat ditemukan di sini: JavaScript membuat perbandingan objek Kesalahan .

Tero
sumber
19
Anda dapat memindahkan bagian this.name = 'MyError' luar fungsi dan mengubahnya ke MyError.prototype.name = 'MyError'.
Daniel Beardsley
36
Ini adalah satu-satunya jawaban yang benar di sini, meskipun sebagai masalah gaya, saya mungkin akan menulisnya seperti ini. function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
kybernetikos
11
Saya akan menambahkan MyError.prototype.constructor = MyErrorjuga.
Bharat Khatri
3
di ES6 Error.call (this, message); harus menginisialisasi this, kan?
4esn0k
4
MyError.prototype = Object.create (Error.prototype);
Robin suka burung
170

Dalam ES6:

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

sumber

Mohsen
sumber
53
Layak disebutkan bahwa ini tidak berfungsi jika Anda menggunakan fitur ES6 melalui transpiler, seperti Babel, karena subclass harus memperluas kelas.
aaaidan
6
Jika Anda menggunakan babel dan berada di node> 5.x Anda tidak boleh menggunakan preset es2015 tetapi npmjs.com/package/babel-preset-node5 akan memungkinkan Anda untuk menggunakan es6 asli meluas plus lebih banyak lagi
Ace
2
Ini adalah cara terbaik untuk melakukannya bila memungkinkan. Kesalahan khusus berperilaku lebih seperti kesalahan biasa di Chrome dan Firefox (dan mungkin juga browser lain).
Matt Browne
2
Untuk browser, perhatikan bahwa Anda dapat mendeteksi dukungan untuk kelas saat runtime dan kembali ke versi non-kelas. Kode deteksi:var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
Matt Browne
31
Untuk kemudahan perawatan , gunakan this.name = this.constructor.name;saja.
Константин Ван
53

Pendeknya:

  • Jika Anda menggunakan ES6 tanpa transpiler :

    class CustomError extends Error { /* ... */}
  • Jika Anda menggunakan Babel transpiler :

Opsi 1: gunakan babel-plugin-transform-builtin-extended

Opsi 2: lakukan sendiri (terinspirasi dari perpustakaan yang sama)

    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);
  • Jika Anda menggunakan ES5 murni :

    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;
    }
  • Alternatif: gunakan kerangka kerja Classtrophobic

Penjelasan:

Mengapa memperluas kelas Kesalahan menggunakan ES6 dan Babel merupakan masalah?

Karena turunan CustomError tidak lagi dikenali.

class CustomError extends Error {}
console.log(new CustomError('test') instanceof Error);// true
console.log(new CustomError('test') instanceof CustomError);// false

Bahkan, dari dokumentasi resmi Babel, Anda tidak dapat memperpanjang built-in kelas JavaScript seperti Date, Array, DOMatau Error.

Masalahnya dijelaskan di sini:

Bagaimana dengan jawaban SO lainnya?

Semua jawaban yang diberikan memperbaiki instanceofmasalah tetapi Anda kehilangan kesalahan reguler console.log:

console.log(new CustomError('test'));
// output:
// CustomError {name: "MyError", message: "test", stack: "Error↵    at CustomError (<anonymous>:4:19)↵    at <anonymous>:1:5"}

Sedangkan menggunakan metode yang disebutkan di atas, tidak hanya Anda memperbaiki instanceofmasalah tetapi Anda juga menyimpan kesalahan rutin console.log:

console.log(new CustomError('test'));
// output:
// Error: test
//     at CustomError (<anonymous>:2:32)
//     at <anonymous>:1:5
JBE
sumber
1
class CustomError extends Error { /* ... */}tidak benar menangani argumen khusus vendor ( lineNumber, dll), 'Memperluas Kesalahan dalam Javascript dengan sintaks ES6' adalah spesifik Babel, menggunakan solusi ES5 Anda constdan tidak menangani argumen khusus.
Indolering
Jawabannya sangat lengkap.
Daniele Orlando
Ini sebenarnya memberikan solusi yang paling komprehensif dan menjelaskan mengapa berbagai bagian diperlukan. Terima kasih banyak JBE!
AndrewSouthpaw
Ini membantu saya dalam memecahkan masalah dengan mewarisi dari "Kesalahan". Itu adalah mimpi buruk dua jam!
Iulian Pinzaru
2
Patut dicatat bahwa masalah dengan console.log(new CustomError('test') instanceof CustomError);// falseitu benar pada saat penulisan tetapi sekarang telah diselesaikan. Sebenarnya masalah yang ditautkan dalam jawaban telah diselesaikan dan kami dapat menguji perilaku yang benar di sini dan dengan menempelkan kode dalam REPL dan melihat bagaimana hal itu ditransformasikan dengan benar untuk menjadi contoh dengan rantai prototipe yang benar.
Nobita
44

Sunting: Silakan baca komentar. Ternyata ini hanya berfungsi dengan baik di V8 (Chrome / Node.JS) Maksud saya adalah untuk memberikan solusi lintas-browser, yang akan bekerja di semua browser, dan menyediakan jejak tumpukan di mana ada dukungan.

Sunting: Saya membuat Wiki Komunitas ini untuk memungkinkan pengeditan lebih lanjut.

Solusi untuk V8 (Chrome / Node.JS), berfungsi di Firefox, dan dapat dimodifikasi agar berfungsi sebagian besar dengan benar di IE. (lihat akhir posting)

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
}

Posting asli pada "Tunjukkan kodenya!"

Versi pendek:

function UserError(message) {
  this.constructor.prototype.__proto__ = Error.prototype
  Error.captureStackTrace(this, this.constructor)
  this.name = this.constructor.name
  this.message = message
}

Saya tetap this.constructor.prototype.__proto__ = Error.prototypedi dalam fungsi untuk menyimpan semua kode. Tetapi Anda juga dapat mengganti this.constructordengan UserErrordan itu memungkinkan Anda untuk memindahkan kode ke luar fungsi, sehingga hanya dipanggil sekali.

Jika Anda pergi ke rute itu, pastikan Anda menelepon saluran itu sebelum pertama kali Anda melempar UserError.

Peringatan itu tidak menerapkan fungsi, karena fungsi dibuat terlebih dahulu, apa pun urutannya. Dengan demikian, Anda dapat memindahkan fungsi ke akhir file, tanpa masalah.

Kompatibilitas Browser

Bekerja di Firefox dan Chrome (dan Node.JS) serta memenuhi semua janji.

Internet Explorer gagal sebagai berikut

  • Kesalahan tidak harus err.stackdimulai dengan, jadi "itu bukan salahku".

  • Error.captureStackTrace(this, this.constructor) tidak ada sehingga Anda perlu melakukan hal lain seperti

    if(Error.captureStackTrace) // AKA if not IE
        Error.captureStackTrace(this, this.constructor)
  • toStringtidak ada lagi saat Anda subkelas Error. Jadi, Anda juga perlu menambahkan.

    else
        this.toString = function () { return this.name + ': ' + this.message }
  • IE tidak akan mempertimbangkan UserErrormenjadi instanceof Errorkecuali Anda menjalankan beberapa waktu sebelum Andathrow UserError

    UserError.prototype = Error.prototype
George Bailey
sumber
16
Saya tidak berpikir Firefox sebenarnya memiliki captureStackTrace. Ini adalah ekstensi V8 dan tidak terdefinisi di Firefox untuk saya, saya juga tidak dapat menemukan referensi apa pun di web tentang Firefox yang mendukungnya. (Terima kasih!)
Geoff
5
Error.call(this)memang tidak melakukan apa-apa karena mengembalikan kesalahan daripada memodifikasi this.
kybernetikos
1
Berfungsi bagus untuk Node.js
Rudolf Meijering
1
UserError.prototype = Error.prototypemenyesatkan. Ini tidak melakukan warisan, ini membuat mereka kelas yang sama .
Halcyon
1
Saya percaya Object.setPrototypeOf(this.constructor.prototype, Error.prototype)lebih disukai this.constructor.prototype.__proto__ = Error.prototype, setidaknya untuk browser saat ini.
ChrisV
29

Untuk menghindari pelat ketel untuk setiap jenis kesalahan yang berbeda, saya menggabungkan kebijaksanaan beberapa solusi ke dalam  createErrorTypefungsi:

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

Kemudian Anda dapat mendefinisikan tipe kesalahan baru dengan mudah sebagai berikut:

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';
});
Ruben Verborgh
sumber
Apakah ada alasan Anda masih membutuhkan saluran telepon this.name = name;?
Peter Tseng
@PeterTseng Karena namesudah diatur pada prototipe, itu tidak perlu lagi. Saya menghapusnya. Terima kasih!
Ruben Verborgh
27

Pada 2018 , saya pikir ini adalah cara terbaik; yang mendukung IE9 + dan browser modern.

UPDATE : Lihat tes ini dan repo untuk perbandingan tentang implementasi yang berbeda.

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

Waspadalah juga bahwa __proto__properti tidak digunakan lagi yang banyak digunakan dalam jawaban lain.

Onur Yıldırım
sumber
1
Mengapa Anda menggunakan setPrototypeOf()? Setidaknya menurut MDN, biasanya tidak disarankan untuk menggunakannya jika Anda dapat melakukan hal yang sama dengan hanya menyetel .prototypeproperti pada konstruktor (seperti yang Anda lakukan di elseblok untuk peramban yang tidak memiliki setPrototypeOf).
Matt Browne
Mengubah prototipe suatu objek tidak disarankan secara bersamaan, bukan setPrototypeOf. Tetapi jika Anda masih membutuhkannya (seperti yang diminta OP), Anda harus menggunakan metodologi bawaan. Seperti yang ditunjukkan MDN, ini dianggap sebagai cara yang tepat untuk mengatur prototipe suatu objek. Dengan kata lain, MDN mengatakan jangan mengubah prototipe (karena itu mempengaruhi kinerja dan optimasi) tetapi jika Anda harus, gunakan setPrototypeOf.
Onur Yıldırım
Maksud saya adalah saya tidak berpikir Anda benar-benar perlu mengubah prototipe di sini. Anda cukup menggunakan garis Anda di bagian bawah ( CustomError.prototype = Object.create(Error.prototype)). Juga, Object.setPrototypeOf(CustomError, Error.prototype)adalah menetapkan prototipe konstruktor itu sendiri daripada menentukan prototipe untuk contoh baru CustomError. Bagaimanapun, pada tahun 2016 saya pikir sebenarnya ada cara yang lebih baik untuk memperpanjang kesalahan, meskipun saya masih mencari cara untuk menggunakannya bersama Babel: github.com/loganfsmyth/babel-plugin-transform-builtin-extend/…
Matt Browne
CustomError.prototype = Object.create(Error.prototype)juga mengubah prototipe. Anda harus mengubahnya karena tidak ada logika extended / inherit di ES5. Saya yakin plugin babel yang Anda sebutkan melakukan hal serupa.
Onur Yıldırım
1
Saya membuat intisari yang menunjukkan mengapa menggunakan Object.setPrototypeOftidak masuk akal di sini, setidaknya tidak dengan cara Anda menggunakannya: gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Mungkin Anda bermaksud menulis Object.setPrototypeOf(CustomError.prototype, Error.prototype)- itu akan membuat sedikit lebih masuk akal (meskipun masih tidak memberikan manfaat hanya dengan menetapkan CustomError.prototype).
Matt Browne
19

Demi kelengkapan - hanya karena tidak ada jawaban sebelumnya yang menyebutkan metode ini - jika Anda bekerja dengan Node.js dan tidak perlu peduli dengan kompatibilitas browser, efek yang diinginkan cukup mudah dicapai dengan built in inheritsdari utilmodul ( docs resmi di sini ).

Misalnya, anggap Anda ingin membuat kelas kesalahan khusus yang menggunakan kode kesalahan sebagai argumen pertama dan pesan kesalahan sebagai argumen kedua:

mengajukan 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;

Sekarang Anda dapat instantiate dan lulus / lemparkan 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!');

Perhatikan bahwa, dengan cuplikan ini, jejak tumpukan akan memiliki nama dan baris file yang benar, dan instance kesalahan akan memiliki nama yang benar!

Ini terjadi karena penggunaan captureStackTracemetode, yang membuat stackproperti pada objek target (dalam hal ini, yang CustomErrorsedang dipakai). Untuk detail lebih lanjut tentang cara kerjanya, lihat dokumentasi di sini .

Victor Schröder
sumber
1
this.message = this.message;apakah ini salah atau masih ada hal-hal gila yang tidak saya ketahui tentang JS?
Alex
1
Hei @Alex, Anda benar sekali! Sudah diperbaiki sekarang. Terima kasih!
Victor Schröder
18

Jawaban Crescent Fresh jawaban sangat menyesatkan. Meskipun peringatannya tidak valid, ada batasan lain yang tidak dia tangani.

Pertama, alasan dalam paragraf "Peringatan:" Crescent tidak masuk akal. Penjelasan menyiratkan bahwa pengkodean "sekelompok if (instance error MyError) lain ..." entah bagaimana memberatkan atau bertele-tele dibandingkan dengan beberapa pernyataan tangkapan. Banyak contoh pernyataan dalam satu blok tangkap sama ringkasnya dengan banyak pernyataan tangkap - kode bersih dan ringkas tanpa trik. Ini adalah cara yang bagus untuk meniru penanganan error subtipe-spesifik Java yang bisa dibuang.

WRT "muncul properti pesan dari subkelas tidak diatur", itu tidak terjadi jika Anda menggunakan subkelas Kesalahan dibangun dengan benar. Untuk membuat subclass ErrorX Anda sendiri, cukup salin blok kode yang diawali dengan "var MyError =", mengubah satu kata "MyError" menjadi "ErrorX". (Jika Anda ingin menambahkan metode khusus ke subkelas Anda, ikuti teks sampel).

Batasan nyata dan signifikan dari subclass kesalahan JavaScript adalah bahwa untuk implementasi atau debugers JavaScript yang melacak dan melaporkan jejak stack dan lokasi-instantiasi, seperti FireFox, lokasi dalam implementasi subclass Error Anda sendiri akan dicatat sebagai titik instantiasi dari kelas, sedangkan jika Anda menggunakan Kesalahan langsung, itu akan menjadi lokasi di mana Anda menjalankan "Kesalahan baru (...)"). Pengguna IE mungkin tidak akan pernah memperhatikan, tetapi pengguna Fire Bug pada FF akan melihat nilai nama file dan nomor baris yang tidak berguna yang dilaporkan di samping Kesalahan ini, dan harus menelusuri jejak tumpukan ke elemen # 1 untuk menemukan lokasi instantiasi nyata.

Blaine
sumber
Apakah saya sudah benar - bahwa jika Anda tidak membuat subkelas dan menggunakan Kesalahan baru (...) secara langsung, maka nama dan baris file dilaporkan dengan benar? Dan Anda pada dasarnya mengatakan bahwa pada prakteknya (nyata dan bukan hanya seksi atau dekoratif agak) kesalahan subclassing, tidak masuk akal?
jayarjo
6
Jawaban ini agak membingungkan karena Crescent Fresh'stelah dihapus!
Peter
apakah ini masih terjadi? developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Nomor baris 2 bukan tempat baru dipanggil
Muhammad Umer
13

Bagaimana dengan solusi ini?

Alih-alih melempar Kesalahan khusus Anda menggunakan:

throw new MyError("Oops!");

Anda akan membungkus objek Kesalahan (seperti dekorator):

throw new MyError(Error("Oops!"));

Ini memastikan semua atribut benar, seperti tumpukan, fileName lineNumber, dan lain-lain.

Yang harus Anda lakukan adalah menyalin atribut, atau menentukan getter untuknya. Berikut ini adalah contoh menggunakan getter (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();
};
JoWie
sumber
1
Saya telah merilis solusi ini sebagai paket npm
JoWie
Solusi yang sangat elegan, terima kasih telah berbagi! Satu variasi: new MyErr (arg1, arg2, new Error())dan dalam konstruktor MyErr kita gunakan Object.assignuntuk menetapkan properti arg terakhir kethis
Peeyush Kushwaha
Saya suka ini. Anda melewati batasan dengan menggunakan enkapsulasi alih-alih warisan.
Jenny O'Reilly
13

Seperti yang dikatakan beberapa orang, cukup mudah dengan ES6:

class CustomError extends Error { }

Jadi saya mencobanya di dalam aplikasi saya, (Angular, Typescript) dan ternyata tidak berhasil. Setelah beberapa waktu saya telah menemukan bahwa masalahnya berasal dari Script: O

Lihat https://github.com/Microsoft/TypeScript/issues/13965

Ini sangat mengganggu karena jika Anda melakukannya:

class CustomError extends Error {}


try {
  throw new CustomError()
} catch(e) {
  if (e instanceof CustomError) {
    console.log('Custom error');
  } else {
    console.log('Basic error');
  }
}

Di node atau langsung ke browser Anda, ia akan menampilkan: Custom error

Cobalah untuk menjalankannya dengan Typescript di proyek Anda di pada taman bermain Typecript, itu akan menampilkan Basic error...

Solusinya adalah dengan melakukan hal berikut:

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;
  }
}
maxime1992
sumber
10

Solusi saya lebih sederhana daripada jawaban lain yang diberikan dan tidak memiliki kelemahan.

Ini mempertahankan rantai prototipe Kesalahan dan semua properti pada Kesalahan tanpa perlu pengetahuan khusus tentang mereka. Sudah diuji di Chrome, Firefox, Node, dan IE11.

Satu-satunya batasan adalah entri tambahan di bagian atas tumpukan panggilan. Tapi itu mudah diabaikan.

Berikut ini contoh dengan dua parameter khusus:

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

Contoh penggunaan:

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
}

Untuk lingkungan yang memerlukan polyfil dari setPrototypeOf:

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
    obj.__proto__ = proto;
    return obj;
};
Ben
sumber
1
seperti yang didokumentasikan dalam jawaban saya, solusi ini dapat menyebabkan masalah di Firefox atau browser lain yang hanya mencatat baris pertama jejak tumpukan di konsol
Jonathan Benn
Ini adalah satu-satunya jawaban yang saya temukan yang bekerja dengan baik dengan ES5 (menggunakan kelas ES6 juga berfungsi dengan baik). Kesalahan ditampilkan jauh lebih baik di Chromium DevTools daripada jawaban lainnya.
Ben Gubler
Catatan: jika Anda menggunakan solusi ini dengan TypeScript, Anda harus menjalankan throw CustomError('err')alih-alihthrow new CustomError('err')
Ben Gubler
8

Dalam contoh di atas Error.apply(juga Error.call) tidak melakukan apa pun untuk saya (Firefox 3.6 / Chrome 5). Solusi yang saya gunakan adalah:

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';
panzi
sumber
5

Di Node seperti yang orang lain katakan, itu sederhana:

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);
}
Muhammad Umer
sumber
3

Saya hanya ingin menambahkan apa yang sudah dikatakan orang lain:

Untuk memastikan bahwa kelas kesalahan khusus muncul dengan benar di jejak tumpukan, Anda perlu mengatur properti nama prototipe kelas kesalahan ke properti nama kelas kesalahan kustom. Inilah yang saya maksud:

CustomError.prototype = Error.prototype;
CustomError.prototype.name = 'CustomError';

Jadi contoh lengkapnya adalah:

    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';

Ketika semua dikatakan dan dilakukan, Anda melempar pengecualian baru Anda dan sepertinya ini (saya malas mencoba ini di alat 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)
Gautham C.
sumber
5
Bukankah ini menimpa properti nama untuk SEMUA instance Kesalahan?
panzi
@ Panzi Anda benar. Saya telah memperbaiki bug kecil saya. Terimakasih atas peringatannya!
Gautham C.
3

2 sen saya:

Mengapa ada jawaban lain?

a) Karena mengakses Error.stackproperti (seperti dalam beberapa jawaban) memiliki penalti kinerja yang besar.

b) Karena hanya satu baris.

c) Karena solusi di https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error tampaknya tidak mempertahankan informasi tumpukan.

//MyError class constructor
function MyError(msg){
    this.__proto__.__proto__ = Error.apply(null, arguments);
};

contoh penggunaan

http://jsfiddle.net/luciotato/xXyeB/

Apa fungsinya?

this.__proto__.__proto__adalah MyError.prototype.__proto__, jadi itu mengatur __proto__UNTUK SEMUA INSTAN MyError ke Kesalahan khusus yang baru dibuat. Itu membuat properti dan metode kelas MyError dan juga menempatkan properti Kesalahan baru (termasuk .stack) di __proto__rantai.

Masalah yang jelas:

Anda tidak dapat memiliki lebih dari satu instance MyError dengan info tumpukan berguna.

Jangan gunakan solusi ini jika Anda tidak sepenuhnya mengerti apa yang this.__proto__.__proto__=terjadi.

Lucio M. Tato
sumber
2

Karena Pengecualian JavaScript sulit untuk sub-kelas, saya tidak sub-kelas. Saya baru saja membuat kelas Pengecualian baru dan menggunakan Kesalahan di dalamnya. Saya mengubah properti Error.name sehingga sepertinya pengecualian khusus pada konsol:

var InvalidInputError = function(message) {
    var error = new Error(message);
    error.name = 'InvalidInputError';
    return error;
};

Pengecualian baru di atas dapat dilemparkan seperti Kesalahan biasa dan akan berfungsi seperti yang diharapkan, misalnya:

throw new InvalidInputError("Input must be a string");
// Output: Uncaught InvalidInputError: Input must be a string 

Peringatan: jejak tumpukan tidak sempurna, karena akan membawa Anda ke tempat Kesalahan baru dibuat dan bukan ke tempat Anda membuang. Ini bukan masalah besar di Chrome karena memberi Anda jejak tumpukan penuh langsung di konsol. Tapi itu lebih bermasalah di Firefox, misalnya.

Jonathan Benn
sumber
Ini gagal dalam kasus inim = new InvalidInputError(); dontThrowMeYet(m);
Eric
@ Eric Saya setuju, tapi sepertinya ini batasan yang cukup kecil. Saya tidak pernah perlu membuat instance objek sebelumnya (kecuali menggunakan meta-pemrograman seperti contoh kode saya di atas). Apakah ini benar-benar masalah bagi Anda?
Jonathan Benn
Ya, kelihatannya perilakunya sama, jadi saya akan mengubah jawaban saya. Saya tidak 100% puas dengan jejak tumpukan, yang membawa Anda ke baris "var error" di Firefox dan Chrome
Jonathan Benn
1
@ JonathanBenn Saya benar-benar terlambat ke pesta, jadi mungkin Anda sudah mengambil ini. Saya sering instantiate objek pengecualian ketika saya menggunakan pemrograman asinkron dan Janji. Berikut @ nama Eric, saya sering menggunakan m = new ...kemudian Promise.reject(m). Bukan keharusan, tetapi kodenya lebih mudah dibaca.
BaldEagle
1
@ JonathanBenn: (he he) pada 14 Oktober, Anda tampaknya berpikir instantiating objek pengecualian sebelum melempar itu akan jarang terjadi. Saya memberi contoh satu kali saya melakukannya. Saya tidak akan mengatakan itu biasa, tetapi berguna jika saya menginginkannya. Dan, kode saya lebih mudah dibaca karena instantiating adalah semua pada satu baris dan menolak semua pada yang lain. Saya harap itu berhasil!
BaldEagle
2

Seperti yang ditunjukkan dalam jawaban Mohsen, dalam ES6 dimungkinkan untuk memperluas kesalahan menggunakan kelas. Ini jauh lebih mudah dan perilaku mereka lebih konsisten dengan kesalahan asli ... tapi sayangnya itu bukan masalah sederhana untuk menggunakan ini di browser jika Anda perlu mendukung browser pra-ES6. Lihat di bawah untuk beberapa catatan tentang bagaimana hal itu dapat diterapkan, tetapi sementara itu saya menyarankan pendekatan yang relatif sederhana yang menggabungkan beberapa saran terbaik dari jawaban lain:

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';

Dalam ES6 sesederhana:

class CustomError extends Error {}

... dan Anda dapat mendeteksi dukungan untuk kelas ES6 try {eval('class X{}'), tetapi Anda akan mendapatkan kesalahan sintaksis jika Anda mencoba untuk memasukkan versi ES6 dalam skrip yang dimuat oleh browser yang lebih lama. Jadi satu-satunya cara untuk mendukung semua browser adalah dengan memuat skrip terpisah secara dinamis (misalnya melalui AJAX atau eval()) untuk browser yang mendukung ES6. Komplikasi lebih lanjut adalah yang eval()tidak didukung di semua lingkungan (karena Kebijakan Keamanan Konten), yang mungkin atau mungkin tidak menjadi pertimbangan untuk proyek Anda.

Jadi untuk saat ini, baik pendekatan pertama di atas atau hanya menggunakan Errorsecara langsung tanpa mencoba memperluas tampaknya menjadi yang terbaik yang secara praktis dapat dilakukan untuk kode yang perlu mendukung browser non-ES6.

Ada satu pendekatan lain yang beberapa orang mungkin ingin mempertimbangkan, yaitu menggunakan di Object.setPrototypeOf()mana tersedia untuk membuat objek kesalahan yang merupakan contoh dari jenis kesalahan kustom Anda tetapi yang terlihat dan berperilaku lebih seperti kesalahan asli di konsol (berkat jawaban Ben untuk rekomendasi). Inilah pendapat saya tentang pendekatan itu: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Tetapi mengingat bahwa suatu hari kita akan dapat hanya menggunakan ES6, secara pribadi saya tidak yakin kompleksitas pendekatan itu sepadan.

Matt Browne
sumber
1

Cara untuk melakukan ini dengan benar adalah dengan mengembalikan hasil berlaku dari konstruktor, serta menetapkan prototipe dengan cara javascripty rumit yang biasa:

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

Satu-satunya masalah dengan cara melakukannya pada saat ini (saya sudah mengulanginya sedikit) adalah itu

  • properti selain stackdan messagetidak termasuk dalam MyErrordan
  • stacktrace memiliki baris tambahan yang sebenarnya tidak perlu.

Masalah pertama dapat diperbaiki dengan mengulangi semua properti kesalahan yang tidak dapat dihitung menggunakan trik dalam jawaban ini: Apakah mungkin untuk mendapatkan nama properti yang diwarisi dari objek yang tidak dapat dihitung? , tetapi ini tidak didukung oleh yaitu <9. Masalah kedua dapat diselesaikan dengan merobek garis itu di jejak tumpukan, tapi saya tidak yakin bagaimana melakukannya dengan aman (mungkin hanya menghapus baris kedua dari e.stack.toString () ??).

BT
sumber
Saya membuat modul yang dapat memperpanjang sebagian besar objek javascript lama, termasuk Kesalahan. Cukup matang pada titik ini github.com/fresheneesz/proto
BT
1

Cuplikan menunjukkan semuanya.

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)
      }
    }
Amarpreet Singh
sumber
0

Saya akan mengambil langkah mundur dan mempertimbangkan mengapa Anda ingin melakukan itu? Saya pikir intinya adalah menangani kesalahan yang berbeda secara berbeda.

Misalnya, dalam Python, Anda dapat membatasi pernyataan tangkapan hanya tangkapan MyValidationError, dan mungkin Anda ingin dapat melakukan hal serupa di javascript.

catch (MyValidationError e) {
    ....
}

Anda tidak dapat melakukan ini dalam javascript. Hanya akan ada satu blok penangkap. Anda seharusnya menggunakan pernyataan if pada kesalahan untuk menentukan jenisnya.

catch(e) { if(isMyValidationError(e)) { ... } else { // maybe rethrow? throw e; } }

Saya pikir saya akan membuang objek mentah dengan tipe, pesan, dan properti lain yang Anda inginkan.

throw { type: "validation", message: "Invalid timestamp" }

Dan ketika Anda menangkap kesalahan:

catch(e) {
    if(e.type === "validation") {
         // handle error
    }
    // re-throw, or whatever else
}
Hasen
sumber
1
Melempar benda bukanlah ide bagus. Anda tidak punya error.stack, perkakas standar tidak akan berfungsi dengannya, dll. Cara yang lebih baik adalah menambahkan properti ke instance kesalahan, misalnyavar e = new Error(); e.type = "validation"; ...
timruffles
0

Penghias Kesalahan Kustom

Ini didasarkan pada jawaban George Bailey , tetapi memperluas dan menyederhanakan ide aslinya. Ini ditulis dalam CoffeeScript, tetapi mudah dikonversi ke JavaScript. Idenya adalah memperpanjang kesalahan kustom Bailey dengan dekorator yang membungkusnya, memungkinkan Anda untuk membuat kesalahan khusus baru dengan mudah.

Catatan: Ini hanya akan berfungsi di V8. Tidak ada dukungan untuk Error.captureStackTracedi lingkungan lain.

Menetapkan

Dekorator mengambil nama untuk jenis kesalahan, dan mengembalikan fungsi yang mengambil pesan kesalahan, dan menyertakan nama kesalahan.

CoreError = (@message) ->

    @constructor.prototype.__proto__ = Error.prototype
    Error.captureStackTrace @, @constructor
    @name = @constructor.name

BaseError = (type) ->

    (message) -> new CoreError "#{ type }Error: #{ message }"

Menggunakan

Sekarang mudah untuk membuat jenis kesalahan baru.

StorageError   = BaseError "Storage"
SignatureError = BaseError "Signature"

Untuk bersenang-senang, Anda sekarang dapat mendefinisikan fungsi yang melempar a SignatureErrorjika dipanggil dengan terlalu banyak argumen.

f = -> throw SignatureError "too many args" if arguments.length

Ini telah diuji dengan cukup baik dan tampaknya berfungsi dengan baik pada V8, mempertahankan traceback, posisi, dll.

Catatan: Menggunakan newadalah opsional saat membuat kesalahan khusus.

Carl Smith
sumber
0

jika Anda tidak peduli dengan kesalahan karena ini adalah yang terkecil yang dapat Anda lakukan

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

Anda dapat menggunakannya tanpa hanya MyError (pesan) baru

Dengan mengubah prototipe setelah Kesalahan konstruktor dipanggil, kita tidak perlu mengatur callstack dan pesan

bormat
sumber
0

Mohsen memiliki jawaban hebat di atas dalam ES6 yang menetapkan nama, tetapi jika Anda menggunakan TypeScript atau jika Anda hidup di masa depan di mana semoga proposal untuk bidang kelas publik dan swasta ini telah bergerak melewati tahap 3 sebagai proposal dan membuatnya ke tahap 4 sebagai bagian dari ECMAScript / JavaScript maka Anda mungkin ingin tahu ini kemudian hanya sedikit lebih pendek. Tahap 3 adalah tempat browser mulai mengimplementasikan fitur, jadi jika browser Anda mendukungnya, kode di bawah ini mungkin berfungsi. (Diuji di browser Edge baru v81 tampaknya berfungsi dengan baik). Berhati-hatilah meskipun ini adalah fitur yang tidak stabil saat ini dan harus digunakan dengan hati-hati dan Anda harus selalu memeriksa dukungan browser pada fitur yang tidak stabil. Posting ini terutama untuk para penghuni masa depan ketika browser mungkin mendukung ini. Untuk memeriksa dukungan, periksa MDNdan Dapatkah saya menggunakan . Saat ini mendapat dukungan 66% di seluruh pasar browser yang ada di sana tetapi tidak terlalu bagus jadi jika Anda benar-benar ingin menggunakannya sekarang dan tidak ingin menunggu menggunakan transpiler seperti Babel atau sesuatu seperti TypeScript .

class EOFError extends Error { 
  name="EOFError"
}
throw new EOFError("Oops errored");

Bandingkan ini dengan kesalahan tanpa nama yang ketika dilempar tidak akan mencatat namanya.

class NamelessEOFError extends Error {}
throw new NamelessEOFError("Oops errored");

John
sumber