Memperluas Kesalahan dalam Javascript dengan sintaks ES6 & Babel

132

Saya mencoba untuk memperpanjang Kesalahan dengan ES6 dan Babel. Itu tidak berhasil.

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

Objek Kesalahan tidak pernah mendapatkan pesan yang benar ditetapkan.

Coba di Babel REPL .

Sekarang saya telah melihat beberapa solusi pada SO ( misalnya di sini ), tetapi mereka semua tampak sangat un-ES6-y. Bagaimana cara melakukannya dengan cara ES6 yang bagus? (Itu bekerja di Babel)

Karel Bílek
sumber
2
Mengikuti tautan Anda ke Babel REPL tampaknya mengindikasikan bahwa ia bekerja dengan benar sekarang. Saya kira itu adalah bug di Babel yang telah diperbaiki.
kybernetikos

Jawaban:

188

Berdasarkan jawaban Karel Bílek, saya akan membuat perubahan kecil pada 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);

Ini akan mencetak MyErrordalam tumpukan, dan bukan generik Error.

Itu juga akan menambahkan pesan kesalahan ke jejak tumpukan - yang hilang dari contoh Karel.

Ini juga akan digunakan captureStackTracejika tersedia.

Dengan Babel 6, Anda perlu transform-builtin- extended ( npm ) agar bisa berfungsi.

Lee Benson
sumber
1
@MichaelYounkin if (typeof Error.captureStackTrace === 'function') { Error.captureStackTrace(this, this.constructor.name) } else { this.stack = (new Error(message)).stack; } . Saya berpendapat bahwa lebih baik menggunakan fungsi ini jika tersedia, karena menyediakan stack panggilan yang lebih 'asli' dan mencetak nama objek kesalahan. Tentu saja, jika Anda menggunakan ini hanya pada sisi-server juga (Node), maka itu juga tidak masalah.
Lee Benson
4
@MichaelYounkin Saya tidak berpikir ini layak untuk downvote. OP berbicara tentang memperluas kesalahan dalam ES6. Mengikuti logika itu, hampir semua ES6 hilang dari setidaknya satu browser. Solusi saya (dengan tambahan fungsi periksa) memberikan cakupan asli di peramban yang paling banyak digunakan, mundur satu sama lain, dan cakupan 100% di Node.js. Saya setuju bahwa jika Anda melakukan kesalahan apa yang disebut classname secara konsisten maka this.stack = (new Error(message)).stackAnda mendapatkan itu ... tetapi dalam praktiknya, ini mungkin bukan masalah besar.
Lee Benson
6
Ini tidak berfungsi dalam Babel 6:new MyError('foo') instanceof MyError === false
Sukima
5
Kode ini pra-dikompilasi dengan babel sebagai modul NPM: extendable-error-class npmjs.com/package/extendable-error-class yang nyaman untuk menghindari ketergantungan pada babel-plugin-transform-builtin-
extended
3
this.message = message;berlebihan dengansuper(message);
mathieug
39

Menggabungkan jawaban ini , jawaban ini dan kode ini , saya telah membuat kelas "pembantu" kecil ini, yang sepertinya berfungsi dengan baik.

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

Coba di REPL

Karel Bílek
sumber
1
this.stack = (new Error(message)).stack;- kalau tidak, pesannya hilang dari stacktrace
Lee Benson
3
Saya menduga ini tidak berfungsi sebagaimana diperlukan, karena jika Anda melakukannya: console.log (myerror instanceof ExtendableError); masih dikatakan salah ..
Mauno Vähä
4
masalah yang sama, menggunakan instanceof CustomError tidak berfungsi, apa gunanya memperpanjang jika Anda tidak dapat menggunakan instanceof.
gre
Ini dapat ditingkatkan dengan menambahkan messagedi konstruktor Error stack, sehingga ia menunjukkan pesan yang tepat di bagian atas stack ketika dilemparkan:this.stack = (new Error(message)).stack;
Sebastien
1
myerror.namesekarang mengembalikan "Kesalahan". Tidak yakin apakah ini terkait dengan versi babel nanti. Lihat jawaban @ sukima di bawah ini
Eric H.
27

Akhirnya meletakkan ini untuk beristirahat. Di Babel 6 itu adalah eksplisit bahwa pengembang tidak mendukung membentang dari dibangun di. Meskipun trik ini tidak akan membantu dengan hal-hal seperti Map, Set, dll tidak bekerja untuk Error. Ini penting karena salah satu ide inti dari suatu bahasa yang dapat menimbulkan pengecualian adalah mengizinkan kesalahan khusus. Ini sangat penting karena Janji menjadi lebih berguna karena mereka dirancang untuk menolak Kesalahan .

Kebenaran yang menyedihkan adalah Anda masih perlu melakukan ini dengan cara lama di ES2015.

Contoh dalam Babel REPL

Pola Kesalahan Khusus

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

Di sisi lain ada plugin untuk Babel 6 untuk memungkinkan ini.

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

Pembaruan: (per 2016-09-29) Setelah beberapa pengujian tampak bahwa babel.io tidak memperhitungkan dengan benar semua pernyataan (memperluas dari Kesalahan yang diperluas khusus). Namun dalam Ember.JS, memperpanjang Kesalahan berfungsi seperti yang diharapkan: https://ember-twiddle.com/d88555a6f408174df0a4c8e0fd6b27ce

Sukima
sumber
ya, dengan plugin babel tertaut berfungsi dengan benar dengan jawaban yang diterima. (Namun, file yang dihasilkan tidak berfungsi di Node, karena tampaknya tidak memiliki Reflect)
Karel Bílek
Sama seperti rasa ingin tahu, jika spesifikasi ES2016 mengatakan bahwa builtin dapat diperpanjang, mengapa vms seperti v8 dan B5's es5 mentransmisikan hal yang bertentangan dengannya? Bukankah ini harapan yang masuk akal bahwa suatu kelas dapat memperluas kelas dengan cara yang sama seperti rantai prototipe dapat berasal dari prototipe lain? Mengapa perlunya upacara seperti itu disembunyikan dalam sebuah plugin?
Sukima
Ini sangat membuat frustrasi ketika kebanyakan kasus penggunaan hanya ingin membuat objek sederhana yang berbagi perilaku. Kesalahan khusus yang dapat digunakan Error.toString(). Kebutuhan untuk melakukan simpangan dan putaran khusus untuk mencapai hal ini berarti sebagian besar devs akan menghindarinya dan melakukan praktik-praktik buruk seperti melempar string, bukannya Kesalahan. Atau membuat Peta sendiri seperti objek. Mengapa perlu untuk menghalangi metode OOP seperti itu?
Sukima
Menurut pendapat saya mereka tidak menentangnya, itu hanya masalah teknis. Saya tidak yakin, lho! Anda dapat bertanya kepada mereka :) proyeknya cukup terbuka
Karel Bílek
Sejauh ini semua tanggapan dari pertanyaan simular pada topik dibiarkan pada "babel tidak mendukung itu" saya pikir itu adalah akhir dari percakapan. Daging sapi saya adalah kurangnya dukungan membuat idiom OOP umum sulit dan saya bahkan harus bertarung dengan co-wokers untuk mendapatkan mereka di atas boilerplate cruft. Saya hanya berharap di sini adalah solusi alternatif yang bersih. Tampaknya menambahkan plugin adalah pilihan terbaik.
Sukima
15

Sunting : Memecah perubahan dalam naskah 2.1

Memperluas bawaan seperti Galat, Array, dan Peta mungkin tidak lagi berfungsi.

Sebagai rekomendasi, Anda dapat menyesuaikan prototipe secara langsung segera setelah panggilan super (...).

Mengedit jawaban asli Lee Benson sedikit bermanfaat bagi saya. Ini juga menambah stackdan metode tambahan ExtendableErrorkelas ke instance.

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);
Artur Aleksanyan
sumber
1
Anda perlu menelepon Object.setPrototypeOfdalam MyErrorkonstruktor juga. stackoverflow.com/a/41102306/186334 github.com/Microsoft/TypeScript-wiki/blob/master/…
CallMeLaNN
10

Dengan perubahan terbaru dalam babel 6, saya menemukan transform-builtin-extended tidak lagi berfungsi. Saya akhirnya menggunakan pendekatan campuran ini:

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;

dan

import MyError from './MyError';

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

Akibatnya semua tes ini lulus:

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');
Diego Ferri
sumber
6

Mengutip

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

Tidak perlu this.stack = (new Error()).stack;trik, terima kasih untuk super()menelepon.

Meskipun kode di atas tidak dapat menampilkan jejak stack kecuali this.stack = (new Error()).stack;atau Error.captureStackTrace(this, this.constructor.name);dipanggil dalam Babel . IMO, mungkin ada satu masalah di sini.

Sebenarnya, jejak stack bisa menjadi output di bawah Chrome consoledan Node.js v4.2.1dengan potongan kode ini.

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

Output dari 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)

Output dari 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
zangw
sumber
4

Selain jawaban @ zangw, Anda dapat menentukan kesalahan Anda seperti ini:

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

yang akan membuang nama, pesan, dan stacktrace yang benar:

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
Honza Stepanovsky
sumber
4
Ini tidak bekerja: new MyError('foo') instanceof MyError === false.
Sukima
1
Ya, pada Node.js v7.7.3.
Gunar Gessner
2

Saya mencoba untuk memperpanjang Kesalahan dengan ES6

Bahwa class MyError extends Error {…} sintaks benar.

Perhatikan bahwa transpiler masih memiliki masalah dengan pewarisan dari objek bawaan. Dalam kasus anda,

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

tampaknya memperbaiki masalah.

Bergi
sumber
Benar! Tapi pesannya belum diatur - saya akan menulis contoh baru.
Karel Bílek
Saya telah menulis ulang contohnya sekarang
Karel Bílek
"Super (m)" akan mengembalikan objek kosong, tampaknya. Jadi Object.assign tidak membantu.
Karel Bílek
@ KarelBílek: Peramban apa yang Anda gunakan? Error.call()mengembalikan contoh kesalahan baru untuk saya.
Bergi
2

Mengingat ini jawaban yang diterima tidak lagi bekerja Anda selalu bisa menggunakan pabrik sebagai alternatif ( 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);

Melbourne2991
sumber
Jawaban yang diterima masih bekerja untuk saya, jika Anda memiliki plugin babel yang diperlukan. Terima kasih atas jawaban ini juga!
Karel Bílek
2

Saya lebih suka sintaks yang lebih kuat daripada yang dijelaskan di atas. Metode tambahan pada tipe kesalahan akan membantu Anda membuat cantik console.logatau sesuatu yang lain.

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

Untuk menguji kode ini, Anda dapat menjalankan sesuatu yang serupa:

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

    console.warn(message);
}

Perluasan CustomErrorjenis dipersilakan. Dimungkinkan untuk menambahkan beberapa fungsi spesifik ke tipe yang diperluas atau menimpa yang sudah ada. Sebagai contoh.

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}.`;
    }
}
B. Bohdan
sumber
1

Seperti @sukima menyebutkan, Anda tidak dapat memperpanjang JS asli. Pertanyaan OP tidak dapat dijawab.

Mirip dengan jawaban Melbourne2991 , saya memang menggunakan pabrik, tetapi mengikuti rekomendasi MDN untuk jenis kesalahan pelanggan .

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;
}
Eric H.
sumber
1

Ini bekerja untuk saya:

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

Tidak menggunakan Babel, tetapi di ES6 biasa, berikut ini sepertinya berfungsi dengan baik untuk saya:

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

Pengujian dari 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 ...'

Seperti yang Anda lihat, tumpukan berisi nama kesalahan dan pesan. Saya tidak yakin apakah saya kehilangan sesuatu, tetapi semua jawaban lain tampaknya terlalu rumit.

JHH
sumber
0

Saya sedikit memperbaiki solusi @Lee Benson dengan cara ini:

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

contoh kesalahan

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 

Kemudian Anda dapat mengelompokkan kesalahan sambil memiliki opsi penentu untuk memutuskan apa yang harus dilakukan secara berbeda dalam beberapa situasi spesifik aplikasi Anda

new AuthorizationError ("The user must be a seller to be able to do a discount", AuthorizationError.errors.BAD_PROFILE_TYPE )
Zied Hamdi
sumber