JavaScript'te Hatayı genişletmenin iyi bir yolu nedir?


369

JS koduma bazı şeyler atmak istiyorum ve ben Hata örneği olmasını istiyorum, ama aynı zamanda başka bir şey olmasını istiyorum.

Python'da, tipik olarak, istisna alt sınıf olur.

JS'de yapılacak uygun şey nedir?

Yanıtlar:


218

Error nesnesinin sahip olduğu tek standart alan messageözelliğidir. (Bkz. MDN veya EcmaScript Dil Spesifikasyonu, bölüm 15.11) Diğer her şey platforma özgüdür.

Mosts ortamları set stacközelliği, ancak fileNameve lineNumbermiras kullanılmak üzere pratik olarak işe yaramaz.

Yani minimalist yaklaşım:

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

Yığını koklayabilir, istenmeyen öğeleri kaldırabilir ve fileName ve lineNumber gibi bilgileri çıkarabilirsiniz, ancak bunu yapmak için JavaScript'in şu anda çalıştığı platform hakkında bilgi gerekir. Çoğu durumda gereksizdir - ve eğer isterseniz gerçekten ölüm sonrası yapabilirsiniz.

Safari dikkate değer bir istisnadır. stackÖzellik yok , ancak throwanahtar kelime kümeleri sourceURLve lineatılan nesnenin özellikleri. Bu şeylerin doğru olduğu garanti edilmektedir.

Kullandığım test senaryolarını burada bulabilirsiniz: JavaScript kendi kendine yapılan Hata nesnesi karşılaştırması .


19
this.name = 'MyError' İşlevin dışını taşıyabilir ve olarak değiştirebilirsiniz MyError.prototype.name = 'MyError'.
Daniel Beardsley

36
Buradaki tek doğru cevap bu, ancak bir stil meselesi olarak, muhtemelen böyle yazardım. function MyError(message) { this.message = message; this.stack = Error().stack; } MyError.prototype = Object.create(Error.prototype); MyError.prototype.name = "MyError";
kybernetikos

11
Ben de eklerdim MyError.prototype.constructor = MyError.
Bharat Khatri

3
ES6 Error.call dosyasında (bu ileti); başlatmalı this, değil mi?
4esn0k

4
MyError.prototype = Object.create (Hata.prototype);
Robin kuş gibi

170

ES6'da:

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

kaynak


53
Alt sınıfların bir sınıfı genişletmesi gerektiğinden, ES6 özelliklerini Babel gibi bir transpiler aracılığıyla kullanıyorsanız bunun işe yaramadığını belirtmek gerekir.
aaaidan

6
Babel kullanıyorsanız ve 5.x düğümündeyseniz, es2015 ön ayarını kullanmamalısınız, ancak npmjs.com/package/babel-preset-node5 yerel es6 uzantıları ve daha fazlasını kullanmanıza izin verir
Ace

2
Mümkün olduğunda bunu yapmanın en iyi yolu budur. Özel hatalar, hem Chrome hem de Firefox'ta (ve muhtemelen diğer tarayıcılarda) normal hatalar gibi davranır.
Matt Browne

2
Tarayıcılar için, çalışma zamanında sınıflar için desteği algılayabileceğinizi ve buna göre sınıf dışı bir sürüme geri dönebileceğinizi unutmayın. Tespit kodu:var supportsClasses = false; try {eval('class X{}'); supportsClasses = true;} catch (e) {}
Matt Browne

31
Bakım kolaylığı için this.name = this.constructor.name;bunun yerine kullanın.
Константин Ван

53

Kısacası:

  • Transferi olmayan ES6 kullanıyorsanız :

    class CustomError extends Error { /* ... */}
  • Babel transpiler kullanıyorsanız :

Seçenek 1: Babel-Plugin-Transform-Builtin-Extend kullanın

2.Seçenek: Kendiniz yapın (aynı kütüphaneden esinlenerek)

    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);
  • Saf ES5 kullanıyorsanız :

    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: Classtrophobic framework kullanın

Açıklama:

Error sınıfını ES6 ve Babel kullanarak genişletmek neden bir sorundur?

Çünkü CustomError örneği artık bu şekilde tanınmıyor.

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

Aslında, Babil resmi belgelerinden, sen herhangi yerleşik JavaScript sınıfları uzatamaz gibi Date, Array, DOMveya Error.

Sorun burada açıklanmaktadır:

Diğer SO cevapları ne olacak?

Verilen tüm yanıtlar sorunu giderir, instanceofancak normal hatayı kaybedersiniz console.log:

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

Yukarıda belirtilen yöntemi kullanırken, yalnızca instanceofsorunu düzeltmekle kalmaz, aynı zamanda normal hatayı da tutarsınız console.log:

console.log(new CustomError('test'));
// output:
// Error: test
//     at CustomError (<anonymous>:2:32)
//     at <anonymous>:1:5

1
class CustomError extends Error { /* ... */}satıcıya özgü bağımsız değişkenleri ( lineNumbervb.) doğru şekilde işlemezse, 'Javascript'te ES6 sözdizimi ile Uzatma Hatası' Babil'e özgüdür, ES5 çözümünüz kullanır constve özel bağımsız değişkenleri işlemez.
Eylül'de

Çok eksiksiz bir cevap.
Daniele Orlando

Bu aslında en kapsamlı çözümü sağlar ve çeşitli parçaların neden gerekli olduğunu açıklar. Çok teşekkürler JBE!
AndrewSouthpaw

Bu bana "Hata" dan devralma problemini çözmede yardımcı oldu. İki saatlik bir kabus oldu!
Iulian Pinzaru

2
Bununla ilgili sorunun console.log(new CustomError('test') instanceof CustomError);// falseyazma sırasında doğru olduğunu, ancak şimdi çözüldüğünü belirtmek gerekir . Aslında cevap bağlantılı sorunu çözüldüğünü ve biz doğru davranışı test edebilirsiniz burada ve kod yapıştırarak REPL ve doğru doğru prototip zinciri ile örneğini transpiled alır nasıl gördükten.
Nobita

44

Düzenle: Lütfen yorumları okuyun. Bu sadece V8 (Chrome / Node.JS) 'de iyi çalışıyor. Amacım tüm tarayıcılarda çalışan bir çapraz tarayıcı çözümü sağlamak ve desteğin olduğu yerde yığın izlemesi sağlamaktı.

Düzenleme: Bu Topluluk Wiki'sini daha fazla düzenlemeye izin verdim.

V8 (Chrome / Node.JS) çözümü, Firefox'ta çalışır ve çoğunlukla IE'de çalışacak şekilde değiştirilebilir. (yazının sonuna bakın)

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
}

"Bana kodu göster!"

Kısa versiyon:

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

Ben tutmak this.constructor.prototype.__proto__ = Error.prototypehep birlikte kodunu tutmak için işlev içinde. Ancak this.constructorbununla da değiştirebilirsiniz UserErrorve bu da kodu fonksiyonun dışına taşımanıza izin verir, böylece sadece bir kez çağrılır.

O rotaya giderseniz , ilk defa atmadan önce bu hattı aradığınızdan emin olun UserError.

Bu uyarı işlevi uygulamaz, çünkü sipariş ne olursa olsun önce işlevler oluşturulur. Böylece, işlevi sorunsuz bir şekilde dosyanın sonuna taşıyabilirsiniz.

Tarayıcı Uyumluluğu

Firefox ve Chrome'da (ve Node.JS) çalışır ve tüm vaatleri yerine getirir.

Internet Explorer aşağıda başarısız oluyor

  • Hataların err.stackbaşlaması gerekmez , bu yüzden "bu benim hatam değil".

  • Error.captureStackTrace(this, this.constructor) mevcut değil, bu yüzden böyle bir şey yapmalısın

    if(Error.captureStackTrace) // AKA if not IE
        Error.captureStackTrace(this, this.constructor)
  • toStringalt sınıfta kalmayı bırakır Error. Bu yüzden de eklemeniz gerekiyor.

    else
        this.toString = function () { return this.name + ': ' + this.message }
  • Eğer UserErrorbir instanceof Errorsüre önce aşağıdaki çalıştırmak sürece IE bir olarak kabul etmeyecektirthrow UserError

    UserError.prototype = Error.prototype

16
Firefox'un aslında captureStackTrace olduğunu sanmıyorum. Bu bir V8 uzantısı ve Firefox'ta benim için tanımlanmamış, ne de web'de Firefox'a destek veren herhangi bir referans bulamıyorum. (Yine de teşekkürler!)
Geoff

5
Error.call(this)değiştirmekten ziyade hata döndürdüğü için hiçbir şey yapmıyor this.
kybernetikos

1
Node.js
Rudolf Meijering

1
UserError.prototype = Error.prototypeyanıltıcıdır. Bu miras yapmaz, bu onları aynı sınıf yapar .
Halcyon

1
En azından mevcut tarayıcılar için Object.setPrototypeOf(this.constructor.prototype, Error.prototype)tercih edildiğine inanıyorum this.constructor.prototype.__proto__ = Error.prototype.
Mart'ta ChrisV

29

To klişe önlemek hata her farklı türü için, bir içine çözümlerin bazılarının bilgelik kombine  createErrorTypefonksiyonu:

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

Ardından yeni hata türlerini aşağıdaki gibi kolayca tanımlayabilirsiniz :

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

Hâlâ çizgiye ihtiyacın var this.name = name;mı?
Peter Tseng

@PeterTseng namePrototip üzerinde zaten ayarlandığından, artık gerekli değil. Kaldırdım. Teşekkürler!
Ruben Verborgh

27

In 2018 , ben bu en iyi yol olduğunu düşünüyorum; IE9 + ve modern tarayıcıları destekler.

GÜNCELLEME : Farklı uygulamalarla karşılaştırma için bu test ve repo'ya bakın .

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

Ayrıca , diğer yanıtlarda yaygın olarak kullanılan __proto__mülkün kullanımdan kaldırıldığına dikkat edin .


1
Neden kullanıyorsun setPrototypeOf()? En azından MDN'ye göre, genellikle .prototypeyapıcıdaki özelliği ayarlayarak aynı şeyi başarabilirseniz, kullanmak istemezsiniz ( elseolmayan taramalar için blokta yaptığınız gibi setPrototypeOf).
Matt Browne

Bir nesnenin prototipini değiştirmek hep birlikte önerilmez, değil setPrototypeOf. Ancak yine de ihtiyacınız varsa (OP'nin istediği gibi), yerleşik yöntemi kullanmalısınız. MDN'nin belirttiği gibi, bu, bir nesnenin prototipini ayarlamak için uygun bir yol olarak kabul edilir. Başka bir deyişle MDN, prototipi değiştirmediğini (performansı ve optimizasyonu etkilediğinden) değil, kullanmanız gerektiğini söylüyor setPrototypeOf.
Onur Yıldırım

Demek istediğim, burada prototipi gerçekten değiştirmen gerektiğini düşünmüyordum. Satırınızı altta ( CustomError.prototype = Object.create(Error.prototype)) kullanabilirsiniz. Ayrıca, Object.setPrototypeOf(CustomError, Error.prototype)yeni örneklerin prototipini belirtmek yerine yapıcısının prototipini ayarlamaktır CustomError. Her neyse, 2016'da bence hataları genişletmenin daha iyi bir yolu var, ancak yine de Babel ile birlikte nasıl kullanılacağını anlıyorum: github.com/loganfsmyth/babel-plugin-transform-builtin-extend/…
Matt Browne

CustomError.prototype = Object.create(Error.prototype)prototipi de değiştiriyor. ES5'te yerleşik bir genişletme / devralma mantığı olmadığından bunu değiştirmeniz gerekir. Eminim bahsettiğiniz babel eklentisi benzer şeyler yapar.
Onur Yıldırım

1
Object.setPrototypeOfEn azından nasıl kullandığınızı değil, burada kullanımın neden mantıklı olmadığını gösteren bir gist oluşturdum : gist.github.com/mbrowne/4af54767dcb3d529648f5a8aa11d6348 . Belki de yazmak istediniz Object.setPrototypeOf(CustomError.prototype, Error.prototype)- bu biraz daha mantıklı olacaktır (yine de basitçe ayarlamaya hiçbir fayda sağlamaz CustomError.prototype).
Matt Browne

19

Tamlık uğruna - sadece önceki cevapların hiçbiri bu yöntemden bahsetmediği için - Node.js ile çalışıyorsanız ve tarayıcı uyumluluğunu önemsemek zorunda değilseniz, yerleşik etki ile istenen etkiyi elde etmek oldukça kolaydır. inheritsait utilmodülü ( resmi dokümanlar burada ).

Örneğin, birinci bağımsız değişken olarak bir hata kodu ve ikinci bağımsız değişken olarak hata iletisini alan özel bir hata sınıfı oluşturmak istediğinizi varsayalım:

custom-error.js dosyası :

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

Şimdi aşağıdakileri başlatabilir ve geçebilir / atabilirsiniz 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!');

Bu snippet ile yığın izlemesinde doğru dosya adı ve satırı ve hata örneğinin doğru adı olacağını unutmayın!

Bu , hedef nesne üzerinde captureStackTracebir stacközellik oluşturan yöntemin kullanılması nedeniyle oluşur (bu durumda, CustomErrorörnekleme). Nasıl çalıştığı hakkında daha fazla ayrıntı için belgelerini kontrol buraya .


1
this.message = this.message;bu yanlış mı yoksa JS hakkında bilmediğim hala çılgınca şeyler var mı?
Alex

1
Hey @Alex, tamamen haklısın! Şimdi düzeltildi. Teşekkürler!
Victor Schröder

18

Crescent Fresh'in cevabı yüksek oy alan cevabı yanıltıcıdır. Uyarıları geçersiz olsa da, ele almadığı başka sınırlamalar da var.

Birincisi, Crescent'in "Uyarılar:" paragrafındaki mantık mantıklı değil. Açıklama "bir sürü if (MyError örneğinde hata) ..." kodlamanın birden fazla catch deyimiyle karşılaştırıldığında bir şekilde külfetli veya ayrıntılı olduğunu ima eder. Tek bir catch bloğundaki çoklu instanceof deyimleri, birden fazla catch deyimi kadar özlüdür - hile olmadan temiz ve özlü kod. Bu, Java'nın tek kullanımlık alt tipine özgü hata işlemeyi taklit etmenin harika bir yoludur.

WRT "alt sınıfın message özelliği ayarlanmadıysa görünür", düzgün yapılandırılmış bir Hata alt sınıfı kullanırsanız durum böyle değildir. Kendi ErrorX Error alt sınıfınızı yapmak için, "var MyError =" ile başlayan ve "MyError" kelimesini "ErrorX" olarak değiştirerek kod bloğunu kopyalamanız yeterlidir. (Alt sınıfınıza özel yöntemler eklemek istiyorsanız, örnek metni izleyin).

JavaScript hata alt sınıflamasının gerçek ve önemli sınırlaması, FireFox gibi yığın izlemesi ve örnekleme konumu hakkında izleyen ve raporlayan JavaScript uygulamaları veya hata ayıklayıcıları için, kendi Hata alt sınıf uygulamanızdaki bir konumun doğrudan bir hata kullandıysanız "new Error (...)") çalıştırdığınız konum olacaktır. IE kullanıcıları muhtemelen hiçbir zaman fark etmeyeceklerdi, ancak FF'deki Fire Bug kullanıcıları, bu Hataların yanı sıra rapor edilen işe yaramaz dosya adı ve satır numarası değerlerini görecek ve gerçek örnekleme konumunu bulmak için yığın izini # 1'e indirmeleri gerekecek.


Doğru anladım - doğrudan alt sınıf ve yeni Hata (...) kullanmazsanız, dosya adı ve satırı düzgün bir şekilde rapor ediliyor mu? Ve temel olarak, pratikte (sadece seksi veya dekoratif türlerin gerçek değil) alt sınıflandırma Hatalarının, hiçbir anlamı olmadığını söylüyorsunuz?
jayarjo

6
Bu cevap Crescent Fresh'ssilindi gibi kafa karıştırıcı bazı !
Peter

hala böyle mi? developer.mozilla.org/tr-TR/docs/Web/JavaScript/Reference/… Satır numarası yeni çağrıldığı yerde 2 değil
Muhammad Umer

13

Bu çözüme ne dersiniz?

Özel Hatanızı kullanarak:

throw new MyError("Oops!");

Error nesnesini sararsınız (bir dekoratör gibi):

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

Bu, stack, fileName lineNumber, et cetera gibi tüm özniteliklerin doğru olmasını sağlar.

Daha sonra tek yapmanız gereken, ya niteliklerin üzerine kopyalamak ya da onlar için alıcıları tanımlamaktır. İşte getters (IE9) kullanan bir örnek:

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
Bu çözümü bir npm paketi olarak yayınladım: npmjs.com/package/throwable
JoWie

İnanılmaz derecede zarif bir çözüm, paylaştığınız için teşekkürler! Bir varyasyon: new MyErr (arg1, arg2, new Error())ve MyErr yapıcısında Object.assignson this
bağımsız

Bunu severim. Kalıtım yerine kapsülleme kullanarak bir sınırlamayı atlarsınız.
Jenny O'Reilly

13

Bazı insanların söylediği gibi, ES6 ile oldukça kolay:

class CustomError extends Error { }

Bu yüzden benim app, (Açısal, Typescript) içinde denedim ve işe yaramadı. Bir süre sonra sorunun Typescript'ten geldiğini gördüm: O

Bkz. Https://github.com/Microsoft/TypeScript/issues/13965

Çok rahatsız edici çünkü eğer yaparsan:

class CustomError extends Error {}


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

Düğümde veya doğrudan tarayıcınızda şunları görüntüler: Custom error

Dizginiz oyun alanında projenizde Typescript ile çalıştırmayı deneyin, görüntülenecektir Basic error...

Çözüm aşağıdakileri yapmaktır:

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

Benim çözümüm sağlanan diğer cevaplardan daha basit ve dezavantajları yok.

Hata prototip zincirini ve Hata üzerindeki tüm özelliklerini, özel bir bilgiye ihtiyaç duymadan korur. Chrome, Firefox, Node ve IE11'de test edildi.

Tek sınırlama, çağrı yığınının üst kısmındaki fazladan bir giriştir. Ancak bu kolayca göz ardı edilir.

İki özel parametreye sahip bir örnek:

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

Örnek Kullanım:

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
}

SetPrototypeOf bir polifil gerektiren ortamlar için:

Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) {
    obj.__proto__ = proto;
    return obj;
};

1
cevabımda belgelendiği gibi, bu çözüm Firefox'ta veya diğer tarayıcılarda yalnızca konsoldaki yığın izlemesinin ilk satırını kaydeden bir soruna neden olabilir
Jonathan Benn 15:17

ES5 ile iyi çalışan bulduğum tek cevap bu (ES6 sınıflarını kullanmak da iyi çalışıyor). Hatalar Chromium DevTools'ta diğer yanıtlara göre çok daha güzel görünüyor.
Ben Gubler

Not: Bu çözümü TypeScript ile kullanıyorsanızthrow CustomError('err')throw new CustomError('err')
Ben Gubler

8

Yukarıdaki örnekte Error.apply(ayrıca Error.call) benim için hiçbir şey yapılmaz (Firefox 3.6 / Chrome 5). Kullandığım bir geçici çözüm:

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

Düğümde diğerlerinin söylediği gibi, basit:

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

Sadece başkalarının zaten söylediklerine eklemek istiyorum:

Özel hata sınıfının yığın izlemesinde düzgün göründüğünden emin olmak için, özel hata sınıfının prototipinin name özelliğini özel hata sınıfının name özelliğine ayarlamanız gerekir. Demek istediğim bu:

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

Yani tam örnek:

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

Her şey söylendiğinde ve yapıldığında, yeni istisnınızı atarsınız ve şöyle görünür (Bunu krom dev araçlarında tembel olarak denedim):

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
Bu, TÜM Hata örnekleri için name özelliğinin üzerine yazmıyor mu?
14'te panzi

@panzi haklısın. Küçük hatamı düzelttim. Söylediğin için teşekkürler!
Gautham C.28

3

2 sentim:

Neden başka bir cevap?

a) Tesise erişimin Error.stack(bazı cevaplarda olduğu gibi) büyük bir performans cezası olduğu için.

b) Çünkü bu sadece bir satır.

c) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error adresindeki çözüm, yığın bilgisini korumuyor gibi görünüyor.

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

kullanım örneği

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

Bu ne işe yarıyor?

this.__proto__.__proto__olduğu MyError.prototype.__proto__, bu yüzden kuruyor __proto__belirli yeni oluşturulan Hatası MyError ait tüm örnekleri için. MyError sınıfı özelliklerini ve yöntemlerini tutar ve ayrıca yeni Error özelliklerini (.stack dahil) __proto__zincire koyar .

Açık problem:

Yararlı yığın bilgisine sahip birden fazla MyError örneğiniz olamaz.

Ne yaptığını tam olarak anlamadıysanız bu çözümü kullanmayın this.__proto__.__proto__=.


2

JavaScript İstisnalarının alt sınıfa zor gelmesi nedeniyle alt sınıfa katılmam. Sadece yeni bir İstisna sınıfı oluşturuyorum ve içinde bir Hata kullanıyorum. Konsoldaki özel özel durumum gibi görünecek şekilde Error.name özelliğini değiştirdim:

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

Yukarıdaki yeni istisna, normal bir Hata gibi atılabilir ve beklendiği gibi çalışacaktır, örneğin:

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

Uyarı: Yığın izi mükemmel değildir, çünkü sizi yeni Hatanın oluşturulduğu yere getirir ve attığınız yere getirmez. Bu, Chrome'da büyük bir sorun değil, çünkü doğrudan konsolda tam bir yığın izlemesi sağlıyor. Ancak, örneğin Firefox'ta daha sorunlu.


Bu durumda başarısızm = new InvalidInputError(); dontThrowMeYet(m);
Eric

@Eric Katılıyorum, ama bu oldukça küçük bir sınırlama gibi görünüyor. (Önceden benim kod örneği gibi meta-programlama kullanır hariç) vaktinden önce bir istisna nesnesini somutlaştırmak için hiç gerek yoktu. Bu gerçekten senin için bir sorun mu?
Jonathan Benn

Evet, davranış aynı görünüyor, bu yüzden cevabımı değiştireceğim. Firefox ve Chrome'daki "var hatası" satırına getiren yığın izinden% 100 memnun değilim
Jonathan Benn

1
@JonathanBenn Partiye çok geç kaldım, belki de bunu zaten aldın. Eşzamansız programlama ve Vaatler kullandığımda sıklıkla bir istisna nesnesi başlatıyorum. @ Eric'in isimlerini takiben sık sık m = new ...o zaman kullanıyorum Promise.reject(m). Bu bir zorunluluk değildir, ancak kodun okunması daha kolaydır.
BaldEagle

1
@JonathanBenn: (o) 14 Ekim'de, bir istisna nesnesini atmadan önce somutlaştırmayı nadir görüyordunuz. Bir kez yaptığım bir örnek verdim. Yaygın olduğunu söylemeyeceğim, ama istediğim zaman sahip olmak kullanışlı. Ve benim kodum daha okunabilir çünkü örnekleme hepsi bir satırda ve reddetme başka bir satırda. Umarım yapar!
BaldEagle

2

Mohsen'in cevabında belirtildiği gibi, ES6'da sınıfları kullanarak hataları genişletmek mümkündür. Çok daha kolay ve davranışları yerel hatalarla daha tutarlı ... ancak ES6 öncesi tarayıcıları desteklemeniz gerekiyorsa maalesef bunu tarayıcıda kullanmak basit bir mesele değil. Bunun nasıl uygulanabileceğine dair bazı notlar için aşağıya bakın, ancak bu arada diğer cevaplardan en iyi önerilerin bazılarını içeren nispeten basit bir yaklaşım öneriyorum:

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'da bu kadar basit:

class CustomError extends Error {}

... ve ES6 sınıfları için desteği algılayabilirsiniz try {eval('class X{}'), ancak ES6 sürümünü eski tarayıcılar tarafından yüklenen bir komut dosyasına dahil etmeye çalışırsanız bir sözdizimi hatası alırsınız. Bu nedenle, tüm tarayıcıları desteklemenin tek yolu, eval()ES6'yı destekleyen tarayıcılar için dinamik olarak ayrı bir komut dosyası (ör. AJAX veya aracılığıyla ) yüklemek olacaktır. Diğer bir karmaşık nokta ise eval(), projeniz için dikkate alınabilecek veya olmayabilecek tüm ortamlarda (İçerik Güvenliği Politikaları nedeniyle) desteklenmemesidir.

Şimdilik, yukarıdaki ilk yaklaşım ya da sadece Errorgenişletmeye çalışmadan doğrudan kullanmak, ES6 olmayan tarayıcıları desteklemesi gereken kod için pratik olarak yapılabilecek en iyi şey gibi görünüyor.

Bazı insanların dikkate almak isteyebileceği başka bir yaklaşım daha vardır; bu, Object.setPrototypeOf()özel hata türünüzün bir örneği olan ancak konsoldaki yerel bir hataya benzeyen ve daha çok davranan bir hata nesnesi oluşturmak için mümkün olan yerlerde kullanmaktır ( Ben'in cevabı sayesinde) tavsiye için). İşte bu yaklaşımı benim almak: https://gist.github.com/mbrowne/fe45db61cea7858d11be933a998926a8 . Ancak bir gün ES6'yı kullanabileceğimiz göz önüne alındığında, kişisel olarak bu yaklaşımın karmaşıklığının buna değdiğinden emin değilim.


1

Bu hakkı yapmanın yolu, yapıcıdan uygulamanın sonucunu döndürmenin yanı sıra, prototipi her zamanki karmaşık javascripty yolunda ayarlamaktır:

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

Bu noktada bunu yapmanın tek yolu (biraz yineledim)

  • dışındaki özellikler stackve messageyer almayan MyErrorve
  • yığın izinin gerçekten gerekli olmayan ek bir satırı vardır.

İlk sorun, bu yanıttaki numarayı kullanarak tüm numaralandırılamayan hata özellikleri aracılığıyla yinelenerek düzeltilebilir: Bir nesnenin numaralandırılamayan devralınan özellik adlarını almak mümkün mü? , ancak bu <9 tarafından desteklenmez. İkinci sorun yığın izinde bu satırı yırtarak çözülebilir, ancak bunu nasıl güvenli bir şekilde yapacağımdan emin değilim (belki sadece ikinci e.stack.toString () ?? satırını kaldırarak).


Hatalar da dahil olmak üzere en normal eski javascript nesnesini genişletebilir bir modül yaptım. Bu noktada oldukça olgun github.com/fresheneesz/proto
BT

1

Snippet hepsini gösterir.

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

Bir adım geriye gidip neden bunu yapmak istediğini düşünürdüm? Bence önemli olan farklı hatalarla farklı başa çıkmaktır.

Örneğin, Python'da catch deyimini yalnızca yakalamak için kısıtlayabilirsiniz MyValidationErrorve belki de javascript'te benzer bir şey yapmak isteyebilirsiniz.

catch (MyValidationError e) {
    ....
}

Javascript ile bunu yapamazsınız. Sadece bir tane yakalama bloğu olacak. Türünü belirlemek için hata üzerinde bir if ifadesi kullanmanız gerekir.

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

Bunun yerine bir tür, mesaj ve uygun gördüğünüz diğer özellikleri ile ham bir nesne atmak düşünüyorum.

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

Ve hatayı yakaladığınızda:

catch(e) {
    if(e.type === "validation") {
         // handle error
    }
    // re-throw, or whatever else
}

1
Bir nesneyi atmak harika bir fikir değildir. Hiç var error.stack, standart kalıp olacak onunla değil iş, vs vs. yolu bir hata örneğine özelliklerini eklemek olacaktır örneğin daha iyi birvar e = new Error(); e.type = "validation"; ...
timruffles

0

Özel Hata Dekoratörü

Bu George Bailey'in cevabına dayanıyor , ancak orijinal fikri genişletiyor ve basitleştiriyor. CoffeeScript ile yazılmıştır, ancak JavaScript'e dönüştürmek kolaydır. Fikir, Bailey'nin özel hatasını, onu saran bir dekoratörle genişleterek kolayca yeni özel hatalar oluşturmanıza izin vermektir.

Not: Bu yalnızca V8'de çalışır. Error.captureStackTraceDiğer ortamlarda destek yoktur .

Tanımlamak

Dekoratör, hata türü için bir ad alır ve hata iletisi alan bir işlev döndürür ve hata adını ekler.

CoreError = (@message) ->

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

BaseError = (type) ->

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

kullanım

Artık yeni hata türleri oluşturmak çok kolay.

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

Eğlenmek için, artık SignatureErrorçok fazla argümanla çağrılırsa bir işlev atan bir işlev tanımlayabilirsiniz .

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

Bu oldukça iyi test edildi ve V8'de mükemmel bir şekilde çalışıyor, geri izleme, konum vb.

Not: newÖzel bir hata oluştururken kullanmak isteğe bağlıdır.


0

hata performanslarını umursamıyorsanız, yapabileceğiniz en küçük şey budur

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

sadece yeni MyError (mesaj) olmadan kullanabilirsiniz

Oluşturucu Hatası çağrıldıktan sonra prototipi değiştirerek çağrı ve mesajı ayarlamamız gerekmiyor


0

Mohsen'in ES6'da adı belirleyen harika bir cevabı var, ancak TypeScript kullanıyorsanız veya umarım gelecekte kamu ve özel sınıf alanları için bu teklifin bir teklif olarak 3. aşamayı geçtiğini ve yaptığını düşünüyorsanız ECMAScript / JavaScript'in bir parçası olarak 4. aşamaya geçtikten sonra bunun biraz daha kısa olduğunu bilmek isteyebilirsiniz. Aşama 3, tarayıcıların özellikleri uygulamaya başladığı yerdir, bu nedenle tarayıcınız destekliyorsa aşağıdaki kod işe yarayabilir. (Yeni Edge tarayıcı v81'de test edilmiş gibi görünüyor). Bu şu anda kararsız bir özellik olsa da dikkatli olun ve dikkatli kullanılmalıdır ve kararsız özellikler konusunda her zaman tarayıcı desteğini kontrol etmelisiniz. Bu yayın, tarayıcıların bunu destekleyebileceği gelecekteki sakinler içindir. Destek kontrolünü kontrol etmek MDN'yive kullanabilir miyim . Şu anda tarayıcı pazarında% 66 destek var, ancak o kadar da iyi değil, bu yüzden şimdi kullanmak istiyorsanız ve Babel gibi bir transpiler veya TypeScript gibi bir şey kullanmak istemiyorsanız .

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

Bunu, atıldığında adının kaydedilmeyeceği isimsiz bir hatayla karşılaştırın.

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.