Warum funktioniert instanceof nicht bei Instanzen von Error-Unterklassen unter babel-node?

77

Ich sehe, dass der instanceofOperator nicht mit Instanzen von ErrorUnterklassen arbeitet, wenn er unter babel-node Version 6.1.18 / Node Version 5.1.0 unter OS X ausgeführt wird. Warum ist das so? Der gleiche Code funktioniert gut im Browser. Versuchen Sie es mit meiner Geige .

Der folgende Code wird trueim Browser ausgegeben, während er unter babel-node falsch ist:

class Sub extends Error {
}

let s = new Sub()
console.log(`The variable 's' is an instance of Sub: ${s instanceof Sub}`)

Ich kann mir nur vorstellen, dass dies auf einen Fehler in babel-node zurückzuführen ist, da dies instanceoffür andere Basisklassen als funktioniert Error.

.babelrc

{
  "presets": ["es2015"]
}

Kompilierte Ausgabe

Dies ist das von babel 6.1.18 kompilierte JavaScript:

'use strict';

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var Sub = (function (_Error) {
  _inherits(Sub, _Error);

  function Sub() {
    _classCallCheck(this, Sub);

    return _possibleConstructorReturn(this, Object.getPrototypeOf(Sub).apply(this, arguments));
  }

  return Sub;
})(Error);

var s = new Sub();
console.log('The variable \'s\' is an instance of Sub: ' + (s instanceof Sub));
aknuds1
quelle
Ich kann Ihr Problem nicht reproduzieren. Ich habe sowohl mit Online Babel Repl als auch mit Babel-Node (neueste Version) getestet.
Denys Séguret
Welche Voreinstellung verwenden Sie mit Babel? Vielleicht würde das Posten des transpilierten Skripts helfen, das Problem zu reproduzieren
CodingIntrigue
@ DenysSéguret Danke, ich werde weiter nachsehen.
Aknuds1
@RGraham Die Geige benutzt Babel im Browser? Der Punkt ist, dass Babel im Browser anders funktioniert als Node anscheinend.
Aknuds1
1
@RGraham Ich habe das kompilierte Skript gepostet.
aknuds1

Antworten:

88

tl; dr Wenn Sie auf Babel 6 sind, können Sie https://www.npmjs.com/package/babel-plugin-transform-builtin-extend verwenden

Das Erweitern von integrierten Typen wie Arrayund Errorund wurde in Babel noch nie unterstützt. Es ist in einer realen ES6-Umgebung vollkommen gültig, aber es gibt Anforderungen, damit es funktioniert, die sehr schwer auf eine Weise zu transpilieren sind, die mit älteren Browsern kompatibel ist. In Babel 5 "funktionierte" es, indem es keinen Fehler auslöste, aber Objekte, die aus der erweiterten Unterklasse instanziiert wurden, funktionierten nicht so, wie sie es sollten, zum Beispiel:

class MyError extends Error {}

var e1 = new MyError();
var e2 = new Error();

console.log('e1', 'stack' in e1);
console.log('e2', 'stack' in e2);

führt zu

e1 false
e2 true

Obwohl kein Fehler aufgetreten ist, erhält die Unterklasse nicht ordnungsgemäß einen "Stapel", wie dies bei Fehlern der Fall sein sollte. Wenn Sie es erweitern Array, verhält es sich möglicherweise ähnlich wie ein Array und verfügt über Array-Methoden, verhält sich jedoch nicht vollständig wie ein Array.

In der Babel 5-Dokumentation wurde dies ausdrücklich als Randfall von Klassen bezeichnet, die beachtet werden sollten.

In Babel 6 wurden Klassen geändert, um den Umgang mit Unterklassen spezifikationskonformer zu gestalten. Ein Nebeneffekt davon ist, dass der obige Code jetzt immer noch nicht funktioniert, aber nicht anders als zuvor. Dies wurde in https://phabricator.babeljs.io/T3083 behandelt , aber ich werde hier auf eine mögliche Lösung eingehen.

Um das Verhalten der Unterklassen von Babel 5 zurückzugeben (das sich daran erinnert, dass es immer noch nicht richtig oder empfohlen ist), können Sie den eingebauten Konstruktor in Ihre eigene temporäre Klasse einschließen, z

function ExtendableBuiltin(cls){
    function ExtendableBuiltin(){
        cls.apply(this, arguments);
    }
    ExtendableBuiltin.prototype = Object.create(cls.prototype);
    Object.setPrototypeOf(ExtendableBuiltin, cls);

    return ExtendableBuiltin;
}

Mit diesem Helfer, anstatt zu tun

class MyError extends Error {}

machen

class MyError extends ExtendableBuiltin(Error) {}

In Ihrem speziellen Fall haben Sie jedoch angegeben, dass Sie sich auf Knoten 5.x befinden. Knoten 5 unterstützt native ES6-Klassen ohne Transpilieren. Ich würde empfehlen, dass Sie diese verwenden, indem Sie die es2015Voreinstellung löschen und stattdessen verwenden, node5damit Sie unter anderem native Klassen erhalten. In diesem Zusammenhang

class MyError extends Error {}

wird so funktionieren, wie Sie es erwarten.

Für Benutzer, die nicht auf Node 4/5 oder nur in Chrome sind, sollten Sie beispielsweise https://www.npmjs.com/package/error verwenden . Sie können auch https://www.npmjs.com/package/babel-plugin-transform-builtin-extend erkunden . Die approximateOption hierfür ist das gleiche Verhalten wie in Babel 5. Beachten Sie, dass das Nicht- approximateVerhalten definitiv ein Randfall ist und in 100% der Fälle möglicherweise nicht funktioniert.

loganfsmyth
quelle
1
Aha danke. Was ist dann die empfohlene Methode zum Erstellen neuer Ausnahmeklassen?
aknuds1
2
Schließlich empfiehlt der MDN die prototypische Vererbung von Error, um neue Ausnahmeklassen zu erstellen. AFAICT, dies sollte der Unterklasse in ES6 entsprechen, es sei denn, ich übersehen etwas. Ich habe auch diesen Artikel über die Semantik der ES6-Klasse gefunden, der genau ein Beispiel für die Unterbesetzung enthält Error.
aknuds1
1
Ich habe meine Antwort etwas erweitert. Die Unterklasse in ES6 ist viel komplizierter als erwartet und lässt sich nicht gut in ES5 abbilden.
Loganfsmyth
1
Ist es schon behoben? Oder wird es jemals behoben? Sie haben Wontfixdieses Problem in phabricator markiert. Das ist etwas enttäuschend, weil ich dachte, ich sollte erwarten, dass diese Funktion funktioniert, zumindest wenn Babel es richtig und gemäß der Spezifikation gemacht hat.
Michał Miszczyszyn
4
Für diese Anwendungsfälle habe ich sie gerade in ES5 geschrieben, zBvar CustomError = function(message){ Error.call(this, message) this.message = message }
Ally
1

instanceoffunktioniert nicht für untergeordnete Fehler, wenn das Kompilierungsziel auf "es5" gesetzt ist. Ich habe das Ziel in meinem auf "es6" gesetzt tsconfig.jsonund instanceofdas richtige Ergebnis erzielt .

belvederef
quelle