جاوااسکریپت - تایپاسکریپت - توسعه کلاس Error
من سعی دارم یک خطای سفارشی با نام کلاس «CustomError» خودم نمایش بدهم تا در کنسول به جای «Error» چاپ شود، اما موفق نشدهام:
class CustomError extends Error {
constructor(message: string) {
super(`Lorem "${message}" ipsum dolor.`);
this.name = 'CustomError';
}
}
throw new CustomError('foo');
خروجی خطای غیرمنتظره: Lorem "foo" ipsum dolor است.
آنچه انتظار دارم: Uncaught CustomError: Lorem "foo" ipsum dolor.
من کنجکاوم بفهمم آیا میتوان این کار را فقط با TS انجام داد (بدون دستکاری پروتوتایپهای JS)؟
پاسخ شماره ۱
آیا از نسخه ۲.۱ تایپاسکریپت استفاده میکنید و به ES5 تبدیل میکنید؟ این بخش از صفحه تغییرات شکسته شده را برای مشکلات و راهحلهای ممکن بررسی کنید: https://github.com/microsoft/TypeScript-wiki/blob/94fb0618c7185f86afec26e6b4d2d6eb7c049e47/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
قسمت مربوطه:
به عنوان توصیه، شما میتوانید بلافاصله پس از هر فراخوانی super(...)، پروتوتایپ را به صورت دستی تنظیم کنید.
class FooError extends Error { constructor(m: string) { super(m); // Set the prototype explicitly. Object.setPrototypeOf(this, FooError.prototype); } sayHello() { return "hello " + this.message; } }با این حال، هر زیرکلاس از FooError نیز باید بهصورت دستی پروتوتایپ را تنظیم کند. برای محیطهایی که Object.setPrototypeOf را پشتیبانی نمیکنند، ممکن است بتوانید بهجای آن از
__proto__استفاده کنید.متأسفانه، این راهحلهای جایگزین در اینترنت اکسپلورر ۱۰ و نسخههای قبل از آن کار نخواهند کرد. میتوان روشها را به صورت دستی از پروتوتایپ روی خود نمونه کپی کرد (مثلاً FooError.prototype روی this)، اما زنجیره پروتوتایپ به خودی خود قابل اصلاح نیست.
پاسخ شماره ۲
مشکل این است که کلاس داخلی جاوااسکریپت Error زنجیره پروتوتایپ را خراب میکند زیرا هنگام فراخوانی super، شیء در حال ساخته شدن (یعنی this) را به یک شیء جدید و متفاوت تغییر میدهد و آن شیء جدید زنجیره پروتوتایپ مورد انتظار را ندارد، یعنی این شیء یک نمونه از Error است نه CustomError.
این مشکل را میتوان بهطور شیک با استفاده از 'new.target' حل کرد، که از نسخه ۲.۲ تایپاسکریپت پشتیبانی میشود، برای اطلاعات بیشتر اینجا ببینید: 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 این مزیت را دارد که نیازی به سختکدنویسی پروتوتایپ ندارید، مانند برخی جوابهای دیگر که در اینجا پیشنهاد شدهاند. این همچنین این مزیت را دارد که کلاسهایی که از CustomError ارثبری میکنند، بهطور خودکار نیز زنجیره پروتوتایپ صحیح را دریافت خواهند کرد.
اگر شما بخواهید نمونه اولیه را به صورت سختکد تعریف کنید (مثلاً Object.setPrototype(this, CustomError.prototype))، خود CustomError دارای زنجیره نمونه اولیه کاری خواهد بود، اما هر کلاس دیگری که از CustomError ارث میبرد خراب خواهد شد، مثلاً نمونههای یک class VeryCustomError < CustomError همانطور که انتظار میرود instanceof VeryCustomError نخواهند بود، بلکه تنها instanceof CustomError خواهند بود.
همچنین ببینید: https://github.com/Microsoft/TypeScript/issues/13965#issuecomment-278570200
پاسخ شماره ۳
از نسخه TypeScript 2.2 میتوان این کار را از طریق new.target.prototype انجام داد. 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
}
}
پاسخ شماره ۴
این در ES2015 به درستی کار میکند (https://jsfiddle.net/x40n2gyr/). به احتمال زیاد، مشکل این است که کامپایلر TypeScript به ES5 ترنسپایل میکند و Error نمیتواند به درستی با استفاده از ویژگیهای تنها ES5 subclass شود؛ تنها میتواند به درستی با ویژگیهای ES2015 و بالاتر subclass شود (class یا، به صورت پیچیدهتر، Reflect.construct). دلیل این است که وقتی Error را به عنوان یک تابع صدا میزنید (به جای استفاده از new یا، در ES2015، super یا Reflect.construct), this را نادیده میگیرد و یک Error جدید ایجاد میکند.
شاید مجبور باشید با خروجی ناقص زندگی کنید تا زمانی که بتوانید ES2015 یا بالاتر را هدف قرار دهید...
پاسخ شماره ۵
من واقعاً هیچوقت در SO پست نمیکنم، اما تیم من روی یک پروژه TypeScript کار میکند، و ما نیاز داشتیم که چندین کلاس خطای سفارشی بسازیم، در حالی که هدف ما es5 نیز بود. انجام راهحل پیشنهاد شده در هر کلاس خطای جداگانه واقعاً خستهکننده میبود. اما ما متوجه شدیم که میتوانیم با ایجاد یک کلاس خطای اصلی سفارشی و این که باقی خطاهای ما از آن کلاس extend کنند، اثر پسرو بر روی تمام کلاسهای خطای بعدی داشته باشیم. در داخل آن کلاس خطای اصلی ما کارهای زیر را انجام دادیم تا اثر پسرو بهروزرسانی پروتوتایپ را داشته باشیم:
class MainErrorClass extends Error {
constructor() {
super()
Object.setPrototypeOf(this, new.target.prototype)
}
}
class SomeNewError extends MainErrorClass {}
...
استفاده از new.target.prototype کلید بهروزرسانی تمام کلاسهای ارور ارثبریشده بود بدون اینکه نیازی به بهروزرسانی سازنده هر کدام باشد.
فقط امیدوارم که این در آینده کسی دیگر را از سردرد نجات دهد!
