Typescript: اقتراح: عبارة "رميات" وعبارة "catch" مكتوبة

تم إنشاؤها على ٢٩ ديسمبر ٢٠١٦  ·  135تعليقات  ·  مصدر: microsoft/TypeScript

يعد نظام الكتابة المطبوعة مفيدًا في معظم الحالات ، ولكن لا يمكن استخدامه عند معالجة الاستثناءات.
فمثلا:

function fn(num: number): void {
    if (num === 0) {
        throw "error: can't deal with 0";
    }
}

المشكلة هنا ذات شقين (دون النظر إلى الكود):

  1. عند استخدام هذه الوظيفة ، لا توجد طريقة لمعرفة أنها قد تؤدي إلى حدوث خطأ
  2. ليس من الواضح نوع (أنواع) الخطأ الذي سيكون

في العديد من السيناريوهات ، لا تمثل هذه مشكلة حقًا ، ولكن معرفة ما إذا كانت دالة / طريقة قد تؤدي إلى استثناء يمكن أن تكون مفيدة جدًا في سيناريوهات مختلفة ، خاصة عند استخدام مكتبات مختلفة.

من خلال تقديم استثناء (اختياري) محدد ، يمكن استخدام نظام النوع لمعالجة الاستثناءات.
أعلم أنه لم يتم الاتفاق على الاستثناءات المحددة (على سبيل المثال Anders Hejlsberg ) ، ولكن بجعلها اختيارية (وربما يتم الاستدلال عليها لاحقًا) ، فإنها تضيف فقط فرصة لإضافة المزيد من المعلومات حول الكود الذي يمكن أن يساعد المطورين والأدوات و توثيق.
سيسمح أيضًا باستخدام أفضل للأخطاء المخصصة ذات المعنى للمشاريع الكبيرة الكبيرة.

نظرًا لأن جميع أخطاء وقت تشغيل جافا سكريبت من النوع Error (أو توسيع الأنواع مثل TypeError ) ، فإن النوع الفعلي للدالة سيكون دائمًا type | Error .

القواعد واضحة ومباشرة ، يمكن أن ينتهي تعريف الوظيفة بجملة رميات متبوعة بنوع:

function fn() throws string { ... }
function fn(...) throws string | number { ... }

class MyError extends Error { ... }
function fn(...): Promise<string> throws MyError { ... }

عند اكتشاف الاستثناءات ، يكون بناء الجملة هو نفسه مع القدرة على التصريح عن نوع (أنواع) الخطأ:
catch(e: string | Error) { ... }

أمثلة:

function fn(num: number): void throws string {
    if (num === 0) {
        throw "error: can't deal with 0";
    }
}

من الواضح هنا أن الوظيفة يمكن أن تسبب خطأ وأن الخطأ سيكون عبارة عن سلسلة ، وبالتالي عند استدعاء هذه الطريقة ، يكون المطور (والمجمع / IDE) على دراية بها ويمكنه التعامل معها بشكل أفضل.
لذا:

fn(0);

// or
try {
    fn(0); 
} catch (e: string) { ... }

يجمع مع عدم وجود أخطاء ، ولكن:

try {
    fn(0); 
} catch (e: number) { ... }

فشل التجميع لأن number ليس string .

تدفق التحكم واستدلال نوع الخطأ

try {
    fn(0);
} catch(e) {
    if (typeof e === "string") {
        console.log(e.length);
    } else if (e instanceof Error) {
        console.log(e.message);
    } else if (typeof e === "string") {
        console.log(e * 3); // error: Unreachable code detected
    }

    console.log(e * 3); // error: The left-hand side of an arithmetic operation must be of type 'any', 'number' or an enum type
}
function fn(num: number): void {
    if (num === 0) {
        throw "error: can't deal with 0";
    }
}

رمى string .

function fn2(num: number) {
    if (num < 0) {
        throw new MyError("can only deal with positives");
    }

    fn(num);
}

رمى MyError | string .
ومع ذلك:

function fn2(num: number) {
    if (num < 0) {
        throw new MyError("can only deal with positives");
    }

    try {
        fn(num);
    } catch(e) {
        if (typeof e === "string") {
           throw new MyError(e);
       } 
    }
}

رمى فقط MyError .

Awaiting More Feedback Suggestion

التعليق الأكثر فائدة

@ aleksey-bykov

أنت تقترح عدم استخدام throw إطلاقًا في الكود الخاص بي وبدلاً من ذلك قم بلف النتائج (في وظائف قد تكون خطأ).
هذا النهج له بعض العيوب:

  • هذا الالتفاف يخلق المزيد من التعليمات البرمجية
  • يتطلب أن تقوم كل سلسلة الدالة المستدعاة بإرجاع هذه القيمة المغلفة (أو الخطأ) أو بدلاً من ذلك ، لا يمكن للوظيفة التي تحصل على Tried<> أن تختار تجاهل الخطأ.
  • إنها ليست مكتبات قياسية تابعة لجهات خارجية وتلقي أخطاء js الأصلية

ستؤدي إضافة throws إلى تمكين المطورين الذين يختارون معالجة الأخطاء من التعليمات البرمجية والمكتبات الثالثة و js الأصلية.
نظرًا لأن الاقتراح يطلب أيضًا استنتاج الخطأ ، يمكن أن تتضمن جميع ملفات التعريف المُنشأة عبارة throws .
سيكون من الملائم جدًا معرفة الأخطاء التي قد تظهرها الوظيفة مباشرة من ملف التعريف بدلاً من الحالة الحالية حيث تحتاج إلى الانتقال إلى المستندات ، على سبيل المثال لمعرفة الخطأ JSON.parse الذي قد أحتاجه للذهاب إلى صفحة MDN واقرأ ما يلي:

يطرح استثناءً SyntaxError إذا كانت السلسلة المراد تحليلها غير صالحة JSON

وهذه هي الحالة الجيدة عند توثيق الخطأ.

ال 135 كومينتر

فقط للتوضيح - إحدى الأفكار هنا ليست إجبار المستخدمين على الإمساك بالاستثناء ، ولكن بدلاً من ذلك ، لاستنتاج نوع متغير عبارة catch بشكل أفضل؟

تضمين التغريدة
نعم ، لن يتم إجبار المستخدمين على اكتشاف الاستثناءات ، لذلك هذا جيد مع المترجم (في وقت التشغيل يتم إلقاء الخطأ بالطبع):

function fn() {
    throw "error";
}

fn();

// and
try {
    fn();
} finally {
    // do something here
}

ولكنه سيمنح المطورين طريقة للتعبير عن الاستثناءات التي يمكن طرحها (سيكون من الرائع الحصول على ذلك عند استخدام مكتبات أخرى .d.ts ملفات) ومن ثم اجعل نوع المحول البرمجي نوع الاستثناءات داخل جملة catch.

كيف تختلف الرمية المحددة عن Tried<Result, Error> ؟

type Tried<Result, Error> = Success<Result> | Failure<Error>;
interface Success<Result> { kind: 'result', result: Result } 
interface Failure<Error> { kind: 'failure', error: Error }
function isSuccess(tried: Tried<Result, Error>): tried is Success<Result> {
   return tried.kind === 'result';
}
function mightFail(): Tried<number, string> {
}
const tried = mightFail();
if (isSuccess(tried)) {
    console.log(tried.success);
}  else {
    console.error(tried.error);
}

بدلا من

try {
    const result: Result = mightFail();
    console.log(success);
} catch (error: Error) {
    console.error(error);
}

@ aleksey-bykov

أنت تقترح عدم استخدام throw إطلاقًا في الكود الخاص بي وبدلاً من ذلك قم بلف النتائج (في وظائف قد تكون خطأ).
هذا النهج له بعض العيوب:

  • هذا الالتفاف يخلق المزيد من التعليمات البرمجية
  • يتطلب أن تقوم كل سلسلة الدالة المستدعاة بإرجاع هذه القيمة المغلفة (أو الخطأ) أو بدلاً من ذلك ، لا يمكن للوظيفة التي تحصل على Tried<> أن تختار تجاهل الخطأ.
  • إنها ليست مكتبات قياسية تابعة لجهات خارجية وتلقي أخطاء js الأصلية

ستؤدي إضافة throws إلى تمكين المطورين الذين يختارون معالجة الأخطاء من التعليمات البرمجية والمكتبات الثالثة و js الأصلية.
نظرًا لأن الاقتراح يطلب أيضًا استنتاج الخطأ ، يمكن أن تتضمن جميع ملفات التعريف المُنشأة عبارة throws .
سيكون من الملائم جدًا معرفة الأخطاء التي قد تظهرها الوظيفة مباشرة من ملف التعريف بدلاً من الحالة الحالية حيث تحتاج إلى الانتقال إلى المستندات ، على سبيل المثال لمعرفة الخطأ JSON.parse الذي قد أحتاجه للذهاب إلى صفحة MDN واقرأ ما يلي:

يطرح استثناءً SyntaxError إذا كانت السلسلة المراد تحليلها غير صالحة JSON

وهذه هي الحالة الجيدة عند توثيق الخطأ.

وهذه هي الحالة الجيدة عند توثيق الخطأ.

هل هناك طريقة موثوقة في جافا سكريبت للتمييز بين الخطأ النحوي والخطأ؟

  • نعم ، إنه رمز أكثر ، ولكن نظرًا لوجود موقف سيئ في كائن ، يمكن تمريره لمعالجته أو التخلص منه أو تخزينه أو تحويله إلى نتيجة صالحة تمامًا مثل أي قيمة أخرى

  • يمكنك تجاهل المحاولة من خلال إعادة المحاولة أيضًا ، ويمكن اعتبار "المحاولة" وحدة أحادية ، ابحث عن الحسابات الأحادية

    function mightFail(): Tried<number, string> {
    }
    function mightFailToo(): Tried<number, string> {
        const tried = mightFail();
        if (isSuccess(tried))  { 
             return successFrom(tried.result * 2);
        } else {
             return tried;
        }
    }
    
  • إنه قياسي بما فيه الكفاية بالنسبة إلى الكود الخاص بك ، عندما يتعلق الأمر بملفات الطرف الثالث التي ترمي استثناءً ، فهذا يعني عمومًا تجاوز اللعبة بالنسبة لك ، لأنه يكاد يكون من المستحيل التعافي بشكل موثوق من استثناء ، والسبب هو أنه يمكن إلقاؤه من أي مكان داخل الكود إنهائها تعسفيا وترك حالتها الداخلية ناقصة أو فاسدة

  • لا يوجد دعم للاستثناءات المحددة من وقت تشغيل JavaScript ، وأخشى أنه لا يمكن تنفيذه في الكتابة المطبوعة وحدها

بخلاف ذلك الترميز الاستثناء كحالة نتيجة خاصة هو ممارسة شائعة جدًا في عالم FP

أثناء تقسيم النتيجة المحتملة إلى جزأين:

  • واحد تسليمها بيان العودة و
  • آخر سلمت عن طريق رمي

تبدو معقدة

في رأيي ، يعد الرمي مفيدًا للفشل بسرعة وبصوت عالٍ عندما لا يمكنك فعل أي شيء حيال ذلك ، فالنتائج المشفرة صراحة جيدة لأي شيء يشير إلى موقف سيئ ولكنه متوقع يمكنك التعافي منه

يعتبر:

// throw/catch
declare function doThis(): number throws string;
declare function doThat(): number throws string;
function doSomething(): number throws string {
    let oneResult: number | undefined = undefined;
    try {
        oneResult = doThis();
    } catch (e) {
        throw e;
    }

    let anotherResult: number | undefined = undefined;
    try {
        anotherResult = doThat();
    } catch (e) {
        throw e;
    }
    return oneResult + anotherResult;
}

// explicit results
declare function doThis(): Tried<number, string>;
declare function doThat(): Tried<number, string>;
function withBothTried<T, E, R>(one: Tried<T, E>, another: Tried<T, E>, haveBoth: (one: T, another: T) => R): Tried<T, R> {
    return isSuccess(one)
        ? isSuccess(another)
            ? successFrom(haveBoth(one.result, another.result))
            : another
        : one;
}
function add(one: number, another: number) { return one + another; }
function doSomething(): Tried<number, string> {
    return withBothTried(
        doThis(),
        doThat(),
        add
    );
}

@ aleksey-bykov

وجهة نظري مع JSON.parse قد تطرح SyntaxError هي أنني بحاجة إلى البحث عن الوظيفة في المستندات فقط لأعرف أنه قد يتم طرحها ، وسيكون من الأسهل رؤية ذلك في .d.ts .
ونعم ، يمكنك معرفة أنه SyntaxError باستخدام instanceof .

يمكنك تمثيل نفس الموقف السيئ بإلقاء خطأ.
يمكنك إنشاء فئة الخطأ الخاصة بك والتي تمتد إلى Error وتضع جميع البيانات ذات الصلة التي تحتاجها فيها.
أنت تحصل على نفس الشيء برمز أقل.

في بعض الأحيان يكون لديك سلسلة طويلة من استدعاءات الوظائف وقد ترغب في التعامل مع بعض الأخطاء في مستويات مختلفة من السلسلة.
سيكون مزعجًا جدًا استخدام النتائج المغلفة (monads) دائمًا.
ناهيك عن ذلك مرة أخرى ، قد يتم إلقاء مكتبات أخرى وأخطاء أصلية على أي حال ، لذلك قد ينتهي بك الأمر باستخدام كل من monads ومحاولة / catch.

أنا لا أتفق معك ، في كثير من الحالات يمكنك التعافي من الأخطاء التي تم إلقاؤها ، وإذا سمحت لك اللغة بالتعبير عنها بشكل أفضل ، فسيكون من الأسهل القيام بذلك.

كما هو الحال مع الكثير من الأشياء في الكتابة المطبوعة ، فإن نقص دعم الميزة في جافا سكريبت ليس مشكلة.
هذه:

try {
    mightFail();
} catch (e: MyError | string) {
    if (e instanceof MyError) { ... }
    else if (typeof e === "string") { ... }
    else {}
}

سيعمل كما هو متوقع في جافا سكريبت ، فقط بدون نوع التعليق التوضيحي.

استخدام throw كافٍ للتعبير عن ما تقوله: إذا نجحت العملية ، فقم بإرجاع القيمة ، وإلا ستظهر خطأ.
سيقرر مستخدم هذه الوظيفة بعد ذلك ما إذا كان يريد التعامل مع الأخطاء المحتملة أو تجاهلها.
يمكنك التعامل مع الأخطاء التي ألقيتها بنفسك فقط وتجاهل الأخطاء التي تكون طرفًا ثالثًا على سبيل المثال.

إذا كنا نتحدث عن المتصفحات instanceof فهي جيدة فقط للأشياء التي تنشأ من نفس النافذة / المستند ، فجربها:

var child = window.open('about:blank');
console.log(child.Error === window.Error);

لذلك عندما تفعل:

try { child.doSomething(); } catch (e) { if (e instanceof SyntaxError) { } }

لن تمسك به

مشكلة أخرى مع الاستثناءات التي قد تنزلق إلى التعليمات البرمجية الخاصة بك من أبعد مما تتوقع حدوثها

try {
   doSomething(); // <-- uses 3rd party library that by coincidence throws SyntaxError too, but you don' t know it 
} catch (e) {}

إلى جانب instanceof عرضة لميراث النموذج الأولي ، لذلك عليك أن تكون أكثر حذرًا لتتحقق دائمًا من السلف الأخير

class StandardError {}
class CustomError extends StandardError {
}
function doSomething() { throw new CustomError(); }
function oldCode() {
   try {
      doSomething();
   } catch (e) {
      if (e instanceof StandardError) {
          // problem
      }
   }
}

@ aleksey-bykov تعد أخطاء الترابط الصريحة التي تقترحها في الهياكل الأحادية مهمة صعبة وشاقة للغاية. يتطلب الأمر الكثير من الجهد ، ويجعل من الصعب فهم الكود ويتطلب دعمًا للغة / إصدارًا مدفوعًا بالنوع ليكون على وشك أن يكون محتملاً. هذا تعليق صادر عن شخص يبذل الكثير من الجهد لترويج Haskell و FP ككل.

إنه بديل عملي ، خاصة للمتحمسين (بمن فيهم أنا) ، لكنني لا أعتقد أنه خيار قابل للتطبيق لجمهور أكبر.

في الواقع ، شاغلي الرئيسي هنا هو أن الناس سيبدأون في تصنيف خطأ فئة فرعية. أعتقد أن هذا نمط رهيب. بشكل عام ، أي شيء يشجع على استخدام مثيل عامل التشغيل سيؤدي فقط إلى خلق ارتباك إضافي حول الفئات.

هذا تعليق صادر عن شخص يبذل الكثير من الجهد لترويج Haskell و FP ككل.

أعتقد حقًا أنه يجب دفع هذا الأمر بشكل أكبر للجمهور ، ليس حتى يتم استيعابها وطلب المزيد ، هل يمكننا الحصول على دعم FP أفضل في اللغة

وهو ليس شاقًا كما تعتقد ، بشرط أن تكون جميع أدوات الدمج مكتوبة بالفعل ، فقط استخدمها لبناء تدفق بيانات ، كما نفعل في مشروعنا ، لكنني أوافق على أن TS كان بإمكانه دعمه بشكل أفضل: # 2319

محولات موناد هي بيتا حقيقي. أنت بحاجة إلى الرفع والرفع والركض الانتقائي في كثير من الأحيان. والنتيجة النهائية هي رمز بالكاد يمكن فهمه وأعلى بكثير من حاجز الدخول المطلوب. جميع أدوات الدمج ووظائف الرفع (التي توفر الملاكمة الإلزامية / فتح الصندوق) هي مجرد ضوضاء تشتت انتباهك عن المشكلة المطروحة. أعتقد أن الصراحة بشأن الحالة والتأثيرات وما إلى ذلك أمر جيد ، لكنني لا أعتقد أننا وجدنا غلافًا / تجريدًا مناسبًا حتى الآن. إلى أن نجدها ، يبدو أن دعم أنماط البرمجة التقليدية هو السبيل للذهاب دون التوقف للتجربة والاستكشاف في الوقت نفسه.

ملاحظة: أعتقد أننا بحاجة إلى أكثر من مشغلين مخصصين. تعد الأنواع الأعلى نوعًا ونوعًا من فئات النوع ضرورية أيضًا لمكتبة أحادية عملية. من بينها ، كنت أقوم بتقييم HKT أولاً ثم اكتب الفئات في المرتبة الثانية. مع كل ما قيل ، أعتقد أن TypeScript ليست لغة لممارسة مثل هذه المفاهيم. اللعب حولها - نعم ، لكن فلسفتها وجذورها بعيدة جدًا عن التكامل السلس السليم.

العودة إلى سؤال OP - instanceof عامل تشغيل خطير للاستخدام. لكن الاستثناءات الصريحة لا تقتصر على Error . يمكنك طرح أخطاء ADTs الخاصة بك أو أخطاء POJO المخصصة أيضًا. يمكن أن تكون الميزة المقترحة مفيدة جدًا ، وبالطبع يمكن أيضًا إساءة استخدامها بشكل صعب. على أي حال ، فإنه يجعل الوظائف أكثر شفافية وهو أمر جيد بلا شك. ككل أنا 50/50 على ذلك :)

@ aleksey-bykov

يجب أن يكون المطورون على دراية بقضايا js المختلفة التي وصفتها ، فبعد كل إضافة throws إلى الكتابة المطبوعة لا تقدم أي شيء جديد لـ js ، فهي تعطي فقط الكتابة المطبوعة كلغة القدرة على التعبير عن سلوك js الحالي.

إن حقيقة أن مكتبات الطرف الثالث تحتوي على أخطاء برمية هي بالضبط وجهة نظري.
إذا كانت ملفات التعريف الخاصة بهم تتضمن ذلك ، فستكون لدي طريقة لمعرفة ذلك.

تضمين التغريدة
لماذا يعتبر تمديد Error نمطًا فظيعًا؟

تضمين التغريدة
بالنسبة إلى instanceof ، كان هذا مجرد مثال ، يمكنني دائمًا إلقاء أشياء عادية لها أنواع مختلفة ثم استخدام حراس الكتابة للتمييز بينهم.
سيكون الأمر متروكًا للمطور ليقرر نوع الأخطاء التي يرغب في إلقاؤها ، وربما يكون هذا هو الحال بالفعل ، ولكن لا توجد حاليًا طريقة للتعبير عن ذلك ، وهو ما يريد هذا الاقتراح حله.

nitzantomer الفئات الفرعية للفئات الأصلية ( Error ، Array ، RegExp ، إلخ) لم تكن مدعومة في إصدارات ECMAScript القديمة (قبل ES6). يعطي المستوى الأدنى المنبعث من هذه الفئات نتائج غير متوقعة (يتم بذل أفضل جهد ولكن هذا أقصى ما يمكن للمرء أن يذهب إليه) وهو سبب العديد من المشكلات التي يتم تسجيلها على أساس يومي. كقاعدة عامة - لا تندرج ضمن الفئات الفرعية الأصلية إلا إذا كنت تستهدف إصدارات ECMAScript الأخيرة وتعرف حقًا ما تفعله.

تضمين التغريدة
أوه ، أنا أدرك ذلك جيدًا لأنني قضيت أكثر من بضع ساعات في محاولة لمعرفة الخطأ الذي حدث.
ولكن مع القدرة على القيام بذلك الآن ، لا ينبغي أن يكون هناك سبب لعدم القيام بذلك (عند استهداف es6).

في أي حال من الأحوال ، لا يفترض هذا الاقتراح أن المستخدم يقوم بتصنيف فئة الخطأ إلى فئة فرعية ، بل كان مجرد مثال.

nitzantomer لا أجادل في أن الاقتراح يقتصر على Error . لقد شرحت للتو سبب تصنيفها إلى نمط سيء. في رسالتي ، دافعت بالفعل عن الموقف القائل بأنه يمكن أيضًا استخدام الكائنات المخصصة أو النقابات التمييزية.

يعد instanceof أمرًا خطيرًا ويعتبر مضادًا للنمط حتى إذا قمت بإزالة خصائص JavaScript - على سبيل المثال ، احذر من مثيل عامل التشغيل . والسبب هو أن المترجم لا يمكنه حمايتك من الأخطاء التي تسببها الفئات الفرعية الجديدة. المنطق باستخدام instanceof هش ولا يتبع مبدأ الفتح / الإغلاق ، لأنه لا يتوقع سوى عدد قليل من الخيارات. حتى إذا تمت إضافة حالة أحرف البدل ، فمن المحتمل أن تتسبب المشتقات الجديدة في حدوث أخطاء لأنها قد تكسر الافتراضات التي تم إجراؤها في وقت كتابة هذا التقرير.

بالنسبة للحالات التي تريد فيها التمييز بين البدائل المعروفة ، يحتوي TypeScript على اتحادات ذات علامات (تسمى أيضًا الاتحادات المميزة أو أنواع البيانات الجبرية). يتأكد المترجم من التعامل مع جميع الحالات مما يمنحك ضمانات جيدة. الجانب السلبي هو أنه إذا كنت ترغب في إضافة إدخال جديد إلى النوع ، فسيتعين عليك المرور عبر جميع التعليمات البرمجية التي تميزه والتعامل مع الخيار المضاف حديثًا. الجانب الإيجابي هو أنه من المرجح أن يتم كسر هذا الرمز ، لكنه كان سيفشل في وقت التشغيل.

لقد فكرت في هذا الاقتراح مرة أخرى وعارضته. والسبب هو أنه إذا كانت إعلانات throws موجودة على التوقيعات ولكن لم يتم فرضها ، فيمكن معالجتها بالفعل عن طريق تعليقات التوثيق. في حالة فرضها ، أشارك الشعور بأنهم سيصبحون مزعجين وسرعان ما يتم ابتلاعهم لأن JavaScript يفتقر إلى آلية Java الخاصة بعبارات الصيد المكتوبة. لم يكن استخدام الاستثناءات (خاصةً كتدفق التحكم) ممارسة ثابتة أيضًا. يقودني كل هذا إلى فهم أن الاستثناءات المحددة تجلب القليل جدًا ، بينما تتوفر طرق أفضل وأكثر شيوعًا حاليًا لتمثيل الفشل (مثل عودة الاتحاد).

تضمين التغريدة
هذه هي الطريقة التي يتم بها ذلك في C # ، المشكلة هي أن المستندات ليست قياسية في الكتابة المطبوعة.
لا أتذكر أنني صادفت ملف تعريف موثق جيدًا. تحتوي ملفات lib.d.ts المختلفة على تعليقات ، لكن تلك لا تحتوي على أخطاء في الإلقاء (مع استثناء واحد: يحتوي lib.es6.d.ts على واحد throws في Date[Symbol.toPrimitive](hint: string) ).

أيضًا ، يأخذ هذا الاقتراح في الاعتبار خطأ الاستدلال ، وهو أمر لن يحدث إذا كانت الأخطاء تأتي من تعليقات التوثيق. مع الاستثناءات المحددة المستنتجة ، لن يحتاج المطور حتى إلى تحديد عبارة throws ، سوف يستنتجها المترجم تلقائيًا وسيستخدمها للترجمة وسيضيفها إلى ملف التعريف الناتج.

أوافق على أن فرض معالجة الأخطاء ليس بالأمر الجيد ، ولكن امتلاك هذه الميزة سيضيف المزيد من المعلومات التي يمكن استخدامها بعد ذلك من قبل أولئك الذين يرغبون في ذلك.
المشكلة مع:

... هناك طرق أفضل وأكثر شيوعًا حاليًا لتمثيل الفشل

هو أنه لا توجد طريقة معيارية للقيام بذلك.
يمكنك استخدام union return ، سيستخدم @ aleksey-bykov Tried<> ، وسيقوم مطور مكتبة أخرى تابعة لجهة خارجية بعمل شيء مختلف تمامًا.
رمي الأخطاء هو معيار عبر اللغات (js ، java ، c # ...) ولأنه جزء من النظام وليس حلاً ، يجب (في رأيي) التعامل بشكل أفضل في الكتابة المطبوعة ، وإثبات ذلك هو الرقم من المشكلات التي رأيتها هنا بمرور الوقت والتي تطلب كتابة التعليق التوضيحي في عبارة catch .

أرغب في الحصول على معلومات في تلميح الأدوات في VS إذا كانت هناك وظيفة (أو تسمى وظيفة) يمكن طرحها. بالنسبة لملفات *.d.ts ، ربما نحتاج إلى معامل زائف مثل هذا منذ TS2.0.

تضمين التغريدة
لماذا ستكون هناك حاجة؟

هنا سؤال بسيط ، ما هو التوقيع الذي يجب استنتاجه لـ dontCare في الكود أدناه؟

function mightThrow(): void throws string {
   if (Math.random() > 0.5) {
       throw 'hey!';
   }
}

function dontCare() {
   return mightThrow();
}

حسب ما قلته في اقتراحك يجب أن يكون

function dontCare(): void throws string {

أقول أنه يجب أن يكون خطأ نوع حيث لم يتم التعامل مع استثناء محدد بشكل صحيح

function dontCare() { // <-- Checked exception wasn't handled.
         ^^^^^^^^^^

لماذا هذا؟

لأنه بخلاف ذلك ، هناك فرصة جيدة جدًا لإفساد حالة المتصل المباشر:

class MyClass {
    private values: number[] = [];

    keepAllValues(values: number[]) {
       for (let index = 0; index < values.length; index ++) {
            this.values.push(values[index]); 
            mightThrow();
       }
    }
}

إذا تركت استثناءً يتسلل من خلاله ، فلا يمكنك الاستدلال عليه على أنه تم التحقق منه ، لأن عقد السلوك keepAllValues سيتم انتهاكه بهذه الطريقة (لم يتم الاحتفاظ بجميع القيم على الرغم من القصد الأصلي)

الطريقة الآمنة الوحيدة هي القبض عليهم فورًا وإعادة إلقاءهم صراحةً

    keepAllValues(values: number[]) {
           for (let index = 0; index < values.length; index ++) {
                this.values.push(values[index]); 
                try {
                    mightThrow();
                } catch (e) {
                    // the state of MyClass is going to be corrupt anyway
                    // but unlike the other example this is a deliberate choice
                    throw e;
                }
           }
    }

خلافًا لذلك ، على الرغم من أن المتصلين يعرفون ما الذي يمكن أن يتم طرحه ، لا يمكنك منحهم ضمانات بأنه من الآمن المتابعة باستخدام الكود الذي تم إلقاؤه للتو

لذلك لا يوجد شيء مثل نشر عقد الاستثناء الذي تم فحصه تلقائيًا

وصححني إذا كنت مخطئًا ، فهذا بالضبط ما تفعله Java ، والذي ذكرته كمثال سابقًا

@ aleksey-bykov
هذه:

function mightThrow(): void {
   if (Math.random() > 0.5) {
       throw 'hey!';
   }
}

function dontCare() {
   return mightThrow();
}

يعني أن كلا من mightThrow و dontCare يُستدل عليه بـ throws string ، ومع ذلك:

function dontCare() {
    try {
        return mightThrow();
    } catch (e: string) {
        // do something
    }
}

لن يكون لديك عبارة throw لأنه تم معالجة الخطأ.
هذه:

function mightThrow(): void throws string | MyErrorType { ... }

function dontCare() {
    try {
        return mightThrow();
    } catch (e: string | MyErrorType) {
        if (typeof e === "string") {
            // do something
        } else { throw e }
    }
}

سيكون لديك throws MyErrorType .

بالنسبة لمثال keepAllValues ، لست متأكدًا مما تقصده ، في المثال الخاص بك:

class MyClass {
    private values: number[] = [];

    keepAllValues(values: number[]) {
       for (let index = 0; index < values.length; index ++) {
            this.values.push(values[index]); 
            mightThrow();
       }
    }
}

سيتم الاستدلال على MyClass.keepAllValues على أنه throws string لأن mightThrow قد ينتج عنه string ولم يتم التعامل مع هذا الخطأ.

بالنسبة لمثال keepAllValues ، لست متأكدًا مما تقصده

قصدت أن الاستثناءات القادمة غير المعالجة من mightThrow تقاطع keepAllValues وتجعلها تنتهي وسط ما كانت تفعله تاركة حالتها فاسدة. إنها مشكلة. ما تقترحه هو أن تغمض عينيك عن هذه المشكلة وتتظاهر بأنها ليست خطيرة. ما أقترحه هو معالجة هذه المشكلة من خلال المطالبة بمعالجة جميع الاستثناءات المحددة على الفور وإعادة عرضها بشكل صريح. بهذه الطريقة لا توجد طريقة لإفساد الدولة عن غير قصد . وعلى الرغم من أنه قد يظل فاسدًا إذا اخترت ذلك ، إلا أنه سيتطلب بعض الترميز المتعمد.

فكر في الأمر ، هناك طريقتان يمكننا اتباعهما بشأن الاستثناءات:

  • قم بإزالتها ، مما يؤدي إلى وقوع حادث ، إذا كان الانهيار هو ما تريده ، فنحن بخير هنا
  • إذا كنت لا ترغب في حدوث عطل ، فأنت بحاجة إلى بعض الإرشادات حول نوع الاستثناء الذي يجب أن تبحث عنه ، وهذا هو المكان الذي يأتي فيه اقتراحك: الاستثناءات المحددة - جميعها مدرجة بشكل صريح ، بحيث يمكنك التعامل معها جميعًا وعدم لا تفوت أي شيء

الآن إذا قررنا اتباع الاستثناءات المحددة التي يتم التعامل معها بشكل صحيح ومنع حدوث عطل ، فنحن بحاجة إلى استبعاد حالة عندما نتعامل مع استثناء قادم من عدة طبقات في عمق المكان الذي تلتقطه فيه:

export function calculateFormula(input) {
    return calculateSubFormula(input);
}
export function calculateSubFormula(input) {
   return calculateSubSubFormula(input);
}
export function calculateSubSubFormula(input): number throws DivisionByZero  {
   return 1/input;
}

try {
   calculateFormula(0);
} catch (e: DivisionByZero) {
   // it doesn't make sense to expose DivisionByZero from under several layers of calculations
   // to the top level where nothing we can do or even know what to do about it
   // basically we cannot recover from it, because it happened outside of our immediate reach that we can control
}

المثال أعلاه يجلب حالة مثيرة للاهتمام للنظر فيها ، ما هو التوقيع المستنتج لـ:

function boom(value: number) /* what comes here?*/  {
    return 1/value;
}

حالة أخرى مثيرة للاهتمام

// 1.
function run<R, E>(callback(): R throws E) /* what comes here? */ {
    try {
        return callback();
    } catch (e: DivisionByZero) {
        // ignore
    }
}

function throw() { return 1 / 0; }

// 2.
run(throw); /* what do we expect here? */


@ aleksey-bykov
لذا تقترح أنه يجب التعامل مع جميع الأخطاء كما هو الحال مع جافا؟
أنا لست من المعجبين بذلك (على الرغم من أنني أتيت من جافا وما زلت أحبه) لأن js / ts أكثر ديناميكية ومستخدميهم معتادون على ذلك.
يمكن أن تكون علامة تجعلك تتعامل مع الأخطاء إذا قمت بتضمينها عند التجميع (مثل strictNullChecks ).

اقتراحي ليس هنا لحل الاستثناءات التي لم تتم معالجتها ، فالشفرة التي نشرتها ستتعطل الآن بدون تنفيذ هذه الميزة ، وستتعطل أيضًا في js.
يتيح لك اقتراحي كمطور أن تكون أكثر وعياً بالأخطاء المختلفة التي قد يتم طرحها ، ولا يزال الأمر متروكًا لك إذا كنت تتعامل معها أو تتجاهلها.

أما مسألة القسمة على 0 فلا ينتج عنها خطأ:

console.log(1 / 0) // Infinity
console.log(1 / "hey!") // NaN

أكثر وعياً بالأخطاء المختلفة التي قد يتم إلقاؤها

ليس هناك فائدة من القيام بذلك ما لم يتمكنوا من التعامل معهم ، فإن الاقتراح الحالي غير قابل للتطبيق بسبب الحالات التي ذكرتها

لذا تقترح أنه يجب التعامل مع جميع الأخطاء كما هو الحال مع جافا؟

نعم ، هذا ما يعنيه التحقق من الاستثناءات

@ aleksey-bykov
لا أفهم سبب جعل أي من الحالات التي ذكرتها هذا الاقتراح غير قابل للتطبيق.

لا توجد مشكلة في التعامل مع خطأ تم إلقاؤه في سلسلة الاستدعاء ، حتى لو كنت أستخدم وظيفة تم استنتاجها من رمي DivisionByZero (بغض النظر عن المكان الذي تم إلقاؤه فيه) ، يمكنني اختيار التعامل معه .
يمكنني محاولة إعادة المحاولة باستخدام وسيطات مختلفة ، ويمكنني إظهار رسالة للمستخدم تفيد بحدوث خطأ ما ، ويمكنني تسجيل هذه المشكلة حتى أتمكن لاحقًا من تغيير الكود الخاص بي للتعامل معها (إذا حدث ذلك كثيرًا).

مرة أخرى ، لا يغير هذا الاقتراح أي شيء في وقت التشغيل ، لذا فإن كل ما نجح سيستمر في العمل كما كان من قبل.
الاختلاف الوحيد هو أنه سيكون لدي المزيد من المعلومات حول الأخطاء التي قد يتم إلقاؤها.

أرى ما تقوله ، لن يتغير شيء في وقت تشغيل جافا سكريبت ، ولكن رسالتك هنا هي إعطاء المستخدمين بعض الوهم بأنهم يعرفون ما يفعلونه من خلال التعامل مع استثناء جاء من 20 طبقة بالأسفل بنفس الثقة مثل سيتعاملون مع استثناء فوري

ببساطة لا توجد طريقة لإصلاح المشكلة التي حدثت في 20 طبقة بالأسفل

يمكنك تسجيله ، بالتأكيد ، تمامًا مثل أي استثناء لم يتم التحقق منه ، لكن لا يمكنك إصلاحه

لذلك فهي كذبة بشكل عام ، هناك ما يكفي من الأكاذيب في TS ، دعونا لا نربك الناس أكثر

@ aleksey-bykov
ما تصفه موجود بجميع اللغات التي تدعم الاستثناءات.
لم يقل أحد أن العثور على استثناء سيؤدي إلى حل المشكلة ، لكنه سيسمح لك بالتعامل معها بأمان.

إن معرفة الأخطاء التي يمكن إلقاؤها عند استدعاء دالة ما سيساعد المطورين على الفصل بين الأخطاء التي يمكنهم معالجتها وتلك التي لا يمكنهم معالجتها.

قد لا يعرف المطورون حاليًا أن استخدام JSON.parse قد يؤدي إلى حدوث خطأ ، ولكن إذا كان جزءًا من lib.d.ts وكان IDE سيعلمه (على سبيل المثال) ، فربما يختار ذلك التعامل مع هذه الحالة.

لا يمكنك التعامل مع مشكلة حدثت 20 طبقة أدناه بأمان ، لأن الحالة الداخلية فاسدة في 19 طبقة ولا يمكنك الذهاب إلى هناك لأن الدولة خاصة

لتكون بناء: ما أقترحه هو مطالبة المستخدمين بالتعامل مع الاستثناءات التي تم التحقق منها على الفور وإعادة طرحها بشكل صريح ، وبهذه الطريقة نستبعد الارتباك غير المقصود ونفصل الاستثناءات التي تم التحقق منها عن الاستثناءات غير المحددة:

  • تم التحقق من الاستثناء: حدث في متناول اليد ويجب التعامل معه ، وبهذه الطريقة نضمن أن الحالة ليست فاسدة وأنه من الآمن المضي قدمًا
  • استثناء لم يتم التحقق منه: حدث في متناول اليد أو أبعد من ذلك بكثير ، ولا يمكن التعامل معه لأن الحالة كانت فاسدة ، يمكنك تسجيله أو المضي قدمًا على مسؤوليتك الخاصة

يجب التصريح عن SyntaxError in JSON.parse كاستثناء محدد

@ aleksey-bykov

لا أرى سببًا لضرورة إجبار المطورين على فعل شيء لا يرغبون فيه ، شيء لم يفعلوه حتى الآن.

هذا مثال:
لدي عميل ويب حيث يمكن للمستخدم كتابة / لصق بيانات json ، ثم النقر فوق زر.
يأخذ التطبيق هذا الإدخال ويمرره إلى مكتبة تابعة لجهة خارجية تقوم بطريقة ما بتحليل json هذا وإرجاع json مع الأنواع المختلفة من القيم (سلسلة ، رقم ، منطقي ، مصفوفة ، إلخ).
إذا قامت مكتبة الطرف الثالث هذه برمي SyntaxError يمكنني استرداده: أبلغ المستخدم أن مدخلاته غير صالحة وعليه المحاولة مرة أخرى.

من خلال معرفة الأخطاء التي يمكن إلقاؤها عند استدعاء إحدى الوظائف ، يمكن للمطور أن يقرر ما يمكنه / يرغب في معالجته وما لا يمكنه التعامل معه.
لا يهم مدى عمق السلسلة التي تم إلقاء الخطأ فيها.

يبدو أنك لا تفهم ما أقوله ، نحن في دوائر

من خلال السماح بإلقاء SyntaxError من مكتبة تابعة لجهات خارجية ، فأنت بذلك تعرض المستخدم لتفاصيل تنفيذ التعليمات البرمجية الخاصة بك والتي من المفترض أن يتم تغليفها

أنت تقول بشكل أساسي ، ليس الرمز الخاص بي هو الذي لا يعمل ، إنها تلك المكتبة الغبية التي وجدتها على الإنترنت واستخدمتها ، لذلك إذا كانت لديك مشكلة معها ، تعامل مع هذا الطرف الثالث ، وليس أنا ، قلت للتو ما طلب مني

وليس هناك ما يضمن أنه لا يزال بإمكانك استخدام مثيل هذا lib الثالث بعد هذا الخطأ النحوي ، تقع على عاتقك مسؤولية توفير ضمانات للمستخدم ، على سبيل المثال من خلال إعادة التحكم في الطرف الثالث بعد أن ألقى

خلاصة القول ، يجب أن تكون مسؤولاً عن التعامل مع الاستثناءات الداخلية ( ليس كلها ، فقط تلك المحددة ، أتوسل إليك )

إنني أفهم ما تقوله ، لكنني لا أتفق معه.
أنت على حق ، هذا ما أقوله بشكل أساسي.
إذا استخدمت مكتبة تابعة لجهة خارجية تسببت في حدوث خطأ ، يمكنني اختيار التعامل معها أو تجاهلها والسماح لمستخدم الكود الخاص بي بالتعامل معها.
هناك العديد من الأسباب للقيام بذلك ، على سبيل المثال ، lib الذي أكتبه هو محايد لواجهة المستخدم ، لذلك لا يمكنني إبلاغ المستخدم بوجود خطأ ما ، ولكن من يستخدم lib الخاص بي يمكنه التعامل مع الأخطاء التي يتم إلقاؤها عند الاستخدام بلدي lib والتعامل معها من خلال التفاعل مع المستخدم.

إذا تُركت مكتبة بحالة تالفة عند رميها ، فمن المحتمل أن تحتاج إلى توثيقها.
إذا استخدمت هذه المكتبة بعد ذلك ونتيجة لحدوث خطأ فيها تصبح حالتي تالفة ، فأنا بحاجة إلى توثيقها.

الحد الأدنى:
يأتي هذا الاقتراح لتقديم مزيد من المعلومات حول الأخطاء التي تم إلقاؤها.
لا ينبغي أن تفرض على المطورين القيام بالأشياء بشكل مختلف ، فقط اجعل الأمر أسهل عليهم للتعامل مع الأخطاء إذا اختاروا ذلك.

يمكنك عدم الموافقة ، لا بأس ، دعنا فقط لا نطلق عليها الاستثناءات المحددة من فضلك ، لأن الطريقة التي تضعها بها ليست هي الاستثناءات المحددة

دعنا نسميها استثناءات مدرجة أو مكشوفة ، لأن كل ما يهمك هو جعل المطورين على دراية بها

@ aleksey-bykov
عادل بما فيه الكفاية ، تغير الاسم.

@ aleksey-bykov

لا يمكنك التعامل مع مشكلة حدثت 20 طبقة أدناه بأمان ، لأن الحالة الداخلية فاسدة في 19 طبقة ولا يمكنك الذهاب إلى هناك لأن الدولة خاصة

لا ، لا يمكنك إصلاح الحالة الداخلية ، ولكن يمكنك بالتأكيد إصلاح الحالة المحلية ، وهذا بالضبط هو الهدف من التعامل معها هنا وليس بشكل أعمق في المكدس.

إذا كانت حجتك هي أنه لا توجد طريقة للتأكد من الحالة التي توجد بها بعض القيم القابلة للتغيير المشتركة عند التعامل مع الاستثناء ، فهذه حجة ضد البرمجة الإلزامية ، وليست مقتصرة على هذا الاقتراح.

إذا كانت كل طبقة ملزمة بتحمل مسؤولية الرد على استثناء يأتي فورًا من طبقة أدناه ، فهناك فرصة أفضل بكثير لاسترداد ناجح ، هذه هي الفكرة وراء الاستثناءات المحددة كما أراها

لوضعها بعبارة مختلفة ، الاستثناءات القادمة من أكثر من مستوى واحد أدناه هي جملة ، فقد فات الأوان لفعل أي شيء بخلاف إعادة إنشاء جميع البنية التحتية من الألف إلى الياء (إذا كنت محظوظًا بما فيه الكفاية ، فلا توجد بقايا طعام عالمية يمكنك القيام بها ' ر تصل)

الاقتراح كما هو مذكور في الغالب عديم الفائدة ، لأنه لا توجد طريقة موثوقة للرد على معرفة أن شيئًا سيئًا حدث بعيدًا عن متناولك

هذا عظيم. FWIW: أعتقد أنه إذا تمت إضافته ، فيجب أن يكون مطلوبًا بشكل افتراضي للتعامل مع طرق الرمي أو تحديد طريقتك على أنها رمي أيضًا. وإلا فهو مجرد توثيق إلى حد كبير.

تضمين التغريدة
أعتقد أنه مثل معظم الميزات في الكتابة المطبوعة ، يجب أن تكون قادرًا على الاشتراك في هذه الميزة أيضًا.
تمامًا كما لو أنه ليس من الضروري إضافة أنواع ، لا يجب أن يكون الأمر ضروريًا للرمي / الالتقاط.

ربما يجب أن يكون هناك علامة لجعلها ضرورية ، مثل --onlyCheckedExceptions .

في أي حال ، سيتم استخدام هذه الميزة أيضًا لاستنتاج / التحقق من أنواع الاستثناءات التي تم طرحها ، لذلك ليس فقط للتوثيق.

تضمين التغريدة

هذا مثال:
لدي عميل ويب حيث يمكن للمستخدم كتابة / لصق بيانات json ، ثم النقر فوق زر.
يأخذ التطبيق هذا الإدخال ويمرره إلى مكتبة تابعة لجهة خارجية تقوم بطريقة ما بتحليل json هذا وإرجاع json مع الأنواع المختلفة من القيم (سلسلة ، رقم ، منطقي ، مصفوفة ، إلخ).
إذا قامت مكتبة الطرف الثالث هذه بإلقاء خطأ في بناء الجملة يمكنني استرداده: أخبر المستخدم أن مدخلاته غير صالحة ويجب عليه المحاولة مرة أخرى.

هذا بالتأكيد أحد المجالات التي تصبح فيها فكرة الاستثناءات المحددة بأكملها غامضة. إنه أيضًا المكان الذي يصبح فيه تعريف "الوضع الاستثنائي" غير واضح.
قد يكون البرنامج الموجود في المثال الخاص بك وسيطة لـ JSON.parse يتم الإعلان عنه على أنه طرح استثناء محدد.
ولكن ماذا لو كان البرنامج عبارة عن عميل HTTP ويستدعي JSON.parse بناءً على قيمة الرأس المرفق باستجابة HTTP التي تصادف أنها تحتوي على جسم غير صحيح؟ لا يوجد شيء مفيد يمكن للبرنامج القيام به للتعافي ، كل ما يمكنه فعله هو إعادة النظرة.
أود أن أقول أن هذه حجة ضد إعلان أن JSON.parse محدد.

كل هذا يتوقف على حالة الاستخدام.

أفهم أنك تقترح أن يكون هذا تحت علم ولكن دعنا نتخيل أنني أريد استخدام هذه الميزة لذلك قمت بتمكين العلم. اعتمادًا على نوع البرنامج الذي أكتبه ، قد يساعدني أو يعيقني.

حتى java.io.FileNotFoundException الكلاسيكي هو مثال على ذلك. يتم فحصه ولكن هل يمكن للبرنامج التعافي؟ يعتمد الأمر حقًا على ما يعنيه الملف المفقود للمتصل ، وليس المستدعى.

تضمين التغريدة

لا يقترح هذا الاقتراح إضافة أي وظيفة جديدة ، فقط لإضافة طريقة للتعبير بخط مكتوب عن شيء موجود بالفعل في جافا سكريبت.
تم طرح الأخطاء ، ولكن لا توجد طريقة مطبعية حاليًا للتصريح بها (عند الرمي أو الإمساك).

بالنسبة لمثالك ، من خلال اكتشاف الخطأ ، يمكن أن يفشل البرنامج "بأمان" (على سبيل المثال إظهار رسالة "حدث خطأ ما" للمستخدم) من خلال اكتشاف هذا الخطأ ، أو يمكنه تجاهله ، اعتمادًا على البرنامج / المطور.
إذا كان من الممكن أن تتأثر حالة البرامج بهذا الخطأ ، فإن التعامل معها يمكن أن يحافظ على حالة صالحة بدلاً من حالة معطلة.

في أي حال ، يجب على المطور إجراء مكالمة بشأن ما إذا كان يمكنه التعافي من خطأ تم إلقاؤه أم لا.
الأمر متروك له أيضًا ليقرر ما يعنيه الاسترداد ، على سبيل المثال إذا كنت أكتب عميل http هذا لاستخدامه كمكتبة تابعة لجهة خارجية ، فقد أرغب في أن تكون جميع الأخطاء التي تم إلقاؤها من مكتبتي من نفس النوع:

enum ErrorCode {
    IllFormedJsonResponse,
    ...
}
...
{
    code: ErrorCode;
    message: string;
}

الآن ، في مكتبتي عندما أقوم بتحليل الاستجابة باستخدام JSON.parse ، أريد اكتشاف خطأ تم إلقاؤه ثم طرح الخطأ الخاص بي:

{
    code: ErrorCode.IllFormedJsonResponse,
    message: "Failed parsing response"
} 

إذا تم تنفيذ هذه الميزة ، فسيكون من السهل علي الإعلان عن هذا السلوك وسيكون واضحًا لمستخدمي مكتبتي كيف يعمل ويفشل.

لا يقترح هذا الاقتراح إضافة أي وظيفة جديدة ، فقط لإضافة طريقة للتعبير بخط مكتوب عن شيء موجود بالفعل في جافا سكريبت.

أنا أعرف. أنا أتحدث عن الأخطاء التي قد تنبعث من TypeScript بموجب هذا الاقتراح.
كان افتراضي أن هذا الاقتراح ينطوي على تمييز بين محددات الاستثناءات المحددة وغير المحددة (المستنبطة أو الصريحة) ، مرة أخرى فقط لأغراض التحقق من النوع.

تضمين التغريدة

ما قلته في التعليق السابق:

ولكن ماذا لو كان البرنامج عبارة عن عميل HTTP ويقوم باستدعاء JSON.parse بناءً على قيمة الرأس المرتبط باستجابة HTTP التي تصادف أنها تحتوي على جسم غير صحيح؟ لا يوجد شيء مفيد يمكن للبرنامج القيام به للتعافي ، كل ما يمكنه فعله هو إعادة النظرة.

ينطبق الأمر نفسه على إرجاع null عندما يتم التصريح عن وظيفتي لإرجاع نتيجة.
إذا اختار المطور استخدام strictNullChecks ، فيمكنك قول نفس الشيء بالضبط إذا كانت الوظيفة ترجع null (بدلاً من الرمي) ، ففي نفس السيناريو "لا يوجد شيء مفيد يمكن للبرنامج القيام به للتعافي ".

ولكن حتى بدون استخدام علامة onlyCheckedExceptions ، تظل هذه الميزة مفيدة لأن المترجم سيشتكي على سبيل المثال إذا حاولت اكتشاف الخطأ كـ string عندما يتم الإعلان عن الوظيفة لإلقاء Error فقط

فكرة جيدة ، ستكون مفيدة ولكنها ليست صارمة / آمنة لأنه لا توجد طريقة لمعرفة ما قد تلقيه عليك المكالمات المتداخلة.

بمعنى ، إذا كانت لدي وظيفة قد تطرح استثناءً من النوع أ ، لكني في الداخل أستدعي وظيفة متداخلة ولا تضعها في محاولة الالتقاط - إنها ستطرح استثناء من النوع ب للمتصل الخاص بي.
لذلك ، إذا كان المتصل يتوقع استثناءات من النوع A فقط ، فليس هناك ما يضمن أنه لن يحصل على أنواع أخرى من الاستثناءات المتداخلة.

(الموضوع طويل جدًا - آسف إذا فاتني هذا التعليق)

تضمين التغريدة
ينص الاقتراح على أن المترجم سوف يستنتج أنواع الأخطاء غير المعالجة وسيضيفها إلى توقيع الوظيفة / الطريقة.
لذلك في الحالة التي وصفتها ، سترمي وظيفتك A | B في حالة عدم معالجة B .

حسنا أرى ذلك. سوف يتعمق في جميع الطرق التي اتصل بها ويجمع كل أنواع الاستثناءات الممكنة؟
أود أن أرى ذلك يحدث ، إذا كان ذلك ممكنًا. انظر ، يمكن للمطور دائمًا أن يكون لديه استثناء غير متوقع لن يتم الإعلان عنه ، وفي هذه الحالة يكون "الكائن لم يتم تعيينه على مثيل" أو "القسمة على 0" أو استثناءات مماثلة ممكنة دائمًا تقريبًا من أي وظيفة.
IMHO ، كان من الأفضل التعامل معها كما هو الحال في C # حيث ترث جميع الاستثناءات من فئة أساسية تحتوي على رسالة ولا تسمح على الإطلاق بإلقاء النص غير المغلف أو الكائنات الأخرى. إذا كان لديك فئة أساسية وميراث ، فيمكنك تسلسل محاصيلك ومعالجة الخطأ المتوقع في كتلة واحدة ، وأخرى غير متوقعة في كتلة أخرى.

تضمين التغريدة
في جافا سكريبت ، تستند جميع الأخطاء إلى فئة Error ، لكنك لست مقيدًا برمي الأخطاء ، يمكنك طرح أي شيء:

  • throw "something went wrong"
  • throw 0
  • throw { message: "something went wrong", code: 4 }

نعم ، أعرف كيف يعمل JavaScript ، نحن نناقش TypeScript الذي يفرض المزيد من القيود.
اقترحت ، أن يكون الحل الجيد IMHO هو جعل TypeScript يتبع معالجة الاستثناءات التي تتطلب أن تكون جميع الاستثناءات التي تم إلقاؤها من فئة أساسية محددة وعدم السماح بإلقاء القيم غير المغلفة مباشرة.

لذلك ، لن يسمح "بطرح 0" أو "طرح" بعض الأخطاء ".
تمامًا مثل JavaScript يسمح بالعديد من الأشياء التي لا يسمح بها TypeScript.

شكرا،

تضمين التغريدة

ينطبق الأمر نفسه على إرجاع قيمة فارغة عندما يتم الإعلان عن وظيفتي لإرجاع نتيجة.
إذا اختار المطور استخدام strictNullChecks ، فيمكنك أن تقول نفس الشيء بالضبط إذا كانت الوظيفة ترجع قيمة خالية (بدلاً من الرمي) ثم في نفس السيناريو "لا يوجد شيء مفيد يمكن للبرنامج القيام به للاسترداد".

ولكن حتى بدون استخدام علامة onlyCheckedExceptions ، تظل هذه الميزة مفيدة لأن المترجم سيشتكي على سبيل المثال إذا حاولت اكتشاف الخطأ كسلسلة عند الإعلان عن أن الوظيفة ترمي خطأ فقط.

أرى ما تقوله ، هذا منطقي.

shaipetel كما تمت مناقشته سابقًا في هذا الاقتراح وفي أي مكان آخر ، لا يعمل التصنيف الفرعي المدمج في وظائف مثل Error و Array . يؤدي إلى سلوك مختلف عبر أوقات التشغيل المستخدمة على نطاق واسع وأهداف التجميع.

يعد اصطياد القيم لأنواع مختلفة من عدم وجود أخطاء هو الطريقة الأكثر قابلية للتطبيق لتصور الوظيفة المقترحة. في الواقع لا أرى ذلك كمشكلة لأن آليات نشر الاستثناء التي تستفيد من هذه الميزة من المحتمل أن تؤدي إلى أخطاء أكثر تحديدًا وأكثر فائدة بكثير من قول تتبع المكدس أو خصائص أخرى محددة للخطأ.
تمديد Error غير قابل للتطبيق. لن يكون قابلاً للتطبيق حتى تصبح جميع الأهداف أدناه es2015 غير مستخدمة.

إذا أدى هذا الاقتراح بشكل غير مباشر إلى المزيد من التصنيف الفرعي للوظيفة Error ، فأنا أعتقد أنها فكرة سيئة. مثل هذا الاعتراض منفصل تمامًا عن أي اعتراضات فلسفية حول استخدام الاستثناءات للتحكم في التدفق أو تعريف الظروف الاستثنائية. لذلك إذا تم اعتماد هذا الاقتراح ، فإنني أتوقع وجود وثائق بصوت عالٍ للغاية ووجهة نظرك فيما يتعلق بالاستخدام الصحيح وضرورة تجنب التصنيف الفرعي Error .

أود أن يكون هناك نوع من المنطق المساعد للتعامل مع الاستثناءات المكتوبة ، فأنا أعيد بناء الكثير من كود الوعد لاستخدام غير متزامن / انتظار ، والذي يبدو حاليًا كما يلي:

doSomethingWhichReturnsPromise()
    .then(send(200))
    .catch(NotFoundError, (error) => { send(404); })
    .catch(SomeBadDataError, (error) => { send(400); })
    .catch(CantSeeThisError, (error) => { send(403); })
    .catch((error) => { send(500); })

ثم في العالم الجديد يبدو كما يلي:

{
    await doSomethingWhichReturnsPromise();
    send(200);
}
catch(error)
{
    if(error instanceof NotFoundError) { send(404); } 
    else if(error instanceof SomeBadDataError) { send(400); } 
    else if(error instanceof CantSeeThisError) { send(403); } 
    else { send(500); } 
}

هذا أمر جيد ولكنه يتطلب المزيد من التعليمات البرمجية وأقل قابلية للقراءة من بعض النواحي ، لذلك سيكون من الرائع وجود شكل من الدعم لـ:

{
    await doSomethingWhichReturnsPromise();
    send(200);
}
catch(NotFoundError, error) { send(404); }
catch(SomeBadDataError, error) { send(404); }
catch(CantSeeThisError, error) { send(404); }
catch(error) { send(404); }

وهو ما سينتج الجزء السابق ، ولكن كسكر نحوي ، يمكنك حتى القيام بذلك على أنه أدوية عامة ولكن أكثر شرا:

{
    await doSomethingWhichReturnsPromise();
    send(200);
}
catch<NotFoundError>(error) { send(404); }
catch<SomeBadDataError>(error) { send(404); }
catch<CantSeeThisError>(error) { send(404); }
catch(error) { send(404); }

تضمين التغريدة
بينما أرغب في أن يدعم النص المطبوع عليه ما تقترحه ، فإنه لا يتعلق بهذه المشكلة في رأيي.
ما تقترحه يمكن تنفيذه بدون تنفيذ موضوع هذه المشكلة ، فقط أن المترجم لن يكون قادرًا على الشكوى (على سبيل المثال) من أن NotFoundError لم يتم طرحه.

اعتقدت أن هذا كان اقتراحًا لعمليات الصيد المكتوبة ، وليست مشكلة من أي نوع ، لم أرغب في تكرار سلاسل الرسائل ، وسأقوم بنشرها في مشكلتها الخاصة بعد ذلك.

تضمين التغريدة
تعد عمليات الصيد المكتوبة أيضًا جزءًا من طلب الميزة ، ولكنها أيضًا القدرة على إعلام المحول البرمجي بنوع الأخطاء التي يمكن إلقاؤها من الوظائف (وبعد ذلك سيتمكن المترجم أيضًا من استنتاج أنواع الأخطاء التي يمكن إلقاؤها).

في رأيي ، يمكن تنفيذ عمليات الصيد المكتوبة بدون الأجزاء الأخرى. افتح مشكلة جديدة ، ربما يقرر فريق TS تمييزها على أنها مكررة ، لا أعرف.

تضمين التغريدة

وهو ما سينتج الجزء السابق ، ولكن كسكر نحوي ، يمكنك حتى القيام بذلك على أنه أدوية عامة ولكن أكثر شرا:

try
{
  await doSomethingWhichReturnsPromise();
  send(200);
}
catch<NotFoundError>(error) { send(404); }
catch<SomeBadDataError>(error) { send(404); }
catch<CantSeeThisError>(error) { send(404); }
catch(error) { send(404); }

لن يعمل لأنه يعني إنشاء رمز يعتمد فقط على الأنواع بينما

 doSomethingWhichReturnsPromise()
    .then(send(200))
    .catch(NotFoundError, (error) => { send(404); })
    .catch(SomeBadDataError, (error) => { send(400); })
    .catch(CantSeeThisError, (error) => { send(403); })
    .catch((error) => { send(500); })

تمرير وظيفة الخطأ في كل حالة ومن المحتمل أن يتم تنفيذه باستخدام instanceof . رمز مثل هذا سام لأنه يشجع على تمديد Error وهو أمر سيء للغاية.

nitzantomer أوافق على أنها قضية منفصلة. يحتوي OP على أمثلة لأنواع الصيد التي لا تمتد Error .

تضمين التغريدة
لما يطلبه grofit ، يمكنك بعد ذلك القيام بشيء مثل:

try {
    // do something
} catch (isNotFoundError(error)) {
    ...
}  catch (isSomeBadDataError(error)) {
    ...
} catch (error) {
    ...
}

حيث يكون isXXX(error) حراس نوع على شكل:
function isXXX(error): error is XXX { ... }

nitzantomer بالتأكيد ، لكن حراس النوع ليسوا المشكلة. المشكلة هي

class MyError extends Error {
  myErrorInfo: string;
}

وهو ما يمثل مشكلة ، ولكن تمت مناقشته هنا بالفعل. لا أرغب في تفصيل هذه النقطة ، لكن العديد من قواعد الكود الكبيرة والمعروفة تأثرت سلبًا بتبني هذه الممارسة السيئة. تمديد Error فكرة سيئة.

تضمين التغريدة
أنا على علم بذلك ، وهذا هو السبب في أنني قدمت طريقة لإنجاز ما طلبته grofit ولكن دون الحاجة إلى تمديد Error . سيظل المطور قادرًا على تمديد Error إذا أراد ذلك ، لكن المترجم سيكون قادرًا على إنشاء كود js الذي لا يحتاج إلى استخدام instanceof

nitzantomer أدرك أنك على علم ، لكنني لم أدرك ما كنت تقترحه على grofit والذي يبدو وكأنه فكرة جميلة.

أنا فقط أتغلب على حصان ميت لأنني لا أحب الاضطرار إلى التعامل مع واجهات برمجة التطبيقات التي تريد أن تجعلني أستخدم مثل هذه الأنماط. بغض النظر ، أنا آسف إذا جعلت هذه المناقشة خارج الموضوع.

هل كان هناك المزيد من التفكير في هذه المناقشة؟ أود أن أرى عبارة throws مكتوبة بخط مكتوب.

aluanhaddad استمر في القول إن تمديد Error ممارسة سيئة. هل يمكنك التوسع قليلاً في هذا الأمر؟

لقد تمت مناقشته بإسهاب. في الأساس لا يمكنك توسيع الأنواع المضمنة بشكل موثوق. يختلف السلوك بشكل كبير عبر مجموعة البيئات وإعدادات --target .

السبب الوحيد الذي سألته هو أن تعليقاتك كانت أول ما سمعته عن هذا. بعد قليل من البحث في googling ، لم أجد سوى مقالات تشرح كيف ولماذا يجب _should_ تمديد Error .

لا لن يعمل. يمكنك أن تقرأ عنها بالتفصيل في https://github.com/Microsoft/TypeScript/issues/12123 والمشكلات المرتبطة العديدة.

ستنجح ، ولكن فقط في "بيئات محددة" ، والتي أصبحت الأغلبية حيث يتم تكييف ES6.

nitzantomer : هل تعتقد أنه من الممكن تنفيذ فحص TSLint الذي يخبر المطور عندما يتم استدعاء دالة ذات throws بدون محيط try/catch ؟

bennyn أنا مستخدم TSLint ، لكني لم أفكر مطلقًا في وضع قواعد جديدة.
لا أعرف حقًا ما إذا كان ذلك ممكنًا (على الرغم من أنني أعتقد أنه كذلك) ، وإذا كان الأمر كذلك ، فما مدى سهولة ذلك.

إذا جربته ، يرجى التحديث هنا ، شكرًا.

shaipetel (وكذلكnitzantomer)

إعادة:

نعم ، أعرف كيف يعمل JavaScript ، نحن نناقش TypeScript الذي يفرض المزيد من القيود.
اقترحت ، أن يكون الحل الجيد IMHO هو جعل TypeScript يتبع معالجة الاستثناءات التي تتطلب أن تكون جميع الاستثناءات التي تم إلقاؤها من فئة أساسية محددة وعدم السماح بإلقاء القيم غير المغلفة مباشرة.
لذلك ، لن يسمح "بطرح 0" أو "طرح" بعض الأخطاء ".
تمامًا مثل JavaScript يسمح بالعديد من الأشياء التي لا يسمح بها TypeScript.

لست متأكدًا مما إذا كان هذا هو ما كنت تقترحهshaipetel ولكن فقط في حالة ... سأحذر من قيام Typescript بتقييد throw لإعادة Error s فقط. تعمل ميزات عرض Async القادمة من React تحت غطاء المحرك بمقدار throw ing Promise s! (يبدو غريبًا أنني أعلم ... لكني قرأت أنهم قيموا هذا مقابل async / await و yield / yield* المولدات لحالة استخدامها وأنا متأكد من أنني أعرف ما يفعلونه!)

throw ing Error s أنا متأكد من أن 99٪ من حالات الاستخدام هناك ، ولكن ليس 100٪. لا أعتقد أن Typescript يجب أن تقيد المستخدمين على throw للأشياء التي تمتد لفئة أساسية Error . إنه بالتأكيد أكثر تقدمًا ، لكن throw به حالات استخدام أخرى غير الأخطاء / الاستثناءات.

تضمين التغريدة
أوافق ، هناك العديد من الأمثلة على كود js الحالي الذي يطرح مجموعة متنوعة من الأنواع (لقد رأيت الكثير من throw "error message string" ).
ميزة React الجديدة هي مثال جيد آخر.

فقط سنتان ، أقوم بتحويل الكود إلى غير متزامن / انتظار ولذا فأنا أتعرض للرمي (كجانب ، أكره الاستثناءات). سيكون وجود شرط رميات كما تناقش هذه المسألة لطيفًا في رأيي. أعتقد أنه سيكون مفيدًا أيضًا حتى إذا تم السماح بـ "رميات أي". (أيضًا ، ربما يكون خيار "nothrows" والمترجم الذي يجعل الأشياء افتراضية إلى "nothrows".)

يبدو أنه امتداد طبيعي للسماح بكتابة ما ترميه الوظيفة. يمكن كتابة قيم الإرجاع اختياريًا في TS ، ويبدو لي أن رمي مجرد نوع آخر من الإرجاع (كما يتضح من جميع البدائل المقترحة لتجنب الرمي ، مثل https://stackoverflow.com/a/39209039/162530).

(شخصياً ، أود أيضًا أن يكون لديّ خيار المترجم (الاختياري) لفرض أن أي متصل بوظيفة مُعلن عنها على أنها رميات يجب إما أن يعلن عن رميات أو يمسكها).

حالة الاستخدام الحالية الخاصة بي: لا أريد تحويل قاعدة رمز [Angular] بالكامل لاستخدام استثناءات لمعالجة الأخطاء (أرى أنني أكرههم). أستخدم غير متزامن / انتظار في تفاصيل تنفيذ واجهات برمجة التطبيقات الخاصة بي ، لكنني أحول الرمي إلى الوعود / الملاحظات العادية عند عودة واجهة برمجة التطبيقات. سيكون من الجيد أن يقوم المترجم بالتحقق من أنني التقط الأشياء الصحيحة (أو أمسكها على الإطلاق ، من الناحية المثالية).

@ aleksey-bykov دعونا لا نغير طبيعة JavaScript ، دعنا نضيف الكتابة إليها. :)

إضافة الكتابة يغيرها بالفعل (يقطع الرمز الذي لا معنى له) ،
بنفس الطريقة يمكننا تشديد معالجة الاستثناءات

في الخميس ، 26 يوليو ، 2018 ، 8:22 مساءً ، كتب Joe Pea [email protected] :

@ aleksey-bykov https://github.com/aleksey-bykov دعونا لا نغير ملف
طبيعة جافا سكريبت ، دعنا نضيف الكتابة إليها. :)

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/Microsoft/TypeScript/issues/13219#issuecomment-408274156 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AA5PzfUNS5E093Z74WA4WCUaTyWRRZC3ks5uKl1FgaJpZM4LXwLC
.

للوعود ، إذا كنت تستخدم then بدلاً من غير المتزامن / الانتظار وتجنب استخدام throw ، فكل هذا يعمل بشكل جميل في الواقع. هنا دليل على رسم المفهوم:

https://bit.ly/2NQZD8i - رابط الملعب

interface SafePromise<T, E> {
    then<U, E2>(
        f: (t: T) => SafePromise<U, E2>):
        SafePromise<U, E | E2>;

    catch<U, E2>(
        f: (e: E) => SafePromise<U, E2>):
        SafePromise<U, E2>

    catch<U, E1, E2>(
        guard: (e: any) => e is E1,
        f: (e: E) => SafePromise<U, E2>):
        SafePromise<U, Exclude<E, E1> | E2>
}

declare function resolve<T>(t:T): SafePromise<T, never>
declare function reject<E extends Error>(e:E): SafePromise<never, E>


class E404 extends Error {
    code:404 = 404;
}

class E403 extends Error {
    code:403 = 403;
    static check(e: any): e is E403 { return e && e.code === 403 }
}

let p = resolve(20)


let oneError = p.then(function f(x) {
    if (x > 5) return reject(new E403())
    return resolve(x)
})


let secondError = oneError.then(x => {
    if (x > 10) return reject(new E404())
    return resolve(x)
})

let remove403 = secondError.catch(E403.check, e => {
    return resolve(25)
})

let moreErrorsInfer = p.then(x => {
    if (x > 5) return reject(new E403())
    if (x > 10) return reject(new E404())
    return resolve(x)
})

let moreErrorsNoInfer = p.then((x):SafePromise<number, E403|E404> => {
    if (x > 5) return reject(new E403())
    if (x > 10) return reject(new E404())
    return resolve(x)
})

يزيل المصيد الأصلي الأنواع من الاتحاد ، بينما يُضاف إليها reject() . الشيء الوحيد الذي لا يعمل تمامًا هو استنتاج أنواع معلمات الاتحاد (ربما يكون خطأ)

المشكلات التي تم تناولها في المثال أعلاه:

  • لا يقتصر الأمر على الأخطاء ، يمكنك الرفض بأي شيء (على الرغم من أنه ربما يكون فكرة سيئة)
  • لا يقتصر على استخدام مثيل من ، يمكنك استخدام أي حارس أصلي لإزالة الأخطاء من الاتحاد

المشكلة الرئيسية التي أراها باستخدام هذا لجميع الأكواد هي تواقيع رد الاتصال ، ولجعلها تعمل بطريقة متوافقة ، سيكون "نوع الإرجاع" الافتراضي هو "might-throw-any" ، وإذا كنت تريد تقييد ذلك يمكنك أن تقول throws X أو throws never إذا لم يكن على سبيل المثال

declare function pureMap<T>(t:T[], f: (x:T) => U throws never):U[] throws never

النسخة بدون التوقيع:

declare function dirtyMap<T>(t:T[], f: (x:T) => U):U[]

في الواقع الافتراضي

declare function dirtyMap<T>(t:T[], f: (x:T) => U throws any):U[] throws any

لضمان تجميع كافة التعليمات البرمجية الحالية.

على عكس strictNullChecks ، حيث تكون قيم الإرجاع الفارغة غير شائعة تمامًا ، أعتقد أن الاستثناءات في JS منتشرة تمامًا. قد لا يكون تصميمها في ملفات .d.ts سيئًا للغاية (أنواع الاستيراد من التبعيات لوصف أخطائك) ، لكنها بالتأكيد ستكون جهدًا غير تافه وستؤدي إلى اتحادات ضخمة.

أعتقد أن الحل الوسط الجيد هو التركيز على الوعود و async/await ، حيث أن الوعد عبارة عن غلاف بالفعل ، والرمز غير المتزامن هو المكان الذي يتعامل فيه الخطأ مع الفروع في السيناريوهات النموذجية. قد تكون الأخطاء الأخرى "غير محددة"

@ spion-h4 هل هذا (بالفعل / سوف) يمكن استخدامه في الكتابة المطبوعة؟

declare function dirtyMap<T>(t:T[], f: (x:T) => U throws any):U[] throws any

تضمين التغريدة
لا ، لا تدعم الكتابة المطبوعة حاليًا الكلمة الرئيسية throws ، هذا ما تدور حوله هذه المشكلة.

هناك شيء يجب ملاحظته (وليس أنه يحظر بالضرورة هذا الاقتراح) هو أنه بدون الفئات الاسمية التي تكتب الأخطاء المضمنة لا تزال غير مفيدة بشكل خاص لأنها كلها متشابهة من الناحية الهيكلية.

على سبيل المثال:

class AnotherError extends Error {}

function* range(min: number, max: number): Iterable<number> throws TypeError, RangeError {
  if (typeof min !== 'number') {
    throw new TypeError('min must be a number')
  }
  if (typeof min !== 'number') {
    throw new TypeError('max must be a number')
  }
  if (!Number.isSafeInteger(min)) {
    // Allowed because without nominal types we can't distinguish
    // Error/RangeError/TypeError/etc
    throw new Error('min must be a safe integer')
  }
  if (!Number.isSafeInteger(max)) {
    // Also allowed because AnotherError is also structurally
    // compatible with TypeError/RangeError
    throw new AnotherError('max must be a safe integer')
  }
  for (let i = min; i < max; i++) {
    yield i
  }
}

تضمين التغريدة
هذه المسألة لا علاقة لها بالاقتراح.
يمكنك الدخول في نفس المشكلة بالضبط عند القيام بذلك:

class BaseClass {
    propA!: string;
}

class MyClass1 extends BaseClass { }

class MyClass2 extends BaseClass { }

function fn(): MyClass1 {
    return new MyClass2();
}

إنها قيود اللغة التي تؤثر على الكثير من حالات الاستخدام.

هناك قراءة رائعة بواسطة ahejlsberg حول الاستثناءات المكتوبة: https://www.artima.com/intv/handcuffs.html

أعتقد أن TypeScript في وضع جيد لتجنب هذه المشكلات. TypeScript هو كل شيء عن حلول _pragmatic_ لمشاكل _ real-world_. من واقع خبرتي ، في قواعد أكواد JavaScript و TypeScript الكبيرة ، فإن معالجة الأخطاء هي واحدة من أكبر المشاكل - رؤية أي أخطاء تعمل _ قد_ ترمي وأنا _ قد _ أرغب في التعامل معها أمر صعب للغاية. إنه ممكن فقط من خلال قراءة وكتابة وثائق جيدة (نعلم جميعًا مدى جودتنا في هذا / ق) أو النظر إلى التنفيذ ، وبما أنه على عكس قيم الإرجاع ، تنتشر الاستثناءات فقط من خلال استدعاءات الوظائف تلقائيًا ، فلا يكفي مجرد تحقق من الوظيفة التي يتم استدعاؤها مباشرة ، ولكن للقبض عليها جميعًا ، يجب عليك التحقق من استدعاءات الوظائف المتداخلة أيضًا. _ هذه مشكلة حقيقية _.

الأخطاء في JavaScript هي في الواقع قيم مفيدة للغاية. تقوم العديد من واجهات برمجة التطبيقات في NodeJS برمي كائنات خطأ مفصلة ، والتي تحتوي على رموز خطأ محددة جيدًا وتكشف عن بيانات وصفية مفيدة. على سبيل المثال ، الأخطاء من child_process.execFile() لها خصائص مثل exitCode و stderr ، أخطاء من fs.readFile() بها رموز خطأ مثل ENOENT (الملف غير موجود ) أو EPERM (أذونات غير كافية). أعرف العديد من المكتبات التي تقوم بذلك أيضًا ، على سبيل المثال ، يمنحك برنامج تشغيل قاعدة البيانات pg بيانات وصفية كافية عن الخطأ لمعرفة أي قيد العمود بالضبط تسبب في فشل INSERT .

يمكنك أن ترى قدرًا مثيرًا للقلق من عمليات التحقق من regex الهشة على رسائل الخطأ في قواعد البيانات لأن الأشخاص لا يدركون أن الأخطاء بها رموز خطأ مناسبة وما هي عليه.

إذا تمكنا من تحديد الإعلانات في @types و lib.d.ts ما هي الأخطاء التي يمكن أن تحدثها هذه الوظائف ، فسيكون لدى TypeScript القدرة على مساعدتنا في بنية الخطأ - ما هي الأخطاء المحتملة ، وما هي قيم رمز الخطأ ، ما هي الخصائص التي لديهم. هذا _لا _ يتعلق بالاستثناءات المكتوبة وبالتالي يتجنب تمامًا جميع المشكلات مع الاستثناءات المكتوبة. إنه حل براغماتي لمشكلة العالم الواقعي (على عكس إخبار الناس باستخدام قيم إرجاع الخطأ الأحادي بدلاً من ذلك - يبدو واقع أرض جافا سكريبت مختلفًا ، والوظائف تطرح الأخطاء).

يمكن أن يكون التعليق التوضيحي اختياريًا تمامًا (أو يكون مطلوبًا باستخدام علامة المترجم). إذا لم يتم تحديدها في ملف التصريح ، فستقوم الدالة ببساطة بإلقاء any (أو unknown ).
يمكن أن تعلن الدالة يدويًا عدم طرحها أبدًا بـ throws never .
إذا كان التنفيذ متاحًا ، فستقوم الوظيفة برمي اتحاد لجميع أنواع الاستثناءات للوظائف التي تستدعيها وبياناتها throw التي ليست داخل كتلة try مع catch شرط.
إذا رمى أحدهم any ، فإن الوظيفة تطرح any أيضًا - لا بأس بذلك ، عند حدود كل دالة لدى dev الفرصة لتصحيحها من خلال تعليق توضيحي صريح.
وفي العديد من الحالات ، حيث يتم استدعاء وظيفة واحدة معروفة وملفوفة في محاولة / التقاط (على سبيل المثال ، قراءة ملف والتعامل معه غير موجود) ، يمكن لـ TypeScript عندئذٍ استنتاج النوع في جملة catch.

لا نحتاج إلى فئة فرعية للخطأ لهذا ولا instanceof - يمكن أن تكون أنواع الخطأ واجهات تحدد أكواد الخطأ مع سلسلة حرفية ، ويمكن لـ TypeScript تمييز الاتحاد على رمز الخطأ ، أو استخدام حراس النوع.

تحديد أنواع الخطأ

interface ExecError extends Error {
  status: number
  stderr: Buffer
}
function execFileSync(cmd: string): Buffer throws ExecError;
interface NoEntityError extends Error { code: 'ENOENT' }
interface PermissionError extends Error { code: 'EPERM' }
function readFileSync(file: string): Buffer throws NoEntityError | PermissionError;



md5-818797fe8809b5d8696f479ce1db4511



Preventing a runtime error due to type mismatch



md5-c2d214f4f8ecd267a9c9252f452d6588



Catching errors with type switch



md5-75d750bbe0c3494376581eaa3fa62ce5



```ts
try {
  const resp = await fetch(url, { signal })
  if (!resp.ok) {
    // inferred error type
    // look ma, no Error subclassing!
    throw Object.assign(new Error(resp.statusText), { name: 'ResponseError', response })
  }
  const data = await resp.json()
} catch (err) { // AbortError | Error & { name: 'ResponseError', response: Response } | SyntaxError
  switch (err.name)
    case 'AbortError': return; // Don't show AbortErrors
    default: displayError(err); return;
  }
}



md5-a859955ab2c42d8ce6aeedfbb6443e93



```ts
interface HttpError extends Error { status: number }
// Type-safe alternative to express-style middleware request patching - just call it (TM)
// The default express error handler recognises the status property
function checkAuth(req: Request): User throws HttpError {
    const header = req.headers.get('Authorization')
    if (!header) {
        throw Object.assign(new Error('No Authorization header'), { status: 401 })
    }
    try {
        return parseHeader(header)
    } catch (err) {
        throw Object.assign(new Error('Invalid Authorization header'), { status: 401 })
    }
}

بصرف النظر عن الأخطاء ، من الممكن أيضًا تحديد أنواع أخرى من الآثار الجانبية مثل

  • التباعد (حلقة لا نهائية / لا يعود)
  • IO
  • عدم الحتمية ( Math.random )

يذكرني Koka من MSR والذي يمكنه وضع علامة على التأثيرات على الأنواع المرتجعة.

الإقتراح أو العرض:

function square(x: number): number &! never { // This function is pure
  return x * x
}

function square2(x : number) : number &! IO {
  console.log("a not so secret side-effect")
  return x * x
}

function square3( x : number ) : number &! Divergence {
  square3(x)
  return x * x
}

function square4( x : number ) : number &! Throws<string> { // Or maybe a simple `number &! Throws`?
  throw "oops"
  return x * x
}

function map<T, R, E>(a: T[], f :(item: T) => R &! E): R[] &! E { ... }
function map<T, R>(a: T[], f :(item: T) => R): R[] { ... } // will also work; TS would collect side effects

function str<T>(x: T): string &! (T.toString.!) // care about the side effect of a type

أحب فكرة إعلان نوع الخطأ! سيكون هذا بمثابة مساعدة كبيرة للأشخاص الذين يحتاجون إليها ولن يفعل أي شيء سيئًا للأشخاص الذين لا يحبونه. لدي الآن مشكلة في مشروع العقدة الخاص بي ، والتي يمكن حلها بشكل أسرع باستخدام هذه الميزة. لا بد لي من اكتشاف الأخطاء وإرسال كود http الصحيح مرة أخرى - للقيام بذلك ، يجب أن أتحقق دائمًا من جميع استدعاءات الوظائف لاكتشاف الاستثناءات التي يتعين علي التعامل معها - وهذا ليس ممتعًا -_-

ملاحظة. تبدو البنية في https://github.com/Microsoft/TypeScript/issues/13219#issuecomment -428696412 جيدة جدًا - &! وأنواع أخطاء الالتفاف في <> مذهلة فقط.

أنا أيضا أؤيد هذا. الآن جميع الأخطاء غير مطبعية وهذا أمر مزعج للغاية. على الرغم من أنني آمل أن يتم اشتقاق معظم throws من الكود الذي يجب علينا كتابته في كل مكان.

وبهذه الطريقة يمكن للأشخاص كتابة قواعد tslint التي تجبر المطور على اكتشاف أخطاء المستخدم غير الودية في نقاط نهاية الراحة وما إلى ذلك.

لقد فوجئت بالفعل بأن هذه الوظيفة لم تكن _بالفعل_ داخل TypeScript. من أول الأشياء التي ذهبت لإعلانها. سيكون من الجيد أن يقوم المترجم بتطبيقها أو لا يطبقها ، طالما يمكنك الحصول على المعلومات التي تفيد بأنه سيكون هناك خطأ ، ونوع الأخطاء التي سيتم إلقاؤها.

حتى لو لم نحصل على &! وحصلنا على Throws<T> الذي سيكون: +1:

هذا هو بالضبط ما يدور في ذهني ، سيكون من الأجمل بكثير أن تدعم TypeScript هذه الجمل.

هناك ميزة أخرى محتملة تتمثل في فرض (ربما على مستوى الوظيفة ، أو على مستوى الوحدة النمطية )rictExceptionTracking - مما يعني أنه سيتعين عليك استدعاء أي شيء تم الإعلان عنه على أنه throws X مع تعبير try! invokeSomething() كما هو الحال في الصدأ والسريع.

سيتم تجميعها ببساطة إلى invokeSomething() لكن إمكانية الاستثناءات ستكون مرئية في الكود ، مما يسهل اكتشاف ما إذا كنت تترك شيئًا قابلًا للتغيير في حالة عابرة سيئة (أو ترك الموارد المخصصة غير مهملة)

@ be5invis

function square(x: number): number &! never { // This function is pure
  return x * x
}
...

أنا فقط أعطي اثنين سنتي هنا ولكن &! يبدو قبيحًا للغاية بالنسبة لي.
أعتقد أن الأشخاص الجدد على الكتابة المطبوعة سيكونون نوعًا ما يتخلصون من هذا الرمز ، إنه حقًا غير جذاب.
throws البسيط هو أكثر وضوحًا وبديهية وبساطة ، في رأيي.

بخلاف ذلك ، +1 للاستثناءات المكتوبة. 👍

أرغب في إضافة 1+ الخاص بي للاستثناءات المكتوبة والمدققة.
لقد استفدت بشكل كبير من الاستثناءات المكتوبة في الكود الخاص بي. أنا على دراية كاملة بمخاطر instanceof ، لقد استخدمتها بالفعل لمصلحتي من خلال القدرة على كتابة معالجات عامة للأخطاء ذات الصلة التي ترث من فئة أساسية مشتركة. معظم الطرق الأخرى التي واجهتها لتجنب الوراثة من فئة الخطأ الأساسية ينتهي بها الأمر (على الأقل) إلى كونها معقدة وإشكالية بطرق مختلفة.
الاستثناءات التي تم التحقق منها هي تحسين يفسح المجال ، في رأيي ، لمزيد من رؤية التحليل الثابت في قاعدة التعليمات البرمجية. في قاعدة بيانات معقدة بدرجة كافية ، قد يكون من السهل تفويت الاستثناءات الناتجة عن وظيفة معينة.

بالنسبة لأولئك الذين يبحثون عن أمان أخطاء وقت الترجمة في الكتابة المطبوعة ، يمكنك استخدام مكتبة نتائج ts .

vultix بالنسبة لي لسوء الحظ ، فإن الفصل ليس واضحًا بما يكفي لوضع هذا في إعداد الفريق.

vultix تمت مناقشة نهج lib الخاص بك أعلاه ، وهو عكس تمامًا ما توجد هنا هذه الميزة لحلها.
جافا سكريبت لديها بالفعل آلية للتعامل مع الأخطاء ، تسمى استثناءات ، لكن الكتابة المطبوعة تفتقر إلى طريقة لوصفها.

nitzantomer أوافق تمامًا على أن هذه الميزة ضرورية للطباعة. مكتبتي ليست أكثر من احتياطي مؤقت حتى تتم إضافة هذه الميزة.

function* range(min: number, max: number): Iterable<number> throws TypeError, RangeError {

أعلم أنني تأخرت قليلاً في هذه اللعبة ، لكن ما يلي يبدو أكثر بقليل من بناء الجملة من النوع الطبيعي بدلاً من الفاصلة.

Iterable<number> throws TypeError | RangeError

لكني ما زلت جيدًا مع الفاصلة .. أتمنى لو كان لدينا هذا في اللغة.
السبب الرئيسي هو أنني أرغب في الحصول على JSON.parse() لأن الكثير من زملائي في العمل يبدو أنهم نسوا أن JSON.parse يمكن أن يتسبب في حدوث خطأ وسيوفر الكثير من عمليات النقل ذهابًا وإيابًا طلبات السحب.

تضمين التغريدة
أنا أتفق تماما.
تضمن اقتراحي بناء الجملة الذي توصي به:

function mightThrow(): void throws string | MyErrorType { ... }

هل يمكننا اتباع نمط إعلان الخطأ للغة OOP مثل Java؟ تُستخدم الكلمة الرئيسية "throws" للإعلان عن حاجتنا إلى "try..catch" عند استخدام دالة بها خطأ محتمل

تضمين التغريدة
الاقتراح للاستخدام الاختياري لشرط الرميات.
هذا يعني أنك لن تضطر إلى استخدام المحاولة / الالتقاط إذا كنت تستخدم وظيفة ترمي.

allicanseenow ، قد ترغب في قراءة كتابتي أعلاه للاطلاع على السياق ، بما في ذلك المقالة المرتبطة: https://github.com/microsoft/TypeScript/issues/13219#issuecomment -416001890

أعتقد أن هذا هو أحد الأشياء المفقودة في نظام الكتابة المطبوعة.
إنه اختياري بحت ، ولا يغير التعليمات البرمجية المنبعثة ، ويساعد في العمل مع المكتبات والوظائف الأصلية.

كما أن معرفة الأخطاء التي قد يتم طرحها قد يتيح للمحررين "الإكمال التلقائي" لشرط catch مع شرط معالجة جميع الأخطاء.

أعني ، حتى التطبيق الصغير حقًا يستحق معالجة مناسبة للخطأ - أسوأ شيء الآن هو أن المستخدمين لا يعرفون متى قد يحدث خطأ ما.

بالنسبة لي ، هذه أهم ميزة مفقودة الآن.

RyanCavanaugh ، كانت هناك مجموعة من التعليقات منذ إضافة التصنيف "في انتظار المزيد من التعليقات". يمكن لأي من أعضاء الفريق المطبوع عليها وزنها؟

nitzantomer أنا أؤيد فكرة إضافة رميات إلى TypeScript ، لكن ألا تسمح المحاولة / الالتقاط الاختيارية بإعلانات رميات غير دقيقة عند استخدامها في وظائف متداخلة؟

لكي يثق المطور في أن جملة رميات الطريقة دقيقة ، يجب عليهم وضع افتراض خطير بأنه في أي وقت من الأوقات في التسلسل الهرمي لاستدعاء تلك الطريقة كان استثناءً اختياريًا يتم تجاهله وإلقاء المكدس دون الإبلاغ عنه. أعتقد أن ajxs ربما ألمح إليها في نهاية تعليقه ، لكنني أعتقد أن هذه ستكون مشكلة كبيرة. خاصة مع كيفية تجزئة معظم مكتبات npm.

تضمين التغريدة
أتمنى أن أفهمك بشكل صحيح:

سوف يستنتج المترجم الأنواع التي تم إلقاؤها ، على سبيل المثال:

function fn() {
    if (something) {
        throw "something happened";
    }
}

سيكون في الواقع function fn(): throws string { ... } .
سيخطئ المترجم أيضًا عندما يكون هناك عدم تطابق بين الأخطاء المُعلنة والأخطاء الفعلية:

function fn1() throws string | MyError {
    ...
}

function fn2() throws string {
    fn1(); // throws but not catched

    if (something) {
        throws "damn!";
    }
}

يجب أن يشكو المترجم من أن fn2 يطرح string | MyError وليس string .

مع وضع كل هذا في الاعتبار ، لا أرى كيف يكون هذا الافتراض أكثر خطورة من الافتراض بأن الأنواع المعلنة الأخرى التي يثق بها المطور عند استخدام مكتبات أو أطر عمل أخرى وما إلى ذلك.
لست متأكدًا من أنني غطيت جميع الخيارات حقًا ، وسأكون سعيدًا إذا كان بإمكانك التوصل إلى سيناريو مثير للاهتمام.

وحتى مع هذه المشكلة ، فهي إلى حد كبير كما هي الآن على أي حال:

// can throw SyntaxError
declare function a_fancy_3rd_party_lib_function(): MyType;

function fn1() {
    if (something) {
        throws "damn!";
    }

    if (something else) {
        throws new Error("oops");
    }

    a_fancy_3rd_party_lib_function();
}

function fn2() {
    try {
        fn1();
    } catch(e) {
        if (typeof e === "string") { ... }
        else if (e instanceof MyError) { ... }
    }
}

يمكننا أن نحرسنا على الكتابة المطبوعة حتى من هذا ، ولكنها تتطلب بنية مختلفة قليلاً عن جافا سكريبت:

function fn2() {
    try {
        fn1();
    } catch(typeof e === "string") {
        ...
    } catch(e instanceof MyError) {
        ...
    }
}

سيتم تجميع هذا إلى:

function fn2() {
    try {
        fn1();
    } catch(e) {
        if (typeof e === "string") {}
        else if (e instanceof MyError) {} 
        else {
            throw e;
        }
    }
}

مهلا! شكرا للرد! في الواقع ، فإن الاستدلال على الرميات كما ذكرت يخفف من هذه المشكلة التي كنت أفكر فيها. لكن المثال الثاني الذي ذكرته مثير للاهتمام.

معطى

function first() : string throws MyVeryImportantError { // Compiler complains: missing number and string
    if(something) {
        throw MyVeryImportantError
    } else {
        return second().toString();
    }
}

function second() : number {
    if(something)
        throw 5;
    else   
        return third();
}

function third() : number throws string {
    if(!something)
        throw 'oh no!';
    return 9;
}

بالنسبة لي للإعلان صراحة عن MyVeryImportantError ، يجب أيضًا أن أعلن صراحة عن جميع الأخطاء الإضافية في callstack ، والتي قد تكون حفنة اعتمادًا على عمق التطبيق. أيضا ، يحتمل أن تكون مملة. بالتأكيد لا أرغب في الغوص في سلسلة المكالمات بأكملها لإنشاء قائمة بالأخطاء المحتملة التي يمكن أن تظهر في الطريق ، ولكن ربما يمكن لـ IDE المساعدة.

كنت أفكر في اقتراح نوع من عامل الانتشار للسماح للمطور بالإعلان صراحة عن خطأه ورمي الباقي.

function first() : string throws MyVeryImportantError | ... { // Spread the rest of the errors

ولكن ستكون هناك طريقة أسهل لتحقيق نفس النتيجة: ما عليك سوى إسقاط إعلان الرميات.

function first() : string { // Everything automatically inferred

الأمر الذي يثير السؤال: متى أرى فائدة من استخدام الكلمة الرئيسية throws بدلاً من ترك الكتابة المطبوعة تستنتج الأخطاء؟

تضمين التغريدة

إنها لا تختلف عن اللغات الأخرى التي تستخدم جملة الرمي ، مثل جافا.
لكنني أعتقد أنه في معظم الحالات لن تضطر حقًا إلى التعامل مع الكثير من الأنواع. في العالم الحقيقي ، ستتعامل عادةً مع string و Error (والفئات الفرعية) وكائنات مختلفة ( {} ).
وفي معظم الحالات ، ستتمكن فقط من استخدام فئة الوالدين التي تلتقط أكثر من نوع واحد:

type MyBaseError = {
    message: string;
};

type CodedError = MyBaseError & {
    code: number;
}

function fn1() throws MyBaseError {
    if (something) {
        throw { message: "something went wrong" };
    }
}

function fn2() throw MyBaseError {
    if (something) {
        throw { code: 2, message: "something went wrong" };
    }

    fn1();
}

بالنسبة إلى استخدام جملة الرمي ضمنيًا مقابل جعل المترجم يستنتجها ، أعتقد أن الأمر يشبه التصريح عن الأنواع في الكتابة المطبوعة ، في كثير من الحالات لا تحتاج إلى ذلك ، ولكن يمكنك القيام بذلك لتوثيق الكود الخاص بك بشكل أفضل بحيث من يقرأها لاحقًا يمكنه فهمها بشكل أفضل.

هناك طريقة أخرى إذا كنت تريد أمان الكتابة وهي التعامل مع الأخطاء على أنها قيم على غرار Golang:
https://gist.github.com/brandonkal/06c4a9c630369979c6038fa363ec6c83
ومع ذلك ، ستكون هذه ميزة رائعة.

تضمين التغريدة
تمت مناقشة هذا النهج بالفعل في وقت سابق في الموضوع.
من الممكن ، ولكن القيام بذلك يعني تجاهل جزء / ميزة متأصلة تسمح لنا جافا سكريبت بها.

أعتقد أيضًا أن المحدد noexcept (# 36075) هو الحل الأبسط والأفضل حاليًا ، كما هو الحال بالنسبة لمعظم المبرمجين ، فإن طرح الاستثناءات يعتبر مضادًا للنمط.

فيما يلي بعض الميزات الرائعة:

  1. لن تؤدي وظائف التحقق إلى حدوث خطأ:
noexcept function foo(): void {
  throw new Error('Some exception'); // <- compile error!
}
  1. تحذير بشأن الكتل الزائدة عن الحاجة try..catch :
try { // <- warning!
  foo();
} catch (e) {}
  1. حل دائما وعود:
interface ResolvedPromise<T> extends Promise<T> {
  // catch listener will never be called
  catch(onrejected?: noexcept (reason: never) => never): ResolvedPromise<T>; 

  // same apply for `.then` rejected listener,
  // resolve listener should be with `noexpect`
}

تضمين التغريدة

كما هو الحال بالنسبة لمعظم المبرمجين ، يعتبر استبعاد الاستثناءات نمطًا مضادًا.

أعتقد أنني لست جزءًا من مجموعة "معظم المبرمجين".
noexcept رائع ولكنه ليس موضوع هذه المشكلة.

إذا كانت الأدوات موجودة ، سأستخدمها. throw و try/catch و reject هناك ، لذا سأستخدمهم.

سيكون من الجيد فقط أن يتم كتابتها بشكل صحيح في الكتابة المطبوعة.

إذا كانت الأدوات موجودة ، سأستخدمها. throw ، try/catch و reject هناك ، لذا سأستخدمهم.

بالتأكيد. سوف استخدمهم ايضا وجهة نظري هي أن noexcept أو throws never ستكون بداية جيدة لهذه المشكلة.

مثال غبي: أريد أن أعرف أنه إذا اتصلت بـ Math.sqrt(-2) فلن يحدث أي خطأ أبدًا.

ينطبق الشيء نفسه على مكتبات الطرف الثالث. إنه يوفر مزيدًا من التحكم في الكود وحالات الحافة التي أحتاجها للتعامل معها كمبرمج.

تضمين التغريدة
لكن لماذا؟
يتضمن هذا الاقتراح جميع مزايا عرض "noexpect" ، فلماذا تقبل بأقل؟

لأن الأمر يستغرق وقتًا طويلاً (هذه المشكلة عمرها 3 سنوات) ، وكنت آمل أن نبدأ على الأقل بالميزة الأساسية أولاً.

تضمين التغريدة
حسنًا ، أفضل ميزة أفضل تستغرق وقتًا أطول من الحل الجزئي الذي سيستغرق وقتًا أقل ولكننا سنبقى عالقين معها إلى الأبد.

أشعر أنه يمكن إضافة noexcept بسهولة لاحقًا ، في الواقع يمكن أن يكون موضوعًا منفصلاً.

noexcept هو نفسه throws never .

TBH أعتقد أنه من المهم أن يكون لديك بعض الآليات التي تضمن استثناءات المعالجة (لم يتم استخدام error in Go) بدلاً من تقديم تلميحات الكتابة مقابل try/catch

roll لا ينبغي أن يكون هناك أي "ضمان".
ليس من الضروري التعامل مع الاستثناءات في جافا سكريبت ولا يجب أن يكون أمرًا ضروريًا في الكتابة المطبوعة أيضًا.

قد يكون خيارًا كجزء من الوضع الصارم لـ typecrypt أنه يجب على الوظيفة إما اكتشاف الخطأ أو الإعلان عنه صراحةً throws وتمريره

لإضافة 5 سنتات: أرى استثناءات مشابهة لوظيفة تعيد null : إنه شيء لا تتوقع حدوثه عادةً. ولكن إذا حدث خطأ في وقت التشغيل ، فهذا أمر سيء. الآن ، أضاف TS "أنواع non-nullable" لتذكيرك بالتعامل مع null . أرى أن إضافة throws تقدم هذه الجهود خطوة إلى الأمام ، بتذكيرك بمعالجة الاستثناءات أيضًا. لهذا السبب أعتقد أن هذه الميزة مطلوبة بالتأكيد.

إذا نظرت إلى Go ، الذي يعرض الأخطاء بدلاً من رميها ، يمكنك أن ترى بشكل أوضح أن كلا المفهومين ليسا مختلفين. كما أنه يساعدك على فهم بعض واجهات برمجة التطبيقات بشكل أعمق. ربما لا تعرف أن بعض الوظائف يمكن أن تطرح وتلاحظ ذلك في وقت لاحق في الإنتاج (على سبيل المثال ، JSON.parse ، من يعرف عدد الوظائف الأخرى؟).

@ obedm503 هذه في الواقع فلسفة TS: افتراضيًا لا يشتكي المترجم من أي شيء. يمكنك تمكين الخيارات للتعامل مع أشياء معينة على أنها خطأ أو تمكين الوضع المتشدد لتمكين جميع الخيارات مرة واحدة. لذلك ، يجب أن يكون هذا معطى.

أنا أحب هذه الميزة المقترحة.
يمكن أن تكون كتابة الاستثناءات والاستدلال من أفضل الأشياء في سجل البرمجة بأكمله.
❤️

مرحبًا ، لقد قضيت بعض الوقت للتو في محاولة إيجاد حل بديل لما لدينا حاليًا في TS. نظرًا لعدم وجود طريقة للحصول على نوع الأخطاء التي تم إلقاؤها داخل نطاق الوظيفة (ولا للحصول على نوع الطريقة الحالية بالمناسبة) فقد اكتشفت كيف يمكننا تحديد الأخطاء المتوقعة بشكل صريح داخل الطريقة نفسها. يمكننا لاحقًا استرداد هذه الأنواع ومعرفة على الأقل ما يمكن إلقاؤه داخل الطريقة.

هنا هو بلدي POC

/***********************************************
 ** The part to hide a type within another type
 **********************************************/
// A symbol to hide the type without colliding with another existing type
const extraType = Symbol("A property only there to store types");

type extraType<T> = {
    [extraType]?: T;
}

// Set an extra type to any other type
type extraTyped<T, E> = T & extraType<E>

// Get back this extra type
type getExtraType<T> = T extends extraType<infer T> ? T : never;

/***********************************************
 ** The part to implement a throwable logic
 **********************************************/

// Throwable is only a type holding the possible errors which can be thrown
type throwable<T, E extends Error> = extraTyped<T,E>

// return the error typed according to the throwableMethod passed into parameter
type basicFunction = (...any: any[]) => any;
const getTypedError = function<T extends basicFunction> (error, throwableMethod:T) {
    return error as getExtraType<ReturnType<T>>;
};

/***********************************************
 ** An example of usage
 **********************************************/

class CustomError extends Error {

}

// Here is my unreliable method which can crash throwing Error or CustomError.
// The returned type is simply our custom type with what we expect as the first argument and the
// possible thrown errors types as the second (in our case a type union of Error and CustomError)
function unreliableNumberGenerator(): throwable<number, Error | CustomError> {

    if (Math.random() > 0.5) {
        return 42;
    }

    if (Math.random() > 0.5) {
        new Error('No luck');
    }

    throw new CustomError('Really no luck')
}

// Usage
try {
    let myNumber = unreliableNumberGenerator();
    myNumber + 23;
}

// We cannot type error (see TS1196)
catch (error) {
    // Therefore we redeclare a typed value here and we must tell the method which could have crashed
    const typedError = getTypedError(error, unreliableNumberGenerator);

    // 2 possible usages:
    // Using if - else clauses
    if (typedError instanceof CustomError) {

    }

    if (typedError instanceof Error) {

    }

    // Or using a switch case on the constructor:
    // Note: it would have been really cool if TS did understood the typedError.constructor is narrowed by the types Error | CustomError
    switch (typedError.constructor) {
        case Error: ;
        case CustomError: ;
    }

}

// For now it is half a solution as the switch case is not narrowing anything. This would have been 
// possible if the typedError would have been a string union however it would not be reliable to rely
// on typedError.constructor.name (considering I would have find a way to convert the type union to a string union)

شكرا جزيلا لملاحظاتك الايجابية! أدرك أنه قد يكون من الأسهل إعادة بناء الكود الحالي دون الحاجة إلى التفاف النوع الذي تم إرجاعه بالكامل في النوع throwable . بدلاً من ذلك ، يمكننا فقط إلحاق هذا النوع بالنوع الذي تم إرجاعه ، تتيح لك الشفرة التالية إلحاق الأخطاء القابلة للرمي على النحو التالي:

// now only append '& throwable<ErrorsThrown>' to the returned type
function unreliableNumberGenerator(): number & throwable<Error | CustomError> { /* code */ }

هذا هو التغيير الوحيد لجزء المثال ، إليك إعلان الأنواع الجديدة:

/***********************************************
 ** The part to hide a type within another type
 **********************************************/
// A symbol to hide the type without colliding with another existing type
const extraType = Symbol("A property only there to store types");

type extraType<T> = {
    [extraType]?: T;
}

// Get back this extra type
type getExtraType<T> = T extends extraType<infer T> ? T : never;

/***********************************************
 ** The part to implement a throwable logic
 **********************************************/

// Throwable is only a type holding the possible errors which can be thrown
type throwable<E extends Error> = extraType<E>

// return the error typed according to the throwableMethod passed into parameter
type basicFunction = (...any: any[]) => any;

type exceptionsOf<T extends basicFunction> = getExtraType<ReturnType<T>>;

const getTypedError = function<T extends basicFunction> (error: unknown, throwableMethod:T) {
    return error as exceptionsOf<T>;
};

أضفت أيضًا نوعًا جديدًا exceptionsOf والذي يسمح باستخراج أخطاء الوظيفة من أجل تصعيد المسؤولية. على سبيل المثال:

function anotherUnreliableNumberGenerator(): number & throwable<exceptionsOf<typeof unreliableNumberGenerator>> {
// I don't want to use a try and catch block here
    return (Math.random() > 0.5) ? unreliableNumberGenerator() : 100;
}

نظرًا لأن exceptionsOf يحصل على اتحاد من الأخطاء ، يمكنك تصعيد أي عدد من الطرق المهمة كما تريد:

function aSuperUnreliableNumberGenerator(): number & throwable<exceptionsOf<typeof unreliableNumberGenerator> | exceptionsOf<typeof anotherUnreliableNumberGenerator>> {
// I don't want to use a try and catch block here
    return (Math.random() > 0.5) ? unreliableNumberGenerator() : unreliableNumberGenerator();
}

لا أحب استخدام typeof ، إذا وجدت طريقة أفضل ، فسأعلمك بذلك

يمكنك اختبار نصائح النتائج هنا : مرر typedError في السطر 106

Xample هذا حل رائع باستخدام الأدوات المتوفرة لدينا الآن!
لكنني أعتقد أنه من الناحية العملية لا يكفي ذلك لأنه لا يزال بإمكانك القيام بأشياء مثل:

const a = superRiskyMethod();
const b = a + 1;

ويتم الاستدلال على نوع b على أنه رقم صحيح ولكن فقط إذا تم تغليفه داخل محاولة
هذا لا ينبغي أن يكون صحيحا

luisgurmendezMLabs أنا لا أفهم تمامًا ما تقصده. إذا اتبعت repoXample في السطر 56. يمكنك رؤية نتيجة myNumber + 23 يتم استنتاجها كـ number ، بينما myNumber: number & extraType<Error | CustomError> .

في مرحلة أخرى ، لا يتم تغليف a في مثالك أبدًا في شيء مثل Try Monad. لا يحتوي على غلاف على الإطلاق بخلاف التقاطع مع & extraType<Error | CustomError> .

تهانينا على التصميم الرائع @ Xample . هذا حقًا واعد ومفيد بالفعل حتى بدون أي سكر نحوي. هل لديك أي خطة لبناء مكتبة كتابة لهذا الغرض؟

ivawzh أفكر في أنه من الناحية العملية قد يؤدي هذا إلى بعض المشكلات منذ:

function stillARiskyMethod() { 
    const a = superRiskyMethod();
    return a + 1
}

يُستدل على إرجاع نوع الوظيفة هذا كرقم وهذا ليس صحيحًا تمامًا

luisgurmendezMLabs يكون النوع القابل للإلقاء ضمن نوع الإرجاع للدالة فقط كطريقة لإلصاق نوع الخطأ بالدالة (ولتتمكن من استعادة تلك الأنواع لاحقًا). أنا لا أعيد الأخطاء بشكل حقيقي ، أنا أرميها فقط. لذلك ، إذا كنت داخل كتلة محاولة أو لا ، فلن تغير أي شيء.

ivawzh أشكرك على سؤالك ، لقد فعلت ذلك من أجلك

@ luisgurmendezMLabs آه حسنًا ، أفهم وجهة نظرك ، يبدو أنه مكتوب على الطباعة فقط استنتاج النوع الأول الذي تم العثور عليه. على سبيل المثال إذا كان لديك:

function stillARiskyMethod() { 
    return superRiskyMethod();
}

سيتم استنتاج نوع الإرجاع لـ stillARiskyMethod بشكل صحيح ، بينما

function stillARiskyMethod() { 
    return Math.random() < 0.5 superRiskyMethod() : anotherSuperRiskyMethod();
}

سيكتب فقط نوع الإرجاع كـ superRiskyMethod() ويرفض النوع anotherSuperRiskyMethod() . أنا لم أحقق أكثر

لهذا السبب ، تحتاج إلى تصعيد نوع الخطأ يدويًا.

function stillARiskyMethod(): number & throwable<exceptionOf<typeof superRiskyMethod>> { 
    const a = superRiskyMethod();
    return a + 1
}

أنا أيضا أردت فقط أن أترك أفكاري في هذا.

أعتقد أن هذه ستكون ميزة جيدة حقًا للتنفيذ حيث توجد حالات استخدام مختلفة للرغبة في إرجاع خطأ / فارغ والرغبة في رمي شيء ما ويمكن أن يكون لها الكثير من الإمكانات. يعد طرح الاستثناءات جزءًا من لغة Javascript على أي حال ، فلماذا لا نعطي خيار الكتابة والاستنتاج؟

على سبيل المثال ، إذا كنت أقوم بالعديد من المهام التي قد تخطئ ، أجد أنه من غير الملائم استخدام عبارة if للتحقق من نوع الإرجاع في كل مرة ، حيث يمكن تبسيط ذلك باستخدام try / catch حيث يوجد سيتم التعامل مع إحدى هذه المهام دون الحاجة إلى أي تعليمات برمجية إضافية.

يكون هذا مفيدًا بشكل خاص عندما تتعامل مع الخطأ بنفس الطريقة ؛ على سبيل المثال في express / node.js ، قد أرغب في تمرير الخطأ إلى NextFunction (معالج الأخطاء).

بدلاً من تنفيذ if (result instanceof Error) { next(result); } في كل مرة ، يمكنني فقط تغليف كل التعليمات البرمجية الخاصة بهذه المهام في محاولة / التقاط ، وأنا أعلم أنه منذ طرح استثناء ، سأرغب دائمًا في تمرير هذا إلى معالج الأخطاء ، لذلك يمكن أن يكون catch(error) { next(error); }

لم نر هذا أيضًا تمت مناقشته حتى الآن (ربما فاته ، ومع ذلك ، يحتوي هذا الموضوع على عدد قليل من التعليقات!) ولكن إذا تم تنفيذ ذلك ، فهل سيكون إلزاميًا (على سبيل المثال: خطأ في التجميع) أن يكون لديك وظيفة يتم طرحها دون استخدام شرط throws في إعلان وظيفته؟ أشعر أنه سيكون من اللطيف القيام بذلك (نحن لا نجبر الأشخاص على التعامل مع الرميات ، بل سنخبرهم فقط أن الوظيفة ترمي) ولكن القلق الأكبر هنا هو أنه إذا تم تحديث تنقيط بهذه الطريقة ، فمن المحتمل كسر الكثير من التعليمات البرمجية الموجودة حاليًا.

تحرير: حالة استخدام أخرى فكرت فيها يمكن أن تساعد أيضًا في إنشاء محرر JSD باستخدام علامة throws

سأكرر هنا ما قلته في الموضوع الملتزم الآن.


أعتقد أن الكتابة المطبوعة يجب أن تكون قادرة على استنتاج نوع الخطأ لمعظم تعبيرات JavaScript. السماح بالتنفيذ الأسرع من قبل مصممي المكتبات.

function a() {
  if (Math.random() > .5) {
    throw 'unlucky';
  }
}

function b() {
  a();
}

function c() {
  if (Math.random() > .5) {
    throw 'unlucky';
  }
  if (Math.random() > .5) {
    throw 'fairly lucky';
  }
}

function d() {
  return eval('You have no IDEA what I am capable of!');
}

function e() {
  try {
    return c;
  } catch(e) {
    throw 'too bad...';
  }
}

function f() {
  c();
}

function g() {
  a();
  c();
}
  • الوظيفة a نعلم أن نوع الخطأ هو 'unlucky' ، وإذا أردنا أن نكون حذرين للغاية ، فيمكننا توسيعه إلى Error | 'unlucky' ، وبالتالي includeError .
  • سوف ترث الوظيفة b نوع الخطأ الخاص بالدالة a .
  • الوظيفة c متطابقة تقريبًا ؛ 'unlucky' | 'fairly lucky' أو Error | 'unlucky' | 'fairly lucky' .
  • الوظيفة d ستضطر إلى طرح unknown ، حيث إن Eval غير معروف ...
  • اكتشفت الوظيفة e الخطأ d ، ولكن نظرًا لوجود throw في كتلة catch ، فإننا نستنتج نوعها 'too bad...' ، هنا ، نظرًا لأن الكتلة فقط يحتوي على throw 'primitive value' يمكننا القول أنه لا يمكنه رمي Error (صححني إذا فاتني بعض السحر الأسود JS ...)
  • ترث الوظيفة f من c كما فعل b من a .
  • الوظيفة g ترث 'unlucky' من a و unknown من c وبالتالي 'unlucky' | unknown => unknown

فيما يلي بعض خيارات المترجم التي أعتقد أنه يجب تضمينها ، حيث قد يختلف تفاعل المستخدمين مع هذه الميزة اعتمادًا على مهارتهم وكذلك نوع أمان المكتبات التي يعتمدون عليها في مشروع معين:

{
  "compilerOptions": {
    "errorHandelig": {
      "enable": true,
      "forceCatching": false,   // Maybe better suited for TSLint/ESLint...
      "includeError": true, // If true, every function will by default throw `Error | (types of throw expressions)`
      "catchUnknownOnly": false, // If true, every function will by default throw `unknown`, for those very skeptics (If someone replaces some global with weird proxy, for example...)
      "errorAsesertion": true,  // If false, the user will not be able to change error type manually
    }
  }
}

بالنسبة إلى بناء الجملة حول كيفية التعبير عن الخطأ الذي يمكن أن تحدثه أي وظيفة ، لست متأكدًا ، لكنني أعلم أننا بحاجة إلى القدرة على أن تكون عامة ولا يمكن الاستدلال عليها.

declare function getValue<T extends {}, K extends keyof T>(obj: T, key: K): T[K] throws void;
declare function readFile<throws E = 'not valid file'>(file: string, customError: E): string throws E;

حالة الاستخدام الخاصة بي ، حيث إن إظهار حالة استخدام فعلية قد يُظهر أن لها قيمة أخرى:

declare function query<T extends {}, throws E>(sql: string, error: E): T[] throws E;

app.get('path',(req, res) => {
  let user: User;
  try {
    user = query('SELECT * FROM ...', 'get user');
  } catch(e) {
    return res.status(401);
  }

  try {
    const [posts, followers] = Promise.all([
      query('SELECT * FROM ...', "user's posts"),
      query('SELECT * FROM ...', "user's follower"'),
    ]);

    res.send({ posts, followers });
  } catch(e) {
    switch (e) {
      case "user's posts":
        return res.status(500).send('Loading of user posts failed');

      case "user's posts":
        return res.status(500).send('Loading of user stalkers failed, thankfully...');

      default:
        return res.status(500).send('Very strange error!');
    }
  }
});

أحتاج إلى حوض خطأ لمنع إرسال رؤوس استجابة عدة مرات لأخطاء متعددة (عادةً لا تحدث ، ولكن عندما تفعل ذلك فإنها تفعل ذلك بكميات كبيرة!)

لا تزال هذه المشكلة تحمل علامة Awaiting More Feedback ، ما الذي يمكننا فعله لتقديم المزيد من الملاحظات؟ هذه هي الميزة الوحيدة التي أحسدها على لغة جافا

لدينا حالة استخدام حيث نعرض خطأً محددًا عندما يقوم استدعاء واجهة برمجة التطبيقات (API) بإرجاع رمز غير 200:

interface HttpError extends Error {
  response: Response
}

try {
  loadData()
} catch (error: Error | ResponseError) {
  if (error.response) {
    checkStatusCodeAndDoSomethingElse()
  } else {
    doGenericErrorHandling()
  }
}

عدم القدرة على كتابة كتلة catch ينتهي بالمطورين متناسين أنه يمكن إلقاء نوعين محتملين من الأخطاء ، ويحتاجون إلى التعامل مع كليهما.

أفضل رمي كائن الخطأ دائمًا:

function fn(num: number): void {
    if (num === 0) {
        throw new TypeError("Can't deal with 0")
    }
}
try {
 fn(0)
}
catch (err) {
  if (err instanceof TypeError) {
   if (err.message.includes('with 0')) { .....}
  }
}

لماذا لا تزال هذه الميزة "ردود فعل غير كافية"؟ إنه مفيد جدًا عند استدعاء واجهة برمجة تطبيقات المتصفح مثل indexedDB و localstorage. لقد تسبب في العديد من الفشل في سيناريو معقد ولكن المطور لا يستطيع أن يدرك في البرمجة.

يبدو أن هيجل يمتلك هذه الميزة تمامًا.
https://hegel.js.org/docs#benefits (مرر إلى قسم "خطأ مكتوب")

أتمنى أن يكون لدى TypeScript ميزة مماثلة!

DL ؛ DR ؛

  • يجب كتابة وظيفة الرفض للوعود
  • يجب استنتاج أي نوع يتم طرحه داخل كتلة try باستخدام اتحاد في وسيطة الخطأ الخاصة بـ catch
  • يجب كتابة error.constructor بشكل صحيح باستخدام النوع الحقيقي وليس فقط any لمنع فقدان خطأ تم طرحه.

حسنًا ، ربما يجب علينا ببساطة توضيح ما هي احتياجاتنا وتوقعاتنا:

يتم التعامل مع الأخطاء عادةً بثلاث طرق في js

1. طريقة العقدة

استخدام عمليات الاسترجاعات (والتي يمكن كتابتها بالفعل)

مثال على الاستخدام:

import * as fs from 'fs';

fs.readFile('readme.txt', 'utf8',(error, data) => {
    if (error){
        console.error(error);
    }
    if (data){
        console.log(data)
    }
});

حيث يمنحنا fs.d.ts :

function readFile(path: PathLike | number, options: { encoding: string; flag?: string; } | string, callback: (err: NodeJS.ErrnoException | null, data: string) => void): void;

لذلك تم كتابة الخطأ على هذا النحو

    interface ErrnoException extends Error {
        errno?: number;
        code?: string;
        path?: string;
        syscall?: string;
        stack?: string;
    }

2. طريق الوعد

حيث يتم حل الوعد أو رفضه ، بينما يمكنك كتابة value الذي تم حله ، لا يمكنك كتابة الرفض الذي يُسمى غالبًا reason .

هذا هو توقيع مُنشئ الوعد: لاحظ أن السبب مكتوب any

    new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;

من الممكن نظريًا كتابتها على النحو التالي:

    new <T, U=any>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: U) => void) => void): Promise<T>;

بهذه الطريقة ، لا يزال بإمكاننا نظريًا إجراء تحويل 1-1 بين رد اتصال عقدة ووعد ، مع الحفاظ على كل الكتابة على طول هذه العملية. على سبيل المثال:

const fs = require('fs')
const util = require('util')

const readFilePromise = util.promisify(fs.readFile); // (path: PathLike | number, options: { encoding: string; flag?: string; } | string) => Promise<data: string, NodeJS.ErrnoException>;

3. طريقة "المحاولة والإمساك"

try {
    throw new Error();
}
catch (error: unknown) { //typing the error is possible since ts 4.0
    if (error instanceof Error) {
        error.message;
    }
}

على الرغم من أننا نلقي خطأ "خطأ" في سطرين قبل كتلة الالتقاط ، إلا أن TS غير قادر على كتابة error (القيمة) كـ Error (النوع).

أليس هذا هو الحال ولا استخدام الوعود داخل وظيفة async (لا يوجد سحر "حتى الآن"). باستخدام رد الاتصال الخاص بالعقدة الموعودة:

async function Example() {
    try {
        const data: string = await readFilePromise('readme.txt', 'utf-8');
        console.log(data)
    }
    catch (error) { // it can only be NodeJS.ErrnoException, we can type is ourself… but nothing prevents us to make a mistake
        console.error(error.message);
    }
}

لا توجد معلومات مفقودة للنص المطبوع حتى يتمكن من التنبؤ بنوع الخطأ الذي يمكن إلقاؤه في نطاق المحاولة.
ومع ذلك ، يجب أن نأخذ في الاعتبار أن الدوال الداخلية الأصلية قد تؤدي إلى حدوث أخطاء ليست ضمن الكود المصدري ، ولكن إذا كانت هناك كلمة رئيسية "رمية" في المصدر في كل مرة ، يجب على TS جمع النوع واقتراحه كنوع محتمل للخطأ. بالطبع سيتم تحديد نطاق هذه الأنواع من خلال كتلة try .

هذه ليست سوى الخطوة الأولى وسيظل هناك مجال للتحسين ، مثل وضع صارم يجبر TS على العمل كما هو الحال في Java ، أي إجبار المستخدم على استخدام طريقة محفوفة بالمخاطر (طريقة يمكنها طرح شيء ما) في غضون try كتلة function example throw ErrorType { ... } لتصعيد مسؤولية معالجة الأخطاء.

أخيرًا وليس آخرًا: منع فقدان الخطأ

يمكن طرح أي شيء ، ليس فقط خطأ أو حتى مثيل كائن. المعنى ما يلي صحيح

try {
    if (Math.random() > 0.5) {
        throw 0
    } else {
        throw new Error()
    }
}
catch (error) { // error can be a number or an object of type Error
    if (typeof error === "number") {
        alert("silly number")
    }

    if (error instanceof Error) {
        alert("error")
    }
}

إن معرفة أن الخطأ قد يكون من النوع number | Error سيكون مفيدًا للغاية. ومع ذلك ، لمنع نسيان التعامل مع نوع محتمل من الخطأ ، فليس من الأفضل حقًا استخدام كتل if / else منفصلة بدون مجموعة صارمة من النتائج المحتملة.
ومع ذلك ، فإن حالة التبديل ستفعل ذلك بشكل أفضل بكثير حيث يمكن تحذيرنا إذا نسينا مطابقة حالة معينة (أي حالة قد ترجع إلى البند الافتراضي). لا يمكننا (ما لم نفعل شيئًا اختراقًا) تبديل حالة نوع كائن ، وحتى إذا استطعنا ، يمكننا رمي أي شيء (ليس فقط كائنًا ولكن أيضًا منطقيًا وسلسلة ورقمًا ليس له "مثيل"). ومع ذلك ، يمكننا استخدام مُنشئ المثيل لمعرفة نوعه. يمكننا الآن إعادة كتابة الكود أعلاه على النحو التالي:

try {
    if (Math.random() > 0.5) {
        throw 0
    } else {
        throw new Error()
    }
}
catch (error) { // error can be a Number or an object of type `Error`
    switch (error.constructor){
        case Number: alert("silly number"); break;
        case Error: alert("error"); break;
    }
}

Horray ... المشكلة الوحيدة المتبقية هي أن TS لا يكتب error.constructor وبالتالي لا توجد طريقة لتضييق حالة التبديل (حتى الآن؟) ، إذا كان سيفعل ذلك ، فسنحصل على لغة خطأ مكتوبة آمنة لـ شبيبة.

الرجاء التعليق إذا كنت بحاجة إلى مزيد من ردود الفعل

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات