javascript - Typescript - Hata sınıfını genişletme


Konsolda "Error" yerine "CustomError" sınıf adımın yazdırıldığı özel bir hata fırlatmaya çalışıyorum, ama başaramadım:

class CustomError extends Error { 
    constructor(message: string) {
      super(`Lorem "${message}" ipsum dolor.`);
      this.name = 'CustomError';
    }
}
throw new CustomError('foo'); 

Çıktı Yakalanmamış Hata: Lorem "foo" ipsum dolor.

Beklediğim: Yakalanmamış CustomError: Lorem "foo" ipsum dolor.

Bunu sadece TS kullanarak yapmanın mümkün olup olmadığını merak ediyorum (JS prototipleriyle uğraşmadan).

Cevap #1

Typescript sürüm 2.1 kullanıyor ve ES5'e dönüştürüyor musunuz? Olası sorunlar ve çözüm yolları için breaking changes sayfasının bu bölümünü kontrol edin: https://github.com/microsoft/TypeScript-wiki/blob/94fb0618c7185f86afec26e6b4d2d6eb7c049e47/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work

İlgili kısım:

Bir öneri olarak, herhangi bir super(...) çağrısından hemen sonra prototipi elle ayarlayabilirsiniz.

class FooError extends Error {
    constructor(m: string) {
        super(m);

        // Set the prototype explicitly.
        Object.setPrototypeOf(this, FooError.prototype);
    }

    sayHello() {
        return "hello " + this.message;
    }
}

Ancak, FooError'ın herhangi bir alt sınıfı da prototipi manuel olarak ayarlamak zorunda kalacaktır. Object.setPrototypeOf'u desteklemeyen çalışma zamanları için, bunun yerine __proto__ kullanmanız mümkün olabilir.

Ne yazık ki, bu geçici çözümler Internet Explorer 10 ve öncesinde işe yaramayacaktır. Bir kişi metodları prototipten örneğin kendisine (yani FooError.prototype'tan this'e) manuel olarak kopyalayabilir, ancak prototip zinciri kendisi düzeltilemez.

Cevap #2

Sorun, Javascript'in yerleşik Error sınıfının, super çağırdığınızda, oluşturulacak nesneyi (yani this) yeni ve farklı bir nesne ile değiştirerek prototip zincirini bozmasıdır ve bu yeni nesne beklenen prototip zincirine sahip değildir, yani CustomError değil, Error örneğidir.

Bu sorun, Typescript 2.2'den beri desteklenen 'new.target' kullanılarak şık bir şekilde çözülebilir, bkz: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html

class CustomError extends Error {
  constructor(message?: string) {
    // 'Error' breaks prototype chain here
    super(message); 

    // restore prototype chain   
    const actualProto = new.target.prototype;

    if (Object.setPrototypeOf) { Object.setPrototypeOf(this, actualProto); } 
    else { this.__proto__ = actualProto; } 
  }
}

new.target kullanmanın avantajı, burada önerilen bazı diğer cevaplarda olduğu gibi prototipi elle kodlamak zorunda kalmamanızdır. Bunun bir diğer avantajı da, CustomError'den miras alan sınıfların otomatik olarak doğru prototip zincirini alacak olmasıdır.

Eğer prototipi sabit kodlamayı düşünürseniz (örneğin Object.setPrototype(this, CustomError.prototype)), CustomError kendisi çalışan bir prototip zincirine sahip olur, ancak CustomError'dan türeyen herhangi bir sınıf bozulur; örneğin bir class VeryCustomError < CustomError örneği beklenildiği gibi instanceof VeryCustomError olmaz, sadece instanceof CustomError olur.

Ayrıca bakınız: https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200

Cevap #3

TypeScript 2.2 itibarıyla bu, new.target.prototype aracılığıyla yapılabilir. https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#example

class CustomError extends Error {
    constructor(message?: string) {
        super(message); // 'Error' breaks prototype chain here
        this.name = 'CustomError';
        Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain
    }
}

Cevap #4

ES2015'te düzgün çalışıyor (https://jsfiddle.net/x40n2gyr/). Muhtemelen sorun, TypeScript derleyicisinin ES5'e dönüştürme yapması ve Error'un yalnızca ES5 özellikleri kullanılarak doğru şekilde alt sınıf yapılamamasıdır; sadece ES2015 ve üzeri özellikler kullanılarak (class veya daha az bilinen bir şekilde Reflect.construct) alt sınıf yapılabilir. Bunun nedeni, Error'u bir fonksiyon olarak çağırdığınızda (yani new ile veya ES2015'te super veya Reflect.construct ile değil), this'i yok sayması ve yeni bir Error oluşturmasıdır.

ES2015 veya daha üstünü hedefleyene kadar muhtemelen kusurlu çıktıyla yaşamak zorunda kalacaksınız...

Cevap #5

SO'ya kelimenin tam anlamıyla hiç bir zaman gönderi yapmam, ama ekibim bir TypeScript projesi üzerinde çalışıyor ve birçok özel hata sınıfı oluşturmamız gerekiyordu, aynı zamanda es5'i hedefliyorduk. Önerilen düzeltmeyi her bir hata sınıfında yapmak inanılmaz derecede zahmetli olurdu. Ama, ana bir özel hata sınıfı oluşturarak ve diğer tüm hatalarımızın bu sınıfı extend etmesini sağlayarak, tüm sonraki hata sınıfları üzerinde bir aşağı yönlü etki yapabileceğimizi gördük. Bu ana hata sınıfının içinde aşağıdaki işlemi yaparak prototipi güncelleme ile bu aşağı yönlü etkiyi elde ettik:

class MainErrorClass extends Error {
  constructor() {
    super()
    Object.setPrototypeOf(this, new.target.prototype)
  }
}

class SomeNewError extends MainErrorClass {} 

...

new.target.prototype kullanmak, her birinin yapıcı metodunu güncellemeden tüm miras alan hata sınıflarının güncellenmesini sağlamanın anahtarıydı.

Umarım bu, gelecekte başka birinin başını ağrıtmaktan kurtarır!

İlgili makaleler