Typescript: Saran: klausa `throws` dan klausa catch yang diketik

Dibuat pada 29 Des 2016  ·  135Komentar  ·  Sumber: microsoft/TypeScript

Sistem tipe TypeScript sangat membantu dalam banyak kasus, tetapi tidak dapat digunakan saat menangani pengecualian.
Sebagai contoh:

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

Masalahnya di sini ada dua (tanpa melihat kode):

  1. Saat menggunakan fungsi ini, tidak ada cara untuk mengetahui bahwa itu mungkin menimbulkan kesalahan
  2. Tidak jelas jenis kesalahan apa yang akan terjadi

Dalam banyak skenario, ini sebenarnya bukan masalah, tetapi mengetahui apakah suatu fungsi/metode mungkin mengeluarkan pengecualian bisa sangat berguna dalam skenario yang berbeda, terutama saat menggunakan pustaka yang berbeda.

Dengan memperkenalkan pengecualian yang dicentang (opsional), sistem tipe dapat digunakan untuk penanganan pengecualian.
Saya tahu bahwa pengecualian yang dicentang tidak disetujui (misalnya Anders Hejlsberg ), tetapi dengan menjadikannya opsional (dan mungkin disimpulkan? nanti) maka itu hanya menambah peluang untuk menambahkan lebih banyak informasi tentang kode yang dapat membantu pengembang, alat, dan dokumentasi.
Ini juga akan memungkinkan penggunaan yang lebih baik dari kesalahan khusus yang berarti untuk proyek-proyek besar yang besar.

Karena semua kesalahan runtime javascript bertipe Error (atau memperluas jenis seperti TypeError ), tipe sebenarnya untuk suatu fungsi akan selalu type | Error .

Tata bahasanya mudah, definisi fungsi dapat diakhiri dengan klausa throws diikuti oleh tipe:

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

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

Saat menangkap pengecualian, sintaksnya sama dengan kemampuan untuk mendeklarasikan jenis kesalahan:
catch(e: string | Error) { ... }

Contoh:

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

Di sini jelas bahwa fungsi dapat menimbulkan kesalahan dan kesalahan akan berupa string, sehingga ketika memanggil metode ini, pengembang (dan kompiler/IDE) menyadarinya dan dapat menanganinya dengan lebih baik.
Jadi:

fn(0);

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

Kompilasi tanpa kesalahan, tetapi:

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

Gagal dikompilasi karena number bukan string .

Kontrol aliran dan inferensi jenis kesalahan

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

Melempar string .

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

    fn(num);
}

Melempar MyError | string .
Namun:

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

Melempar hanya MyError .

Awaiting More Feedback Suggestion

Komentar yang paling membantu

@aleksey-bykov

Anda menyarankan untuk tidak menggunakan throw sama sekali dalam kode saya dan sebaliknya membungkus hasilnya (dalam fungsi yang mungkin error).
Pendekatan ini memiliki beberapa kelemahan:

  • Pembungkus ini menciptakan lebih banyak kode
  • Ini mengharuskan semua rantai fungsi yang dipanggil mengembalikan nilai yang dibungkus ini (atau kesalahan) atau sebagai alternatif, fungsi yang mendapatkan Tried<> tidak dapat memilih untuk mengabaikan kesalahan.
  • Ini bukan standar, perpustakaan pihak ke-3 dan kesalahan lemparan js asli

Menambahkan throws akan memungkinkan pengembang yang memilih untuk menangani kesalahan dari kode mereka, pustaka ke-3, dan js asli.
Karena saran juga meminta untuk inferensi kesalahan, semua file definisi yang dihasilkan dapat menyertakan klausa throws .
Akan sangat mudah untuk mengetahui kesalahan apa yang mungkin dilontarkan suatu fungsi langsung dari file definisi alih-alih status saat ini di mana Anda harus pergi ke dokumen, misalnya untuk mengetahui kesalahan mana yang mungkin terjadi JSON.parse Saya harus pergi ke halaman MDN dan baca bahwa:

Melempar pengecualian SyntaxError jika string yang akan diurai bukan JSON yang valid

Dan ini adalah kasus yang baik ketika kesalahan didokumentasikan.

Semua 135 komentar

Hanya untuk memperjelas - salah satu ide di sini bukan untuk memaksa pengguna untuk menangkap pengecualian, melainkan, untuk lebih menyimpulkan jenis variabel klausa catch?

@DanielRosenwasser
Ya, pengguna tidak akan dipaksa untuk menangkap pengecualian, jadi ini baik-baik saja dengan kompiler (tentu saja saat runtime kesalahan dilemparkan):

function fn() {
    throw "error";
}

fn();

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

Tapi itu akan memberi pengembang cara untuk mengekspresikan pengecualian mana yang dapat dilemparkan (akan luar biasa memilikinya saat menggunakan file .d.ts perpustakaan lain) dan kemudian meminta tipe kompiler menjaga tipe pengecualian di dalam klausa catch.

bagaimana lemparan yang diperiksa berbeda dari 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);
}

dari pada

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

@aleksey-bykov

Anda menyarankan untuk tidak menggunakan throw sama sekali dalam kode saya dan sebaliknya membungkus hasilnya (dalam fungsi yang mungkin error).
Pendekatan ini memiliki beberapa kelemahan:

  • Pembungkus ini menciptakan lebih banyak kode
  • Ini mengharuskan semua rantai fungsi yang dipanggil mengembalikan nilai yang dibungkus ini (atau kesalahan) atau sebagai alternatif, fungsi yang mendapatkan Tried<> tidak dapat memilih untuk mengabaikan kesalahan.
  • Ini bukan standar, perpustakaan pihak ke-3 dan kesalahan lemparan js asli

Menambahkan throws akan memungkinkan pengembang yang memilih untuk menangani kesalahan dari kode mereka, pustaka ke-3, dan js asli.
Karena saran juga meminta untuk inferensi kesalahan, semua file definisi yang dihasilkan dapat menyertakan klausa throws .
Akan sangat mudah untuk mengetahui kesalahan apa yang mungkin dilontarkan suatu fungsi langsung dari file definisi alih-alih status saat ini di mana Anda harus pergi ke dokumen, misalnya untuk mengetahui kesalahan mana yang mungkin terjadi JSON.parse Saya harus pergi ke halaman MDN dan baca bahwa:

Melempar pengecualian SyntaxError jika string yang akan diurai bukan JSON yang valid

Dan ini adalah kasus yang baik ketika kesalahan didokumentasikan.

Dan ini adalah kasus yang baik ketika kesalahan didokumentasikan.

apakah ada cara yang dapat diandalkan dalam javascript untuk membedakan SyntaxError dari Error?

  • ya, ini lebih banyak kode, tetapi karena situasi buruk direpresentasikan dalam suatu objek, itu dapat diteruskan untuk diproses, dibuang, disimpan, atau diubah menjadi hasil yang valid sama seperti nilai lainnya

  • anda dapat mengabaikan percobaan dengan mengembalikan percobaan juga, percobaan dapat dilihat sebagai monad, mencari perhitungan monadik

    function mightFail(): Tried<number, string> {
    }
    function mightFailToo(): Tried<number, string> {
        const tried = mightFail();
        if (isSuccess(tried))  { 
             return successFrom(tried.result * 2);
        } else {
             return tried;
        }
    }
    
  • itu cukup standar untuk kode Anda, ketika datang ke lib pihak ke-3 yang melemparkan pengecualian, itu biasanya berarti permainan untuk Anda, karena hampir tidak mungkin untuk memulihkan secara andal dari pengecualian, alasannya adalah itu dapat dibuang dari mana saja di dalam kode menghentikannya pada posisi yang sewenang-wenang dan membiarkan keadaan internalnya tidak lengkap atau rusak

  • tidak ada dukungan untuk pengecualian yang diperiksa dari runtime JavaScript, dan saya khawatir itu tidak dapat diimplementasikan dalam TypeScript saja

selain itu pengkodean pengecualian sebagai kasus hasil khusus adalah praktik yang sangat umum di dunia FP

sedangkan membagi hasil yang mungkin menjadi 2 bagian:

  • satu disampaikan oleh pernyataan pengembalian dan
  • yang lain disampaikan dengan lemparan

terlihat kesulitan yang dibuat-buat

menurut pendapat saya, lemparan bagus untuk gagal dengan cepat dan keras ketika tidak ada yang dapat Anda lakukan, hasil yang dikodekan secara eksplisit baik untuk apa pun yang menyiratkan situasi buruk namun diharapkan yang dapat Anda pulihkan

mempertimbangkan:

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

Maksud saya dengan JSON.parse mungkin melempar SyntaxError adalah bahwa saya perlu mencari fungsinya di dokumen hanya untuk mengetahui bahwa itu mungkin melempar, dan akan lebih mudah untuk melihatnya di .d.ts .
Dan ya, Anda dapat mengetahui bahwa itu SyntaxError dengan menggunakan instanceof .

Anda dapat mewakili situasi buruk yang sama dengan melempar kesalahan.
Anda dapat membuat kelas kesalahan Anda sendiri yang memperluas Error dan memasukkan semua data relevan yang Anda butuhkan di dalamnya.
Anda mendapatkan hal yang sama dengan lebih sedikit kode.

Terkadang Anda memiliki rantai pemanggilan fungsi yang panjang dan Anda mungkin ingin menangani beberapa kesalahan di tingkat rantai yang berbeda.
Akan sangat menjengkelkan untuk selalu menggunakan hasil yang dibungkus (monads).
Belum lagi, perpustakaan lain dan kesalahan asli mungkin tetap ada, jadi Anda mungkin akhirnya menggunakan monad dan coba/tangkap.

Saya tidak setuju dengan Anda, dalam banyak kasus Anda dapat memulihkan dari kesalahan yang dilemparkan, dan jika bahasa memungkinkan Anda mengekspresikannya lebih baik daripada itu akan lebih mudah untuk melakukannya.

Seperti banyak hal di TypeScript, kurangnya dukungan fitur di javascript tidak menjadi masalah.
Ini:

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

Akan berfungsi seperti yang diharapkan dalam javascript, hanya tanpa anotasi tipe.

Menggunakan throw sudah cukup untuk mengungkapkan apa yang Anda katakan: jika operasi berhasil, kembalikan nilainya, jika tidak, lempar kesalahan.
Pengguna fungsi ini kemudian akan memutuskan apakah dia ingin menangani kemungkinan kesalahan atau mengabaikannya.
Anda hanya dapat menangani kesalahan yang Anda lakukan sendiri dan mengabaikan kesalahan yang merupakan pihak ke-3 misalnya.

jika kita berbicara tentang browser instanceof hanya baik untuk hal-hal yang berasal dari jendela/dokumen yang sama, coba:

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

jadi ketika Anda melakukannya:

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

kamu tidak akan menangkapnya

masalah lain dengan pengecualian bahwa mereka mungkin masuk ke kode Anda dari jauh di luar tempat yang Anda harapkan terjadi

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

selain itu instanceof rentan terhadap pewarisan prototipe, jadi Anda harus ekstra hati-hati untuk selalu memeriksa leluhur terakhir

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

@aleksey-bykov Kesalahan threading eksplisit seperti yang Anda sarankan dalam struktur monadik adalah tugas yang cukup sulit dan menakutkan. Dibutuhkan banyak usaha, membuat kode sulit untuk dipahami dan membutuhkan dukungan bahasa / tipe-driven emit untuk berada di tepi yang tertahankan. Ini adalah komentar yang datang dari seseorang yang berusaha keras untuk mempopulerkan Haskell dan FP secara keseluruhan.

Ini adalah alternatif yang berfungsi, terutama untuk penggemar (termasuk saya sendiri), namun menurut saya ini bukan pilihan yang layak untuk audiens yang lebih besar.

Sebenarnya, perhatian utama saya di sini adalah bahwa orang akan mulai membuat subklasifikasi Error. Saya pikir ini adalah pola yang mengerikan. Secara lebih umum, apa pun yang mempromosikan penggunaan operator instanceof hanya akan membuat kebingungan tambahan di sekitar kelas.

Ini adalah komentar yang datang dari seseorang yang berusaha keras untuk mempopulerkan Haskell dan FP secara keseluruhan.

Saya benar-benar berpikir ini harus didorong lebih keras kepada penonton, tidak sampai dicerna dan diminta lebih banyak, bisakah kita memiliki dukungan FP yang lebih baik dalam bahasa

dan itu tidak menakutkan seperti yang Anda pikirkan, asalkan semua kombinator sudah ditulis, gunakan saja untuk membangun aliran data, seperti yang kami lakukan dalam proyek kami, tetapi saya setuju bahwa TS dapat mendukungnya dengan lebih baik: #2319

Trafo Monad adalah PITA nyata. Anda perlu mengangkat, mengangkat, dan berlari selektif cukup sering. Hasil akhirnya adalah kode yang hampir tidak dapat dipahami dan jauh lebih tinggi dari penghalang masuk yang dibutuhkan. Semua fungsi kombinator dan pengangkatan (yang menyediakan tinju/unboxing wajib) hanyalah kebisingan yang mengalihkan Anda dari masalah yang dihadapi. Saya percaya bahwa menjadi eksplisit tentang keadaan, efek, dll adalah hal yang baik, tetapi saya rasa kami belum menemukan pembungkus/abstraksi yang nyaman. Sampai kami menemukannya, mendukung pola pemrograman tradisional sepertinya merupakan cara yang harus dilakukan tanpa henti untuk bereksperimen dan menjelajah dalam waktu yang bersamaan.

PS: Saya pikir kami membutuhkan lebih dari sekadar operator khusus. Jenis Jenis yang Lebih Tinggi dan beberapa jenis kelas jenis juga penting untuk perpustakaan monadik yang praktis. Di antara mereka, saya akan menilai HKT pertama dan mengetik kelas di urutan kedua. Dengan semua itu, saya percaya TypeScript bukan bahasa untuk mempraktikkan konsep seperti itu. Bermain-main - ya, tetapi filosofi dan akarnya pada dasarnya jauh untuk integrasi tanpa batas yang tepat.

Kembali ke pertanyaan OP - instanceof adalah operator yang berbahaya untuk digunakan. Namun pengecualian eksplisit tidak terbatas pada Error . Anda juga dapat membuang kesalahan ADT atau POJO kustom Anda sendiri. Fitur yang diusulkan bisa sangat berguna dan, tentu saja, juga bisa disalahgunakan dengan cukup keras. Bagaimanapun itu membuat fungsi lebih transparan yang tidak diragukan lagi merupakan hal yang baik. Secara keseluruhan saya 50/50 di atasnya :)

@aleksey-bykov

Pengembang harus menyadari berbagai masalah js yang Anda jelaskan, setelah semua menambahkan throws ke TypeScript tidak memperkenalkan sesuatu yang baru ke js, itu hanya memberikan TypeScript sebagai bahasa kemampuan untuk mengekspresikan perilaku js yang ada.

Fakta bahwa perpustakaan pihak ke-3 dapat menimbulkan kesalahan adalah maksud saya.
Jika file definisi mereka termasuk itu maka saya akan memiliki cara untuk mengetahuinya.

@aluanhaddad
Mengapa pola yang buruk untuk diperpanjang Error ?

@gcnew
Adapun instanceof , itu hanya sebuah contoh, saya selalu dapat membuang objek biasa yang memiliki tipe berbeda dan kemudian menggunakan pelindung tipe untuk membedakannya.
Terserah pengembang untuk memutuskan jenis kesalahan apa yang ingin dia lempar, dan mungkin memang demikian, tetapi saat ini tidak ada cara untuk mengungkapkannya, yang ingin diselesaikan oleh saran ini.

@nitzantomer Mensubklasifikasikan kelas asli ( Error , Array , RegExp , dll) tidak didukung di versi ECMAScript yang lebih lama (sebelum ES6). Pancaran tingkat bawah untuk kelas-kelas ini memberikan hasil yang tidak terduga (usaha terbaik telah dilakukan tetapi ini sejauh yang dapat dilakukan) dan merupakan alasan untuk banyak masalah yang dicatat setiap hari. Sebagai aturan praktis - jangan mensubklasifikasikan asli kecuali Anda menargetkan versi ECMAScript terbaru dan benar-benar tahu apa yang Anda lakukan.

@gcnew
Oh, saya sangat menyadarinya karena saya menghabiskan lebih dari beberapa jam mencoba mencari tahu apa yang salah.
Tetapi dengan kemampuan untuk melakukannya sekarang seharusnya tidak ada alasan untuk tidak melakukannya (saat menargetkan es6).

Bagaimanapun, saran ini tidak mengasumsikan bahwa pengguna mensubklasifikasikan kelas Kesalahan, itu hanya sebuah contoh.

@nitzantomer Saya tidak berpendapat bahwa saran terbatas pada Error . Saya baru saja menjelaskan mengapa pola yang buruk untuk mensubklasifikasikannya. Dalam posting saya, saya sebenarnya membela pendirian bahwa objek khusus atau serikat pekerja yang didiskriminasi dapat digunakan juga.

instanceof berbahaya dan dianggap sebagai anti-pola bahkan jika Anda menghilangkan kekhususan JavaScript - mis. Waspadalah terhadap operator instanceof . Alasannya adalah bahwa kompiler tidak dapat melindungi Anda dari bug yang diperkenalkan oleh subkelas baru. Logika menggunakan instanceof rapuh dan tidak mengikuti prinsip buka/tutup , karena hanya mengharapkan beberapa opsi. Bahkan jika kasus wildcard ditambahkan, turunan baru masih cenderung menyebabkan kesalahan karena dapat mematahkan asumsi yang dibuat pada saat penulisan.

Untuk kasus di mana Anda ingin membedakan di antara alternatif yang diketahui, TypeScript memiliki Tagged Unions (juga disebut serikat terdiskriminasi atau tipe data aljabar). Kompiler memastikan bahwa semua kasus ditangani yang memberi Anda jaminan yang bagus. Kelemahannya adalah jika Anda ingin menambahkan entri baru ke tipe tersebut, Anda harus melalui semua kode yang membedakannya dan menangani opsi yang baru ditambahkan. Keuntungannya adalah bahwa kode seperti itu kemungkinan besar akan rusak, tetapi akan gagal saat runtime.

Saya baru saja memikirkan proposal ini dan menjadi menentangnya. Alasannya adalah jika deklarasi throws ada pada tanda tangan tetapi tidak diterapkan, deklarasi tersebut sudah dapat ditangani oleh komentar dokumentasi. Dalam kasus diberlakukan, saya berbagi sentimen bahwa mereka akan menjadi menjengkelkan dan menelan cepat karena JavaScript tidak memiliki mekanisme Java untuk klausa catch yang diketik. Menggunakan pengecualian (terutama sebagai aliran kontrol) juga tidak pernah menjadi praktik yang mapan. Semua ini membawa saya pada pemahaman bahwa pengecualian yang diperiksa membawa terlalu sedikit, sementara cara yang lebih baik dan saat ini lebih umum untuk mewakili kegagalan tersedia (misalnya pengembalian serikat pekerja).

@gcnew
Beginilah cara melakukannya di C#, masalahnya adalah bahwa dokumen tidak standar dalam TypeScript.
Saya tidak ingat menemukan file definisi yang didokumentasikan dengan baik. Berkas lib.d.ts yang berbeda memang berisi komentar, tetapi itu tidak mengandung kesalahan yang dilemparkan (dengan satu pengecualian: lib.es6.d.ts memiliki satu throws di Date[Symbol.toPrimitive](hint: string) ).

Juga, saran ini memperhitungkan kesalahan dalam menyimpulkan, sesuatu yang tidak akan terjadi jika kesalahan berasal dari komentar dokumentasi. Dengan pengecualian yang dicentang yang disimpulkan, pengembang bahkan tidak perlu menentukan klausa throws , kompiler akan menyimpulkannya secara otomatis dan akan menggunakannya untuk kompilasi dan akan menambahkannya ke file definisi yang dihasilkan.

Saya setuju bahwa menerapkan penanganan kesalahan bukanlah hal yang baik, tetapi memiliki fitur ini hanya akan menambahkan lebih banyak informasi yang kemudian dapat digunakan oleh mereka yang menginginkannya.
Masalah dengan:

... ada cara yang lebih baik dan saat ini lebih umum untuk mewakili kegagalan

Apakah itu tidak ada cara standar untuk melakukannya.
Anda mungkin menggunakan union return, @aleksey-bykov akan menggunakan Tried<> , dan pengembang perpustakaan pihak ketiga lainnya akan melakukan sesuatu yang sama sekali berbeda.
Melempar kesalahan adalah standar di seluruh bahasa (js, Java, c#...) dan karena ini adalah bagian dari sistem dan bukan solusi, seharusnya (menurut saya) penanganan yang lebih baik dalam TypeScript, dan buktinya adalah jumlahnya masalah yang saya lihat di sini dari waktu ke waktu yang meminta anotasi jenis di klausa catch .

Saya ingin memiliki informasi di tooltip di VS jika suatu fungsi (atau disebut fungsi) dapat dilempar. Untuk file *.d.ts kita mungkin memerlukan parameter palsu seperti ini sejak TS2.0.

@HolgerJeromin
Mengapa itu dibutuhkan?

di sini adalah pertanyaan sederhana, tanda tangan apa yang harus disimpulkan untuk dontCare dalam kode di bawah ini?

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

function dontCare() {
   return mightThrow();
}

sesuai dengan apa yang Anda katakan dalam proposal Anda, itu seharusnya

function dontCare(): void throws string {

saya katakan itu seharusnya kesalahan ketik karena pengecualian yang diperiksa tidak ditangani dengan benar

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

mengapa demikian?

karena jika tidak, ada peluang yang sangat bagus untuk membuat status penelepon langsung rusak:

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

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

jika Anda membiarkan pengecualian lolos, Anda tidak dapat menyimpulkannya sebagai dicentang, karena kontrak perilaku keepAllValues akan dilanggar dengan cara ini (tidak semua nilai disimpan terlepas dari maksud aslinya)

satu-satunya cara aman adalah menangkap mereka segera dan melemparkannya kembali secara eksplisit

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

jika tidak, meskipun penelepon tahu apa yang dapat dilakukan, Anda tidak dapat memberi mereka jaminan bahwa aman untuk melanjutkan menggunakan kode yang baru saja dilemparkan

jadi tidak ada yang namanya propagasi kontrak pengecualian yang diperiksa otomatis

dan koreksi saya jika saya salah, inilah yang dilakukan Java, yang Anda sebutkan sebagai contoh sebelumnya

@aleksey-bykov
Ini:

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

function dontCare() {
   return mightThrow();
}

Berarti keduanya mightThrow dan dontCare disimpulkan ke throws string , namun:

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

Tidak akan memiliki klausa throw karena kesalahan telah ditangani.
Ini:

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

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

Akan memiliki throws MyErrorType .

Adapun contoh keepAllValues Anda, saya tidak yakin apa yang Anda maksud, dalam contoh Anda:

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

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

MyClass.keepAllValues akan disimpulkan sebagai throws string karena mightThrow mungkin melempar string dan kesalahan itu tidak ditangani.

Adapun contoh keepAllValues Anda, saya tidak yakin apa yang Anda maksud

Maksud saya pengecualian yang tidak tertangani dari mightThrow interrupt keepAllValues dan membuatnya selesai di tengah-tengah apa yang dilakukannya sehingga statusnya rusak. Ini sebuah masalah. Apa yang Anda sarankan adalah untuk menutup mata Anda pada masalah ini dan berpura-pura tidak serius. Apa yang saya sarankan adalah untuk mengatasi masalah ini dengan mengharuskan semua pengecualian yang diperiksa segera ditangani dan secara eksplisit ditampilkan kembali. Dengan cara ini tidak mungkin negara korup secara tidak sengaja . Dan meskipun masih bisa rusak jika Anda memilihnya, itu akan membutuhkan beberapa pengkodean yang disengaja.

Pikirkan tentang hal ini, ada 2 cara kita bisa melakukan pengecualian:

  • lepaskan mereka, yang mengarah ke kecelakaan, jika kecelakaan itu yang Anda inginkan maka kita baik-baik saja di sini
  • jika Anda tidak ingin crash maka Anda memerlukan beberapa panduan tentang pengecualian seperti apa yang perlu Anda cari, dan di sinilah proposal Anda masuk: memeriksa pengecualian - semua terdaftar secara eksplisit, sehingga Anda dapat menangani semuanya dan tidak jangan lewatkan apapun

sekarang jika kami memutuskan untuk menggunakan pengecualian yang diperiksa yang ditangani dengan benar dan mencegah crash, kami perlu mengesampingkan situasi ketika kami menangani pengecualian yang berasal dari beberapa lapisan di mana Anda menangkapnya:

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
}

contoh di atas membawa kasus yang menarik untuk dipertimbangkan, apa yang akan menjadi tanda tangan yang disimpulkan dari:

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

kasus menarik lainnya

// 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
Jadi Anda mengusulkan agar semua kesalahan harus ditangani seperti halnya dengan Java?
Saya bukan penggemar itu (walaupun saya berasal dari Java dan masih menyukainya) karena js/ts jauh lebih dinamis dan penggunanya terbiasa dengan itu.
Ini bisa menjadi tanda yang membuat Anda berurusan dengan kesalahan jika Anda memasukkannya saat kompilasi (seperti strictNullChecks ).

Saran saya tidak ada di sini untuk menyelesaikan pengecualian yang tidak tertangani, kode yang Anda posting akan rusak sekarang tanpa fitur ini diimplementasikan, dan itu juga akan rusak di js.
Saran saya biar kamu sebagai developer lebih waspada dengan berbagai error yang mungkin dilontarkan, tetap terserah kamu mau menanganinya atau mengabaikannya.

Adapun masalah pembagian dengan 0, itu tidak menghasilkan kesalahan:

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

lebih sadar akan kesalahan berbeda yang mungkin terjadi

tidak ada gunanya melakukannya kecuali mereka dapat menanganinya, proposal saat ini tidak dapat dijalankan karena kasus yang saya daftarkan

Jadi Anda mengusulkan agar semua kesalahan harus ditangani seperti halnya dengan Java?

ya, ini artinya memeriksa pengecualian

@aleksey-bykov
Saya tidak mengerti mengapa salah satu kasus yang Anda daftarkan membuat proposal ini tidak dapat dipertahankan.

Tidak ada masalah dengan penanganan kesalahan yang dilemparkan ke bawah rantai doa, bahkan jika saya menggunakan fungsi yang disimpulkan melempar DivisionByZero (terlepas dari mana itu dilemparkan), saya dapat memilih untuk menanganinya .
Saya dapat mencoba mencobanya kembali dengan argumen yang berbeda, saya dapat menunjukkan kepada pengguna pesan bahwa ada yang tidak beres, saya dapat mencatat masalah ini sehingga saya dapat mengubah kode saya untuk menanganinya nanti (jika itu sering terjadi).

Sekali lagi, proposal ini tidak mengubah apa pun dalam waktu proses, jadi semua yang berfungsi akan terus berfungsi seperti sebelumnya.
Satu-satunya perbedaan adalah bahwa saya akan memiliki lebih banyak informasi tentang kesalahan yang mungkin terjadi.

saya mengerti apa yang Anda katakan, tidak ada yang akan berubah pada runtime javascript, namun pesan Anda di sini adalah untuk memberi pengguna ilusi bahwa mereka tahu apa yang mereka lakukan dengan menangani pengecualian yang berasal dari 20 lapisan di bawah dengan keyakinan yang sama seperti mereka akan menangani pengecualian langsung

tidak mungkin mereka dapat memperbaiki masalah yang terjadi 20 lapisan di bawah

Anda dapat mencatatnya, tentu saja, sama seperti pengecualian yang tidak dicentang, tetapi Anda tidak dapat memperbaikinya

jadi bohong secara umum, TS nya cukup bohong, biar gak bikin bingung orang lagi

@aleksey-bykov
Apa yang Anda gambarkan ada dalam semua bahasa yang mendukung pengecualian.
Tidak ada yang mengatakan bahwa menangkap pengecualian akan memperbaiki masalah, tetapi itu akan membuat Anda menanganinya dengan anggun.

Mengetahui kesalahan mana yang dapat terjadi saat menjalankan suatu fungsi akan membantu pengembang untuk memisahkan antara kesalahan yang dapat mereka tangani dan yang tidak dapat mereka tangani.

Saat ini pengembang mungkin tidak tahu bahwa menggunakan JSON.parse mungkin menimbulkan kesalahan, tetapi jika itu adalah bagian dari lib.d.ts dan IDE akan memberi tahu dia (misalnya) maka mungkin dia akan memilih untuk menangani kasus ini.

Anda tidak dapat menangani masalah yang terjadi 20 lapisan di bawah dengan anggun, karena keadaan internal rusak dalam 19 lapisan dan Anda tidak dapat pergi ke sana karena keadaan pribadi

agar konstruktif: apa yang saya sarankan adalah meminta pengguna menangani pengecualian yang diperiksa segera dan melemparkannya kembali secara eksplisit, dengan cara ini kami mengesampingkan kebingungan yang tidak diinginkan dan memisahkan pengecualian yang diperiksa dari yang tidak dicentang:

  • eksepsi yang diperiksa: terjadi dalam jangkauan langsung dan harus ditangani, dengan cara ini dijamin negara tidak korup dan aman untuk dilanjutkan
  • pengecualian tidak dicentang: terjadi dalam jangkauan langsung atau lebih jauh ke bawah, tidak dapat ditangani karena keadaan rusak, Anda dapat mencatatnya atau melanjutkan dengan risiko Anda sendiri

SyntaxError di JSON.parse harus dideklarasikan sebagai pengecualian yang dicentang

@aleksey-bykov

Saya tidak mengerti mengapa ada kebutuhan untuk memaksa pengembang melakukan sesuatu yang tidak mereka inginkan, sesuatu yang belum mereka lakukan sejauh ini.

Berikut ini contohnya:
Saya memiliki klien web di mana pengguna dapat menulis/menempelkan data json, lalu klik tombol.
Aplikasi mengambil input ini dan meneruskannya ke perpustakaan pihak ke-3 yang entah bagaimana mem-parsing json ini dan mengembalikan json bersama dengan berbagai jenis nilai (string, angka, boolean, array, dll).
Jika perpustakaan pihak ke-3 ini mengeluarkan SyntaxError Saya dapat memulihkan: beri tahu pengguna bahwa inputnya tidak valid dan dia harus mencoba lagi.

Dengan mengetahui kesalahan mana yang dapat terjadi saat menjalankan suatu fungsi, pengembang dapat memutuskan apa yang dapat/ingin ditangani dan apa yang tidak.
Seharusnya tidak masalah seberapa dalam rantai kesalahan itu dilemparkan.

lihat Anda sepertinya tidak mengerti apa yang saya katakan, kita berputar-putar

dengan membiarkan SyntaxError dilempar dari perpustakaan pihak ke-3, Anda mengekspos pengguna Anda ke detail implementasi kode Anda sendiri yang seharusnya dienkapsulasi

pada dasarnya Anda berkata, hei, bukan kode saya yang tidak berfungsi, itu perpustakaan bodoh yang saya temukan di internet dan gunakan, jadi jika Anda memiliki masalah dengan itu, tangani lib pihak ke-3 itu, bukan saya, saya hanya mengatakan apa yang saya diminta untuk

dan tidak ada jaminan bahwa Anda masih dapat menggunakan instance lib ke-3 itu setelah SyntaxError itu, Anda bertanggung jawab untuk memberikan jaminan kepada pengguna, katakanlah dengan mengaktifkan kembali kontrol pihak ke-3 setelah dilemparkan

intinya, Anda harus bertanggung jawab untuk menangani pengecualian dalam ( tidak semuanya, hanya yang dicentang, saya mohon )

Saya mengerti apa yang Anda katakan, tetapi saya tidak setuju dengan itu.
Anda benar, pada dasarnya itulah yang saya katakan.
Jika saya menggunakan perpustakaan pihak ke-3 yang menimbulkan kesalahan, saya dapat memilih untuk menanganinya atau mengabaikannya dan membiarkan pengguna kode saya menanganinya.
Ada banyak alasan untuk melakukannya, misalnya lib yang saya tulis adalah agnostik UI, jadi saya tidak dapat memberi tahu pengguna bahwa ada sesuatu yang salah, tetapi siapa pun yang menggunakan lib saya dapat menangani kesalahan yang dilemparkan saat menggunakan lib saya dan menanganinya dengan berinteraksi dengan pengguna.

Jika perpustakaan dibiarkan dengan status rusak saat dibuang, maka perpustakaan mungkin perlu mendokumentasikannya.
Jika saya kemudian menggunakan perpustakaan seperti itu dan sebagai akibatnya terjadi kesalahan di dalamnya, status saya menjadi rusak maka saya perlu mendokumentasikannya.

Intinya:
Saran ini datang untuk menawarkan lebih banyak informasi tentang kesalahan yang dilemparkan.
Seharusnya tidak memaksa pengembang untuk melakukan hal-hal yang berbeda, hanya membuatnya lebih mudah bagi mereka untuk menangani kesalahan jika mereka mau.

Anda bisa tidak setuju, tidak apa-apa, jangan sebut mereka pengecualian yang diperiksa, karena cara Anda mengatakannya bukan pengecualian yang diperiksa

sebut saja pengecualian yang terdaftar atau terungkap , karena yang Anda pedulikan hanyalah membuat pengembang mengetahuinya

@aleksey-bykov
Cukup adil, nama berubah.

@aleksey-bykov

Anda tidak dapat menangani masalah yang terjadi 20 lapisan di bawah dengan anggun, karena keadaan internal rusak dalam 19 lapisan dan Anda tidak dapat pergi ke sana karena keadaan pribadi

Tidak, Anda tidak dapat memperbaiki keadaan internal, tetapi tentu saja dapat memperbaiki keadaan lokal, dan itulah gunanya menanganinya di sini dan tidak lebih dalam di tumpukan.

Jika argumen Anda adalah bahwa tidak ada cara untuk memastikan status beberapa nilai yang dapat diubah bersama saat menangani pengecualian, maka itu adalah argumen yang menentang pemrograman imperatif, dan tidak terbatas pada proposal ini.

jika setiap lapisan terikat untuk bertanggung jawab untuk bereaksi terhadap pengecualian yang datang segera dari lapisan di bawahnya, ada peluang yang jauh lebih baik untuk pemulihan yang sukses, ini adalah ide di balik pengecualian yang diperiksa seperti yang saya lihat

dengan kata lain, pengecualian yang berasal dari lebih dari 1 level di bawah adalah sebuah kalimat, sudah terlambat untuk melakukan apa pun selain membuat ulang semua infrastruktur dari bawah ke atas (jika Anda cukup beruntung tidak ada sisa global yang Anda bisa' t mencapai)

proposal seperti yang dinyatakan sebagian besar tidak berguna, karena tidak ada cara yang dapat diandalkan untuk bereaksi terhadap pengetahuan tentang sesuatu yang buruk terjadi di luar jangkauan Anda

Ini bagus. FWIW: Saya pikir jika ditambahkan, itu harus diperlukan secara default untuk menangani metode melempar atau menandai metode Anda sebagai melempar juga. Kalau tidak, itu hanya dokumentasi.

@agonzalezjr
Saya pikir seperti kebanyakan fitur dalam TypeScript, Anda juga harus dapat ikut serta dengan fitur ini.
Sama seperti tidak wajib untuk menambahkan tipe, seharusnya tidak menjadi keharusan untuk melempar/menangkap.

Mungkin harus ada bendera untuk menjadikannya suatu keharusan, seperti --onlyCheckedExceptions .

Bagaimanapun, fitur ini juga akan digunakan untuk menyimpulkan/memvalidasi jenis pengecualian yang dilemparkan, jadi tidak hanya untuk dokumentasi.

@nitzantomer

Berikut ini contohnya:
Saya memiliki klien web di mana pengguna dapat menulis/menempelkan data json, lalu klik tombol.
Aplikasi mengambil input ini dan meneruskannya ke perpustakaan pihak ke-3 yang entah bagaimana mem-parsing json ini dan mengembalikan json bersama dengan berbagai jenis nilai (string, angka, boolean, array, dll).
Jika perpustakaan pihak ke-3 ini mengeluarkan SyntaxError yang dapat saya pulihkan: beri tahu pengguna bahwa inputnya tidak valid dan dia harus mencoba lagi.

Ini tentu saja merupakan satu area di mana seluruh gagasan tentang pengecualian yang diperiksa menjadi keruh. Di sinilah definisi _situasi luar biasa_ menjadi tidak jelas.
Program dalam contoh Anda akan menjadi argumen untuk JSON.parse dideklarasikan sebagai melempar pengecualian yang dicentang.
Tetapi bagaimana jika program tersebut adalah klien HTTP dan memanggil JSON.parse berdasarkan nilai header yang dilampirkan ke respons HTTP yang kebetulan berisi badan yang salah? Tidak ada yang berarti yang dapat dilakukan program untuk memulihkan, yang dapat dilakukan hanyalah rethrow.
Saya akan mengatakan bahwa ini adalah argumen terhadap JSON.parse yang dinyatakan sebagai dicentang.

Itu semua tergantung pada kasus penggunaan.

Saya mengerti bahwa Anda mengusulkan agar ini berada di bawah bendera tetapi mari kita bayangkan bahwa saya ingin menggunakan fitur ini jadi saya telah mengaktifkan bendera tersebut. Bergantung pada jenis program apa yang saya tulis, itu mungkin membantu atau menghalangi saya.

Bahkan java.io.FileNotFoundException klasik adalah contohnya. Sudah dicentang tetapi bisakah program pulih? Itu benar-benar tergantung pada apa arti file yang hilang bagi penelepon, bukan yang dipanggil.

@aluanhaddad

Saran ini tidak mengusulkan untuk menambahkan fungsionalitas baru, hanya untuk menambahkan cara untuk mengekspresikan dalam TypeScript sesuatu yang sudah ada di javascript.
Kesalahan dilempar, tetapi saat ini TypeScript tidak memiliki cara untuk menyatakannya (saat melempar atau menangkap).

Sebagai contoh Anda, dengan menangkap kesalahan, program dapat gagal "dengan baik" (misalnya menunjukkan kepada pengguna pesan "ada yang salah") dengan menangkap kesalahan ini, atau dapat mengabaikannya, tergantung pada program/pengembangnya.
Jika status program dapat dipengaruhi oleh kesalahan ini, maka penanganannya dapat mempertahankan status yang valid alih-alih yang rusak.

Bagaimanapun, pengembang harus membuat panggilan apakah dia dapat memulihkan dari kesalahan yang dilemparkan atau tidak.
Terserah dia juga untuk memutuskan apa artinya memulihkan, misalnya jika saya menulis klien http ini untuk digunakan sebagai perpustakaan pihak ke-3, saya mungkin ingin semua kesalahan yang dilemparkan dari perpustakaan saya memiliki jenis yang sama:

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

Sekarang, di perpustakaan saya ketika saya mengurai respons menggunakan JSON.parse Saya ingin menangkap kesalahan yang dilemparkan dan kemudian melemparkan kesalahan saya sendiri:

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

Jika fitur ini diimplementasikan maka akan mudah bagi saya untuk mendeklarasikan perilaku ini dan akan jelas bagi pengguna perpustakaan saya cara kerjanya dan kegagalannya.

Saran ini tidak mengusulkan untuk menambahkan fungsionalitas baru, hanya untuk menambahkan cara untuk mengekspresikan dalam TypeScript sesuatu yang sudah ada di javascript.

Aku tahu. Saya berbicara tentang kesalahan yang akan dipancarkan TypeScript di bawah proposal ini.
Asumsi saya adalah bahwa proposal ini menyiratkan perbedaan antara penentu pengecualian yang dicentang dan yang tidak dicentang (disimpulkan atau eksplisit), sekali lagi hanya untuk tujuan pengecekan tipe.

@aluanhaddad

Apa yang Anda katakan di komentar sebelumnya:

Tetapi bagaimana jika program tersebut adalah klien HTTP dan memanggil JSON.parse berdasarkan nilai header yang dilampirkan ke respons HTTP yang kebetulan berisi badan yang tidak sesuai? Tidak ada yang berarti yang dapat dilakukan program untuk memulihkan, yang dapat dilakukan hanyalah rethrow.

Berlaku hal yang sama untuk mengembalikan null ketika fungsi saya dideklarasikan untuk mengembalikan hasil.
Jika pengembang memilih untuk menggunakan strictNullChecks maka Anda dapat mengatakan hal yang sama persis jika fungsi mengembalikan null (alih-alih melempar) maka dalam skenario yang sama "tidak ada yang berarti yang dapat dilakukan program untuk memulihkan".

Tetapi bahkan tanpa menggunakan flag onlyCheckedExceptions fitur ini masih berguna karena compiler akan mengeluh misalnya jika saya mencoba untuk menangkap kesalahan sebagai string ketika fungsi dideklarasikan untuk membuang hanya Error .

Ide yang bagus, ini akan membantu tetapi tidak ketat/ketik aman karena tidak ada cara untuk mengetahui panggilan bersarang apa yang mungkin diberikan kepada Anda.

Artinya, jika saya memiliki fungsi yang mungkin mengeluarkan pengecualian tipe A, tetapi di dalamnya saya memanggil fungsi bersarang dan tidak memasukkannya ke dalam try catch - itu akan melemparkan pengecualian tipe B ke pemanggil saya.
Jadi, jika penelepon hanya mengharapkan pengecualian tipe A, tidak ada jaminan dia tidak akan mendapatkan tipe lain dari pengecualian bersarang.

(utasnya terlalu panjang jadi - maaf jika saya melewatkan komentar ini)

@shaipetel
Proposisi menyatakan bahwa kompiler akan menyimpulkan jenis kesalahan yang tidak tertangani dan akan menambahkannya ke tanda tangan fungsi/metode.
Jadi dalam kasus yang Anda jelaskan, fungsi Anda akan melempar A | B jika B tidak ditangani.

Oh begitu. Ini akan menelusuri semua metode yang saya panggil dan mengumpulkan semua kemungkinan jenis pengecualian?
Saya akan senang melihatnya terjadi, jika itu mungkin. Lihat, pengembang selalu dapat memiliki pengecualian tak terduga yang tidak akan dideklarasikan, dalam hal ini "objek tidak disetel ke instance" atau "dibagi dengan 0" atau pengecualian serupa selalu dimungkinkan hampir dari fungsi apa pun.
IMHO, itu akan lebih baik ditangani seperti di C # di mana semua pengecualian mewarisi dari kelas dasar yang memiliki pesan dan tidak mengizinkan sama sekali membuang teks yang tidak terbungkus atau objek lain. Jika Anda memiliki kelas dasar dan warisan, Anda dapat mengalirkan tangkapan Anda dan menangani kesalahan yang diharapkan di satu blok, dan yang tidak terduga lainnya di blok lain.

@shaipetel
Dalam javascript, semua kesalahan didasarkan pada kelas Error , tetapi Anda tidak dibatasi untuk melempar kesalahan, Anda dapat membuang apa pun:

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

Ya, saya tahu cara kerja JavaScript, kami membahas TypeScript yang menerapkan batasan lebih lanjut.
Saya menyarankan, solusi yang baik IMHO adalah membuat TypeScript mengikuti penanganan pengecualian yang mengharuskan semua pengecualian yang dilemparkan menjadi kelas dasar tertentu dan tidak mengizinkan melempar nilai yang belum dibuka secara langsung.

Jadi, itu tidak akan mengizinkan "lempar 0" atau "lempar 'beberapa kesalahan'".
Sama seperti JavaScript memungkinkan banyak hal yang tidak bisa dilakukan oleh TypeScript.

Terima kasih,

@nitzantomer

Berlaku hal yang sama untuk mengembalikan nol ketika fungsi saya dideklarasikan untuk mengembalikan hasil.
Jika pengembang memilih untuk menggunakan strictNullChecks maka Anda dapat mengatakan hal yang sama persis jika fungsi mengembalikan null (alih-alih melempar) maka dalam skenario yang sama "tidak ada yang berarti yang dapat dilakukan program untuk memulihkan".

Tetapi bahkan tanpa menggunakan flag onlyCheckedExceptions fitur ini tetap berguna karena compiler akan mengeluh misalnya jika saya mencoba menangkap kesalahan sebagai string ketika fungsi dideklarasikan hanya melempar Kesalahan.

Saya mengerti apa yang Anda katakan, itu masuk akal.

@shaipetel seperti yang dibahas sebelumnya dalam proposal ini dan di tempat lain, subkelas fungsi bawaan seperti Error dan Array tidak berfungsi. Ini mengarah pada perilaku yang berbeda pada waktu berjalan dan target kompilasi yang banyak digunakan.

Menangkap nilai dari berbagai jenis non-kesalahan adalah cara yang paling memungkinkan untuk membayangkan fungsionalitas yang diusulkan. Saya sebenarnya tidak melihat itu sebagai masalah karena mekanisme propagasi pengecualian yang memanfaatkan fitur ini kemungkinan akan menghasilkan kesalahan yang jauh lebih spesifik, dan jauh lebih bermanfaat, daripada mengatakan jejak tumpukan atau properti spesifik kesalahan lainnya.
Memperpanjang Error tidak dapat dilakukan. Itu tidak akan layak sampai semua target di bawah es2015 tidak lagi digunakan.

Jika proposal ini secara tidak langsung mengarah ke lebih banyak subkelas dari fungsi Error maka saya pikir itu adalah ide yang buruk. Keberatan semacam itu sepenuhnya terpisah dari keberatan filosofis apa pun seputar penggunaan pengecualian untuk aliran kontrol atau definisi keadaan luar biasa. Oleh karena itu jika proposal ini diadopsi, saya akan mengharapkan dokumentasi yang sangat keras dan langsung mengenai penggunaan yang benar dan perlunya menghindari subkelas Error .

Saya ingin ada semacam logika pembantu untuk menangani pengecualian yang diketik, saya memfaktorkan ulang banyak kode janji untuk menggunakan async/menunggu, yang saat ini terlihat seperti:

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

Kemudian di dunia baru terlihat seperti ini:

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

Tidak apa-apa tetapi membutuhkan lebih banyak kode dan sedikit kurang dapat dibaca dalam beberapa hal, jadi akan lebih bagus jika ada beberapa bentuk dukungan untuk:

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

Yang akan menampilkan bit sebelumnya, tetapi sebagai gula sintaksis, Anda bahkan dapat melakukannya sebagai obat generik tetapi sedikit lebih buruk:

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

@grofit
sementara saya ingin jika TypeScript akan mendukung apa yang Anda sarankan, menurut saya itu tidak terkait dengan masalah ini.
Apa yang Anda sarankan bahkan dapat diimplementasikan tanpa mengimplementasikan tentang apa masalah ini, hanya saja kompiler tidak akan dapat mengeluh (misalnya) bahwa NotFoundError tidak dilemparkan.

Saya pikir ini adalah saran untuk tangkapan yang diketik, bukan masalah apa pun, saya hanya tidak ingin menduplikasi utas, akan mempostingnya di masalahnya sendiri.

@grofit
Tangkapan yang diketik juga merupakan bagian dari permintaan fitur, tetapi juga kemampuan untuk memberi tahu kompiler jenis kesalahan apa yang dapat dibuang dari fungsi (dan kemudian kompiler juga akan dapat menyimpulkan jenis kesalahan apa yang dapat dibuang).

Menurut pendapat saya, tangkapan yang diketik dapat diimplementasikan tanpa bagian lain. Buka edisi baru, mungkin tim TS akan memutuskan untuk menandainya sebagai duplikat, saya tidak tahu.

@grofit

Yang akan menampilkan bit sebelumnya, tetapi sebagai gula sintaksis, Anda bahkan dapat melakukannya sebagai obat generik tetapi sedikit lebih buruk:

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

tidak akan berfungsi karena ini menyiratkan pembuatan kode hanya berdasarkan tipe sedangkan

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

melewati fungsi Kesalahan dalam setiap kasus dan kemungkinan diimplementasikan dengan instanceof . Kode seperti itu beracun karena mendorong perluasan Error yang merupakan hal yang sangat buruk.

@nitzantomer Saya setuju bahwa ini adalah masalah yang terpisah. OP memiliki contoh tipe penangkapan yang tidak diperluas Error .

@aluanhaddad
Untuk apa yang diminta @grofit, Anda dapat melakukan sesuatu seperti:

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

Dimana isXXX(error) adalah tipe guard berupa:
function isXXX(error): error is XXX { ... }

@nitzantomer yakin, tetapi tipe penjaga bukanlah masalah. Masalahnya adalah

class MyError extends Error {
  myErrorInfo: string;
}

yang bermasalah, tetapi sudah dibahas di sini. Saya tidak ingin menjelaskan maksudnya, tetapi banyak basis kode yang besar dan terkenal telah terkena dampak negatif dengan mengadopsi praktik yang buruk ini. Memperpanjang Error adalah ide yang buruk.

@aluanhaddad
Saya sadar akan hal itu, itulah sebabnya saya menawarkan cara untuk mencapai apa yang diminta @grofit tetapi tanpa perlu memperpanjang Error . Pengembang masih dapat memperpanjang Error jika dia mau, tetapi kompiler akan dapat menghasilkan kode js yang tidak perlu menggunakan instanceof

@nitzantomer Saya menyadari Anda sadar, tetapi saya tidak menyadari apa yang Anda sarankan kepada @grofit yang terdengar seperti ide yang bagus.

Saya hanya memukul kuda mati karena saya tidak suka harus berurusan dengan API yang ingin membuat saya menggunakan pola seperti itu. Apapun, saya minta maaf jika saya mengambil diskusi ini keluar dari topik.

Apakah ada pemikiran lagi dalam diskusi ini? Saya ingin melihat klausa throws dalam TypeScript.

@aluanhaddad Anda terus mengatakan bahwa memperpanjang Error adalah praktik yang buruk. Bisakah Anda menguraikan sedikit tentang ini?

Sudah dibahas panjang lebar. Pada dasarnya Anda tidak dapat memperluas tipe bawaan dengan andal. Perilaku bervariasi secara dramatis di seluruh kombinasi lingkungan dan pengaturan --target .

Satu-satunya alasan saya bertanya adalah karena komentar Anda adalah yang pertama saya dengar tentang ini. Setelah googling sebentar, saya hanya menemukan artikel yang menjelaskan bagaimana dan mengapa _should_ extend Error .

Tidak, itu tidak akan berhasil. Anda dapat membacanya secara detail di https://github.com/Microsoft/TypeScript/issues/12123 dan berbagai masalah terkait.

Ini akan berfungsi, tetapi hanya di "lingkungan tertentu", yang menjadi mayoritas saat ES6 sedang diadaptasi.

@nitzantomer : Apakah menurut Anda mungkin untuk menerapkan pemeriksaan TSLint yang memberi tahu pengembang ketika suatu fungsi dengan @throws dipanggil tanpa try/catch di sekitarnya?

@bennyn Saya pengguna TSLint, tapi saya tidak pernah mencoba membuat aturan baru.
Saya benar-benar tidak tahu apakah itu mungkin (walaupun saya kira itu), dan jika demikian, betapa mudahnya.

Jika Anda mencobanya, harap perbarui di sini, terima kasih.

@shaipetel (dan juga @nitzantomer)

Ulang:

Ya, saya tahu cara kerja JavaScript, kami membahas TypeScript yang menerapkan batasan lebih lanjut.
Saya menyarankan, solusi yang baik IMHO adalah membuat TypeScript mengikuti penanganan pengecualian yang mengharuskan semua pengecualian yang dilemparkan menjadi kelas dasar tertentu dan tidak mengizinkan melempar nilai yang belum dibuka secara langsung.
Jadi, itu tidak akan mengizinkan "lempar 0" atau "lempar 'beberapa kesalahan'".
Sama seperti JavaScript memungkinkan banyak hal yang tidak bisa dilakukan oleh TypeScript.

Tidak yakin apakah ini yang Anda sarankan @shaipetel tetapi untuk berjaga-jaga... Saya akan berhati-hati agar TypeScript membatasi throw hanya untuk mengembalikan Error s. Fitur Rendering Async React yang akan datang bekerja di bawah tenda dengan throw ing Promise s! (Kedengarannya aneh, saya tahu... tapi saya pernah membaca bahwa mereka telah mengevaluasi ini versus async / await dan yield / yield* generator untuk kasus penggunaan mereka dan saya yakin tahu apa yang mereka lakukan!)

throw ing Error s adalah saya yakin 99% dari kasus penggunaan di luar sana, tapi tidak 100%. Saya tidak berpikir TypeScript harus membatasi pengguna untuk hanya throw ing hal-hal yang memperluas kelas dasar Error . Ini tentu jauh lebih maju, tetapi throw memiliki kasus penggunaan lain selain kesalahan/pengecualian.

@mikeleonard
Saya setuju, ada banyak contoh kode js yang ada yang menampilkan berbagai jenis (saya telah melihat banyak throw "error message string" ).
Fitur React baru adalah contoh bagus lainnya.

Hanya dua sen saya, saya mengonversi kode menjadi async/menunggu dan jadi saya menjadi sasaran lemparan (sebagai tambahan, saya benci pengecualian). Memiliki klausa lemparan saat masalah ini membahas akan menyenangkan menurut saya. Saya pikir itu akan berguna juga bahkan jika "melempar apa pun" diizinkan. (Juga, mungkin opsi "nothrows" dan kompiler yang menetapkan default ke "nothrows".)

Sepertinya ekstensi alami untuk memungkinkan pengetikan apa yang dilakukan oleh suatu fungsi. Nilai pengembalian dapat secara opsional diketik dalam TS, dan bagi saya rasanya lemparan hanyalah jenis pengembalian lainnya (sebagaimana dibuktikan oleh semua alternatif yang disarankan untuk menghindari lemparan, seperti https://stackoverflow.com/a/39209039/162530).

(Secara pribadi, saya juga ingin memiliki opsi kompiler (opsional) untuk menegakkan bahwa setiap pemanggil ke fungsi yang dideklarasikan sebagai lemparan harus juga mendeklarasikan sebagai lemparan atau menangkapnya.)

Kasus penggunaan saya saat ini: Saya tidak ingin mengonversi seluruh basis kode [Angular] saya untuk menggunakan pengecualian untuk penanganan kesalahan (mengingat saya membencinya). Saya menggunakan async/menunggu dalam detail implementasi API saya, tetapi mengonversi throw menjadi Janji/Dapat Diamati normal saat API kembali. Akan menyenangkan jika kompiler memeriksa bahwa saya menangkap hal-hal yang benar (atau menangkapnya sama sekali, idealnya).

@aleksey-bykov Jangan ubah sifat JavaScript, cukup tambahkan pengetikan ke dalamnya. :)

menambahkan pengetikan sudah mengubahnya (memotong kode yang tidak masuk akal),
dengan cara yang sama kita dapat memperketat penanganan pengecualian

Pada Kamis, 26 Juli 2018, 20:22 Joe Pea [email protected] menulis:

@aleksey-bykov https://github.com/aleksey-bykov Jangan ubah
sifat JavaScript, mari kita tambahkan mengetik saja. :)


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/Microsoft/TypeScript/issues/13219#issuecomment-408274156 ,
atau matikan utasnya
https://github.com/notifications/unsubscribe-auth/AA5PzfUNS5E093Z74WA4WCUaTyWRRZC3ks5uKl1FgaJpZM4LXwLC
.

Sebagai janji, jika Anda menggunakan then chaining alih-alih async/await dan menghindari penggunaan throw , sebenarnya semuanya berfungsi dengan sangat baik. Berikut adalah bukti sketsa konsep:

https://bit.ly/2NQZD8i - tautan taman bermain

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

Tangkapan predikat menghapus tipe dari gabungan, sementara mengembalikan reject() menambahkannya. Satu-satunya hal yang tidak berhasil adalah inferensi tipe parameter serikat pekerja (mungkin bug)

Masalah yang dibahas dalam contoh di atas:

  • itu tidak terbatas pada kesalahan, Anda dapat menolak dengan apa pun (walaupun mungkin ide yang buruk)
  • itu tidak terbatas pada penggunaan instanceof, Anda dapat menggunakan penjaga jenis predikat apa pun untuk menghapus kesalahan dari serikat pekerja

Masalah utama yang saya lihat menggunakan ini untuk semua kode adalah tanda tangan panggilan balik, untuk membuatnya bekerja dengan cara yang kompatibel, "tipe pengembalian" default adalah "might-throw-any", dan jika Anda ingin membatasi, Anda akan mengatakan throws X atau throws never jika tidak, mis

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

Versi tanpa tanda tangan:

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

sebenarnya akan default ke

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

untuk menjamin bahwa semua kode saat ini dikompilasi.

Tidak seperti strictNullChecks , di mana nilai pengembalian nol sangat jarang, saya pikir pengecualian di JS cukup meresap. Memodelkannya dalam file .d.ts mungkin tidak terlalu buruk (mengimpor jenis dari dependensi untuk menjelaskan kesalahan Anda), tetapi ini pasti akan menjadi upaya yang tidak sepele dan akan menghasilkan serikat pekerja yang besar.

Saya pikir jalan tengah yang baik adalah fokus pada Promises dan async/await , karena Promise sudah menjadi pembungkus, dan kode async adalah tempat penanganan kesalahan paling banyak bercabang dalam skenario tipikal. Kesalahan lain akan "tidak dicentang"

@spion-h4 apakah ini (sudah/akan) dapat digunakan pada TypeScript?

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

@bluelovers
tidak, TypeScript saat ini tidak mendukung kata kunci throws , inilah masalahnya.

Sesuatu yang perlu diperhatikan (bukan berarti itu harus memblokir proposal ini) adalah bahwa tanpa kelas nominal, kesalahan pengetikan bawaan masih tidak terlalu berguna karena semuanya secara struktural sama.

misalnya:

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

@Jamesernator
Masalah ini tidak ada hubungannya dengan proposal.
Anda dapat mengalami masalah yang sama persis dengan melakukan ini:

class BaseClass {
    propA!: string;
}

class MyClass1 extends BaseClass { }

class MyClass2 extends BaseClass { }

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

Ini adalah batasan bahasa yang memengaruhi banyak kasus penggunaan.

Ada bacaan bagus oleh @ahejlsberg tentang pengecualian yang diketik: https://www.artima.com/intv/handcuffs.html

Saya percaya TypeScript berada dalam posisi yang baik untuk menghindari masalah ini. TypeScript adalah tentang solusi _pragmatis_ untuk masalah _dunia nyata_. Dalam pengalaman saya, dalam basis kode JavaScript dan TypeScript yang besar, penanganan kesalahan adalah salah satu masalah terbesar - melihat kesalahan mana yang berfungsi _might_ throw dan saya _might_ ingin tangani sangatlah sulit. Itu hanya mungkin melalui membaca & menulis dokumentasi yang baik (kita semua tahu seberapa baik kita dalam hal ini / s) atau melihat implementasinya, dan karena berlawanan dengan mengembalikan nilai, pengecualian hanya menyebar melalui panggilan fungsi secara otomatis, itu tidak cukup hanya periksa fungsi yang dipanggil secara langsung, tetapi untuk menangkap semuanya, Anda harus memeriksa panggilan fungsi bersarang juga. _Ini adalah masalah dunia nyata_.

Kesalahan dalam JavaScript sebenarnya adalah nilai yang sangat berguna. Banyak API di NodeJS membuang objek kesalahan terperinci, yang memiliki kode kesalahan yang terdefinisi dengan baik dan mengekspos metadata yang berguna. Misalnya, kesalahan dari child_process.execFile() memiliki properti seperti exitCode dan stderr , kesalahan dari fs.readFile() memiliki kode kesalahan seperti ENOENT (file tidak ditemukan ) atau EPERM (izin tidak memadai). Saya tahu banyak perpustakaan yang melakukan ini juga, misalnya driver database pg memberi Anda metadata yang cukup tentang kesalahan untuk mengetahui batasan kolom mana yang menyebabkan INSERT gagal.

Anda dapat melihat jumlah pemeriksaan regex rapuh yang mengkhawatirkan pada pesan kesalahan dalam basis kode karena orang tidak menyadari bahwa kesalahan memiliki kode kesalahan yang tepat dan apa adanya.

Jika kita dapat mendefinisikan dalam @types deklarasi dan lib.d.ts kesalahan apa yang dapat ditimbulkan oleh fungsi-fungsi ini, TypeScript akan memiliki kekuatan untuk membantu kita dengan struktur kesalahan - kesalahan apa yang mungkin terjadi, apa nilai kode kesalahan adalah, properti apa yang mereka miliki. Ini _not_ tentang pengecualian yang diketik dan karenanya sepenuhnya menghindari semua masalah dengan pengecualian yang diketik. Ini adalah _solusi pragmatis_ untuk _masalah dunia nyata_ (berlawanan dengan menyuruh orang untuk menggunakan nilai pengembalian kesalahan monadik sebagai gantinya - realitas tanah JavaScript terlihat berbeda, fungsi membuang kesalahan).

Anotasi dapat sepenuhnya opsional (atau dibuat diperlukan dengan flag compiler). Jika tidak ditentukan dalam file deklarasi, suatu fungsi hanya melempar any (atau unknown ).
Suatu fungsi dapat secara manual mendeklarasikan untuk tidak pernah melempar dengan throws never .
Jika implementasinya tersedia, sebuah fungsi melempar gabungan semua tipe pengecualian dari fungsi yang dipanggilnya dan pernyataan throw miliknya sendiri yang tidak berada di dalam blok try dengan catch klausa.
Jika salah satu dari mereka melempar any , fungsi juga melempar any - tidak apa-apa, pada setiap batas fungsi dev memiliki kesempatan untuk memperbaikinya melalui anotasi eksplisit.
Dan dalam banyak kasus, di mana satu fungsi terkenal dipanggil dan dibungkus dengan try/catch (misalnya membaca file dan menanganinya tidak ditemukan), TypeScript kemudian dapat menyimpulkan tipe dalam klausa catch.

Kita tidak memerlukan subklasifikasi Error untuk ini atau instanceof - tipe error dapat berupa antarmuka, yang menentukan kode error dengan literal string, dan TypeScript dapat membedakan gabungan pada kode error, atau menggunakan pelindung tipe.

Mendefinisikan jenis kesalahan

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

Selain kesalahan, apakah mungkin untuk menandai jenis efek samping lainnya seperti

  • Divergensi (loop tak terbatas / tidak kembali)
  • saya
  • Non-determinisme ( Math.random )

Ini mengingatkan saya Koka dari MSR yang dapat menandai efek pada tipe yang dikembalikan.

Proposal:

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

Saya suka ide deklarasi tipe kesalahan! Ini akan sangat membantu orang yang membutuhkannya dan tidak akan melakukan hal buruk bagi orang yang tidak menyukainya. Saya sekarang memiliki masalah dalam proyek simpul saya, yang dapat diselesaikan lebih cepat dengan fitur ini. Saya harus menangkap kesalahan dan mengirim kembali kode http yang tepat - untuk itu saya harus selalu memeriksa semua panggilan fungsi untuk mengetahui pengecualian yang harus saya tangani - dan ini tidak menyenangkan -_-

PS. Sintaks di https://github.com/Microsoft/TypeScript/issues/13219#issuecomment -428696412 terlihat sangat bagus - &! dan membungkus jenis kesalahan dalam <> sungguh menakjubkan.

Saya juga mendukung ini. Sekarang semua kesalahan tidak diketik dan itu cukup mengganggu. Meskipun saya berharap sebagian besar throws dapat diturunkan dari kode karena kita harus menulisnya di mana-mana.

Dengan cara ini orang bahkan dapat menulis aturan tslint yang memaksa pengembang untuk menangkap kesalahan tidak ramah pengguna pada titik akhir istirahat, dll.

Saya sebenarnya terkejut bahwa fungsi ini tidak _sudah_ di dalam TypeScript. Salah satu hal pertama yang saya pergi untuk menyatakan. Tidak apa-apa jika kompilator melakukan atau tidak memberlakukannya, selama Anda bisa mendapatkan informasi bahwa akan ada kesalahan yang dilemparkan, dan jenis kesalahan apa yang akan dilemparkan.

Bahkan jika kita tidak mendapatkan &! dan hanya mendapatkan Throws<T> yang akan menjadi :+1:

Inilah yang ada dalam pikiran saya, akan jauh lebih indah jika TypeScript mendukung klausa ini.

Fitur lain yang mungkin adalah entah bagaimana memaksa (mungkin pada tingkat fungsi, atau pada tingkat modul) strictExceptionTracking - yang berarti Anda harus memanggil apa pun yang dideklarasikan sebagai throws X dengan ekspresi try! invokeSomething() seperti di Rust dan cepat.

Itu hanya akan dikompilasi ke invokeSomething() tetapi kemungkinan pengecualian akan terlihat dalam kode, membuatnya lebih mudah dikenali jika Anda meninggalkan sesuatu yang bisa berubah dalam keadaan sementara yang buruk (atau membiarkan sumber daya yang dialokasikan tidak dibuang)

@be5invis

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

Saya hanya memberikan dua sen saya di sini tetapi &! terlihat sangat jelek bagi saya.
Saya pikir orang yang baru mengenal TypeScript akan terkesima dengan simbol ini, itu benar-benar tidak menarik.
throws sederhana lebih eksplisit, intuitif dan sederhana, menurut saya.

Selain itu, +1 untuk pengecualian yang diketik. 👍

Saya ingin menambahkan +1 saya untuk pengecualian yang diketik dan diperiksa.
Saya telah memanfaatkan pengecualian yang diketik dengan baik dalam kode saya. Saya sepenuhnya menyadari jebakan instanceof , saya sebenarnya telah menggunakannya untuk keuntungan saya dengan dapat menulis penangan umum untuk kesalahan terkait yang diturunkan dari kelas dasar umum. Sebagian besar metode lain yang saya temui untuk menghindari pewarisan dari kelas Kesalahan dasar akhirnya menjadi ( setidaknya ) sama-sama rumit dan bermasalah dengan cara yang berbeda.
Pengecualian yang diperiksa adalah peningkatan yang menurut saya cocok untuk wawasan analisis statis yang lebih besar ke dalam basis kode. Dalam basis kode dengan kompleksitas yang cukup, mungkin mudah untuk melewatkan pengecualian apa yang dilemparkan oleh fungsi tertentu.

Bagi mereka yang mencari keamanan kesalahan waktu kompilasi dalam TypeScript, Anda dapat menggunakan perpustakaan ts-results saya.

@vultix bagi saya sayangnya, pemisahannya tidak cukup jelas untuk memasukkan ini ke dalam pengaturan tim.

@vultix Pendekatan lib Anda telah dibahas di atas, dan ini adalah kebalikan dari apa yang harus dipecahkan oleh fitur ini.
Javascript sudah memiliki mekanisme untuk menangani kesalahan, itu disebut pengecualian, tetapi TypeScript tidak memiliki cara untuk menggambarkannya.

@nitzantomer Saya sepenuhnya setuju bahwa fitur ini merupakan kebutuhan untuk TypeScript. Perpustakaan saya tidak lebih dari stand-in sementara sampai fitur ini ditambahkan.

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

Saya tahu saya sedikit terlambat untuk permainan ini, tetapi di bawah ini tampaknya sedikit lebih banyak sintaks TypeScripty daripada koma.

Iterable<number> throws TypeError | RangeError

Tapi saya masih baik-baik saja dengan koma .. Saya hanya berharap kami memiliki ini dalam bahasa.
Yang utama adalah saya akan menyukainya di JSON.parse() karena banyak rekan kerja saya tampaknya lupa bahwa JSON.parse dapat menimbulkan kesalahan dan itu akan menghemat banyak bolak-balik dengan tarik permintaan.

@WORMSS
Saya sangat setuju.
Proposal saya menyertakan sintaks yang Anda rekomendasikan:

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

Bisakah kita mengikuti pola deklarasi kesalahan bahasa OOP seperti Java? Kata kunci "throws" digunakan untuk menyatakan bahwa kita perlu "try..catch" saat menggunakan fungsi dengan potensi kesalahan

@allicanseenow
Proposal adalah untuk penggunaan opsional dari klausa throws.
Artinya, Anda tidak perlu menggunakan try/catch jika Anda menggunakan fungsi yang melempar.

@allicanseenow Anda mungkin ingin membaca artikel saya di atas untuk konteks, termasuk artikel tertaut: https://github.com/microsoft/TypeScript/issues/13219#issuecomment -416001890

Saya pikir ini adalah salah satu hal yang hilang dalam sistem tipe TypeScript.
Ini murni opsional, tidak mengubah kode yang dipancarkan, membantu bekerja dengan perpustakaan dan fungsi asli.

Juga mengetahui kesalahan apa yang mungkin terjadi dapat memungkinkan editor untuk "melengkapi otomatis" klausa tangkapan dengan kondisi if untuk menangani semua kesalahan.

Maksud saya, bahkan aplikasi kecil pun layak mendapatkan penanganan kesalahan yang tepat - hal terburuknya sekarang adalah, pengguna tidak tahu kapan ada sesuatu yang salah.

Bagi saya, ini adalah fitur TOP yang hilang sekarang.

@RyanCavanaugh , ada banyak umpan balik sejak label "Menunggu Lebih Banyak Umpan Balik" ditambahkan. Bisakah anggota tim TypeScript menimbang?

@nitzantomer Saya mendukung gagasan untuk menambahkan lemparan ke TypeScript, tetapi bukankah try/catch opsional memungkinkan deklarasi lemparan yang berpotensi tidak akurat saat digunakan dalam fungsi bersarang?

Agar pengembang percaya bahwa klausa lemparan metode akurat, mereka harus membuat asumsi berbahaya bahwa tidak ada titik dalam hierarki panggilan metode itu yang merupakan pengecualian opsional yang diabaikan dan dibuang ke tumpukan tanpa dilaporkan. Saya pikir @ajxs mungkin telah menyinggungnya di akhir komentarnya, tetapi saya pikir ini akan menjadi masalah besar. Terutama dengan betapa terfragmentasinya sebagian besar perpustakaan npm.

@ConnorSinnott
Saya harap saya mengerti Anda dengan benar:

Kompiler akan menyimpulkan tipe yang dilempar, misalnya:

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

Sebenarnya akan menjadi function fn(): throws string { ... } .
Kompiler juga akan error ketika ada ketidakcocokan antara error yang dideklarasikan dan yang sebenarnya:

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

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

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

Kompiler harus mengeluh bahwa fn2 melempar string | MyError dan bukan string .

Dengan semua ini dalam pikiran, saya tidak melihat bagaimana asumsi ini lebih berbahaya daripada asumsi bahwa tipe lain yang dideklarasikan yang dipercaya pengembang saat menggunakan perpustakaan lain, kerangka kerja, dll.
Saya tidak yakin saya benar-benar membahas semua opsi, dan saya akan senang jika Anda dapat membuat skenario yang menarik.

Dan bahkan dengan masalah ini, itu hampir sama seperti sekarang:

// 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) { ... }
    }
}

Kita dapat meminta TypeScript menjaga kita bahkan dari ini, tetapi itu akan membutuhkan sintaks yang sedikit berbeda dari javascript:

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

Ini akan dikompilasi ke:

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

Hai! Terima kasih telah menanggapi! Sebenarnya, menyimpulkan lemparan seperti yang Anda sebutkan mengurangi masalah yang saya pikirkan. Tapi contoh kedua yang Anda sebutkan menarik.

Diberikan

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

Bagi saya untuk secara eksplisit mendeklarasikan MyVeryImportantError, saya juga harus secara eksplisit mendeklarasikan semua kesalahan tambahan dari callstack, yang mungkin sedikit tergantung pada kedalaman aplikasi. Juga, berpotensi membosankan. Saya tentu tidak ingin menyelami seluruh rantai panggilan untuk menghasilkan daftar kesalahan potensial yang dapat muncul dalam perjalanan, tetapi mungkin IDE dapat membantu.

Saya sedang berpikir untuk mengusulkan semacam operator spread untuk memungkinkan pengembang secara eksplisit menyatakan kesalahan mereka dan membuang sisanya.

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

Tetapi akan ada cara yang lebih mudah untuk mencapai hasil yang sama: cukup jatuhkan deklarasi lemparan.

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

Yang menimbulkan pertanyaan: kapan saya akan melihat manfaat menggunakan kata kunci throws daripada hanya membiarkan TypeScript menyimpulkan kesalahan?

@ConnorSinnott

Tidak berbeda dengan bahasa lain yang menggunakan klausa throw, yaitu java.
Tetapi saya pikir dalam banyak kasus Anda tidak perlu berurusan dengan banyak jenis. Di dunia nyata Anda biasanya hanya berurusan dengan string , Error (dan subkelas) dan objek yang berbeda ( {} ).
Dan dalam kebanyakan kasus, Anda hanya dapat menggunakan kelas induk yang menangkap lebih dari satu jenis:

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

Adapun ketika secara implisit menggunakan klausa throw vs. meminta kompiler menyimpulkannya, saya pikir itu seperti mendeklarasikan tipe dalam TypeScript, dalam banyak kasus Anda tidak harus melakukannya, tetapi Anda dapat melakukannya untuk mendokumentasikan kode Anda dengan lebih baik sehingga siapa pun yang membacanya nanti dapat lebih memahaminya.

Pendekatan lain jika Anda ingin keamanan tipe adalah dengan memperlakukan kesalahan sebagai nilai ala Golang:
https://Gist.github.com/brandonkal/06c4a9c630369979c6038fa363ec6c83
Namun, ini akan menjadi fitur yang bagus untuk dimiliki.

@brandonkal
Pendekatan itu sudah dibahas sebelumnya di utas.
Itu mungkin, tetapi melakukannya adalah mengabaikan bagian/fitur bawaan yang diizinkan oleh javascript.

Saya juga berpikir bahwa specifier noexcept (#36075) adalah solusi paling sederhana dan terbaik saat ini, karena bagi sebagian besar programmer, melempar pengecualian dianggap sebagai anti-pola.

Berikut adalah beberapa fitur keren:

  1. Memverifikasi fungsi tidak akan menimbulkan kesalahan:
noexcept function foo(): void {
  throw new Error('Some exception'); // <- compile error!
}
  1. Peringatan tentang blok try..catch yang berlebihan:
try { // <- warning!
  foo();
} catch (e) {}
  1. Putuskan selalu Janji:
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`
}

@moshest

seperti untuk kebanyakan programmer, melempar pengecualian dianggap sebagai anti-pola.

Saya kira saya bukan bagian dari kelompok "kebanyakan programmer".
noexcept itu keren dan bukan itu masalahnya.

Jika alatnya ada, saya akan menggunakannya. throw , try/catch dan reject ada di sana, jadi saya akan menggunakannya.

Akan lebih baik jika mereka diketik dengan benar di TypeScript.

Jika alatnya ada, saya akan menggunakannya. throw , try/catch dan reject ada di sana, jadi saya akan menggunakannya.

Tentu. Saya akan menggunakannya juga. Maksud saya adalah, noexcept atau throws never akan menjadi awal yang baik untuk masalah ini.

Contoh bodoh: Saya ingin tahu bahwa jika saya akan memanggil Math.sqrt(-2) itu tidak akan pernah menimbulkan kesalahan.

Hal yang sama berlaku untuk perpustakaan pihak ketiga. Ini memberi lebih banyak kontrol pada kode dan kasus tepi yang harus saya tangani sebagai seorang programmer.

@moshest
Tapi kenapa?
Proposal ini mencakup semua manfaat dari proposal 'tidak diharapkan', jadi mengapa menerima lebih sedikit?

Karena butuh waktu lama (masalah ini berumur 3 tahun), dan saya berharap setidaknya kita bisa memulai dengan fitur paling dasar terlebih dahulu.

@moshest
Yah, saya lebih suka fitur yang lebih baik yang membutuhkan waktu lebih lama daripada solusi parsial yang akan memakan waktu lebih sedikit tetapi kami akan tetap menggunakannya selamanya.

Saya merasa noexcept dapat dengan mudah ditambahkan nanti, bahkan itu bisa menjadi topik yang terpisah.

noexcept sama dengan throws never .

TBH Saya pikir lebih penting untuk memiliki beberapa mekanisme yang menjamin penanganan pengecualian (ala tidak digunakan error di Go) daripada memberikan petunjuk jenis untuk try/catch

@roll seharusnya tidak ada "jaminan".
itu bukan keharusan untuk menangani pengecualian dalam javascript dan itu juga tidak harus menjadi keharusan dalam TypeScript.

Ini bisa menjadi opsi sebagai bagian dari mode ketat typescrypt bahwa suatu fungsi harus menangkap kesalahan atau secara eksplisit mendeklarasikannya throws dan meneruskannya

Untuk menambahkan 5 sen saya: Saya melihat pengecualian yang mirip dengan fungsi yang mengembalikan null : Ini adalah hal yang biasanya tidak Anda harapkan terjadi. Tetapi jika itu terjadi kesalahan run-time terjadi, yang buruk. Sekarang, TS menambahkan "tipe non-nullable" untuk mengingatkan Anda menangani null . Saya melihat menambahkan throws sebagai upaya ini selangkah lebih maju, dengan mengingatkan Anda untuk menangani pengecualian juga. Karena itulah menurut saya fitur ini sangat dibutuhkan.

Jika Anda melihat Go, yang mengembalikan kesalahan alih-alih membuangnya, Anda dapat melihat lebih jelas bahwa kedua konsep tersebut tidak jauh berbeda. Selain itu, ini membantu Anda memahami beberapa API lebih dalam. Mungkin Anda tidak tahu bahwa beberapa fungsi dapat dilempar dan Anda menyadarinya jauh di kemudian hari dalam produksi (misalnya JSON.parse , siapa yang tahu ada berapa banyak lagi?).

@ obedm503 Ini sebenarnya adalah filosofi TS: secara default kompiler tidak mengeluh tentang apa pun. Anda dapat mengaktifkan opsi untuk memperlakukan hal-hal tertentu sebagai kesalahan atau mengaktifkan mode ketat untuk mengaktifkan semua opsi sekaligus. Jadi, ini harus diberikan.

Saya suka fitur yang disarankan ini.
Pengetikan pengecualian dan inferensi dapat menjadi salah satu hal terbaik di seluruh sejarah pemrograman.
❤️

Halo, Saya hanya menghabiskan sedikit waktu mencoba mencari solusi dengan apa yang kami miliki saat ini di TS. Karena tidak ada cara untuk mendapatkan jenis kesalahan yang dilemparkan dalam ruang lingkup fungsi (atau untuk mendapatkan jenis metode saat ini), saya menemukan bagaimana kita dapat secara eksplisit mengatur kesalahan yang diharapkan dalam metode itu sendiri. Kita nanti bisa, mengambil tipe itu dan setidaknya tahu apa yang bisa dilemparkan ke dalam metode.

Ini POC saya

/***********************************************
 ** 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)

Terima kasih banyak atas tanggapan positif Anda! Saya menyadari akan lebih mudah untuk memfaktorkan ulang kode yang ada tanpa harus membungkus seluruh tipe yang dikembalikan ke dalam tipe throwable . Alih-alih, kami hanya dapat menambahkan yang itu ke tipe yang dikembalikan, kode berikut memungkinkan Anda menambahkan kesalahan yang dapat dibuang sebagai berikut:

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

Ini adalah satu-satunya perubahan untuk bagian contoh, berikut adalah deklarasi tipe baru:

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

Saya juga menambahkan tipe baru exceptionsOf yang memungkinkan untuk mengekstrak kesalahan suatu fungsi untuk meningkatkan tanggung jawab. Misalnya:

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

Karena exceptionsOf mendapatkan gabungan kesalahan, Anda dapat mengeskalasi metode kritis sebanyak yang Anda inginkan:

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

Saya tidak suka penggunaan typeof , jika saya menemukan cara yang lebih baik, saya akan memberi tahu Anda

Anda dapat menguji di sini tips hasil : arahkan typedError di baris 106

@Xample Itu solusi hebat dengan alat yang kami miliki sekarang!
Tapi saya rasa dalam prakteknya itu tidak cukup karena Anda masih bisa melakukan hal-hal seperti:

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

Dan tipe b disimpulkan sebagai angka yang benar tetapi hanya jika dibungkus dengan try
Ini seharusnya tidak valid

@luisgurmendezMLabs Saya tidak mengerti maksud Anda. Jika Anda mengikuti repo @Xample pada baris 56. Anda dapat melihat hasil myNumber + 23 disimpulkan sebagai number , sedangkan myNumber: number & extraType<Error | CustomError> .

Di fase lain, a dalam contoh Anda tidak pernah dibungkus dengan sesuatu seperti Try Monad. Itu tidak memiliki pembungkus sama sekali selain persimpangan dengan & extraType<Error | CustomError> .

Selamat atas desain yang mengagumkan @Xample . Ini benar-benar menjanjikan, dan sudah berguna bahkan tanpa gula sintaksis. Apakah Anda punya rencana untuk membangun perpustakaan tipe untuk ini?

@ivawzh Pikiran saya adalah bahwa dalam praktiknya ini mungkin membawa beberapa masalah karena:

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

Pengembalian tipe fungsi itu disimpulkan sebagai angka dan itu tidak sepenuhnya benar

@luisgurmendezMLabs tipe yang dapat dibuang berada dalam tipe pengembalian fungsi hanya sebagai cara untuk menempelkan tipe kesalahan dengan fungsi (dan untuk dapat memulihkan tipe tersebut nanti). Saya tidak pernah mengembalikan kesalahan secara nyata, saya hanya membuangnya. Karena itu, jika Anda berada dalam blok coba atau tidak, tidak akan mengubah apa pun.

@ivawzh terima kasih telah bertanya, saya baru saja melakukannya untuk Anda

@luisgurmendezMLabs ah ok saya mengerti maksud Anda, sepertinya TypeScript hanya menyimpulkan tipe pertama yang ditemukan. Misalnya jika Anda memiliki:

function stillARiskyMethod() { 
    return superRiskyMethod();
}

tipe kembalian stillARiskyMethod akan disimpulkan dengan benar, sementara

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

hanya akan mengetikkan tipe pengembalian sebagai superRiskyMethod() dan mengabaikan salah satu dari anotherSuperRiskyMethod() . Saya belum menyelidiki lebih lanjut

Untuk alasan ini, Anda perlu mengeskalasi jenis kesalahan secara manual.

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

Saya juga hanya ingin menyampaikan pemikiran saya tentang ini.

Saya pikir ini akan menjadi fitur yang sangat bagus untuk diterapkan karena ada kasus penggunaan yang berbeda untuk ingin mengembalikan Kesalahan/null dan ingin melempar sesuatu dan itu bisa memiliki banyak potensi. Melempar pengecualian adalah bagian dari bahasa Javascript, jadi mengapa kita tidak memberikan opsi untuk mengetik dan menyimpulkannya?

Misalnya, jika saya melakukan banyak tugas yang dapat membuat kesalahan, saya akan merasa tidak nyaman jika harus menggunakan pernyataan if untuk memeriksa jenis pengembalian setiap kali, ketika ini dapat disederhanakan dengan menggunakan try/catch di mana jika ada salah satu lemparan tugas ini, itu akan ditangani di tangkapan tanpa kode tambahan apa pun.

Ini sangat berguna ketika Anda akan menangani kesalahan dengan cara yang sama ; misalnya di express/node.js, saya mungkin ingin meneruskan kesalahan ke NextFunction (Penangan kesalahan).

Daripada melakukan if (result instanceof Error) { next(result); } setiap kali, saya bisa membungkus semua kode untuk tugas-tugas ini dalam coba/tangkap, dan dalam tangkapan saya, saya tahu karena pengecualian dilemparkan, saya selalu ingin meneruskan ini ke penangan kesalahan, begitu juga catch(error) { next(error); }

Juga belum melihat ini dibahas (Mungkin telah melewatkannya, utas ini memiliki beberapa komentar!) tetapi jika ini diterapkan, apakah akan diwajibkan (yaitu: Kesalahan kompilasi) untuk memiliki fungsi yang melempar tanpa menggunakan throws klausa dalam deklarasi fungsinya? Saya merasa ini akan menyenangkan untuk dilakukan (Kami tidak memaksa orang untuk menangani lemparan, itu hanya akan memberi tahu mereka bahwa fungsinya memang melempar) tetapi kekhawatiran besar di sini adalah jika TypeScript diperbarui dengan cara ini, kemungkinan akan memecahkan banyak kode yang ada saat ini.

Sunting: Kasus penggunaan lain yang saya pikirkan adalah ini juga akan membantu pembuatan JSDocs menggunakan tag @throws

Saya akan mengulangi di sini apa yang saya katakan dalam masalah yang sekarang berkomitmen.


Saya pikir TypeScript harus dapat menyimpulkan jenis kesalahan sebagian besar ekspresi JavaScript. Memungkinkan implementasi yang lebih cepat oleh pembuat perpustakaan.

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();
}
  • Fungsi a kita tahu bahwa jenis kesalahannya adalah 'unlucky' , dan jika kita ingin sangat berhati-hati, kita dapat memperluasnya ke Error | 'unlucky' , maka includeError .
  • Fungsi b akan mewarisi jenis kesalahan fungsi a .
  • Fungsi c hampir identik; 'unlucky' | 'fairly lucky' , atau Error | 'unlucky' | 'fairly lucky' .
  • Fungsi d harus membuang unknown , karena eval adalah... tidak diketahui
  • Fungsi e menangkap kesalahan d , namun karena ada throw di blok catch, kami menyimpulkan tipenya 'too bad...' , di sini, karena hanya blok berisi throw 'primitive value' kita bisa mengatakan itu tidak bisa membuang Error (Koreksi saya jika saya melewatkan beberapa ilmu hitam JS...)
  • Fungsi f mewarisi dari c sama seperti yang dilakukan b dari a .
  • Fungsi g mewarisi 'unlucky' dari a dan unknown dari c sehingga 'unlucky' | unknown => unknown

Berikut adalah beberapa opsi kompiler yang menurut saya harus disertakan, karena keterlibatan pengguna dengan fitur ini dapat bervariasi tergantung pada keahlian mereka serta jenis-keamanan perpustakaan yang mereka andalkan dalam proyek yang diberikan:

{
  "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
    }
  }
}

Adapun sintaks tentang cara mengekspresikan kesalahan yang dapat dihasilkan oleh fungsi apa pun, saya tidak yakin, tetapi saya tahu kita membutuhkan kemampuan untuk itu menjadi umum dan dapat disimpulkan.

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;

Kasus penggunaan saya, karena menunjukkan kasus penggunaan yang sebenarnya mungkin menunjukkan yang lain memiliki nilai:

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

Saya memerlukan wastafel kesalahan untuk mencegah pengiriman tajuk respons beberapa kali untuk beberapa kesalahan (biasanya tidak terjadi, tetapi ketika mereka melakukannya, mereka melakukannya secara massal!)

Masalah ini masih diberi tag Awaiting More Feedback , apa yang bisa kami lakukan untuk memberikan lebih banyak umpan balik? Ini adalah satu-satunya fitur yang saya iri dengan bahasa java

Kami memiliki kasus penggunaan di mana kami membuat kesalahan tertentu saat panggilan API mengembalikan kode non-200:

interface HttpError extends Error {
  response: Response
}

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

Tidak dapat mengetikkan blok tangkap berakhir di pengembang lupa bahwa 2 kemungkinan jenis kesalahan dapat dilemparkan, dan mereka harus menangani keduanya.

Saya lebih suka untuk selalu melempar objek Error :

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')) { .....}
  }
}

Mengapa fitur ini masih "tidak cukup umpan balik"? Ini sangat berguna saat menjalankan API browser seperti indexedDB, penyimpanan lokal. Ini menyebabkan banyak kegagalan dalam skenario yang kompleks tetapi pengembang tidak dapat menyadarinya dalam pemrograman.

Hegel tampaknya memiliki fitur ini dengan sempurna.
https://hegel.js.org/docs#benefits (gulir ke bagian "Kesalahan Ketik")

Saya berharap TypeScript memiliki fitur serupa!

DL;DR;

  • Fungsi penolakan dari janji harus diketik
  • Setiap jenis yang dilemparkan dalam blok try harus disimpulkan menggunakan gabungan ke dalam argumen kesalahan catch
  • error.constructor harus diketik dengan benar menggunakan tipe sebenarnya dan tidak hanya any untuk mencegah kesalahan yang dilemparkan.

Oke, mungkin kita harus menjelaskan apa kebutuhan dan harapan kita:

Kesalahan biasanya ditangani dalam 3 cara di js

1. Jalan simpul

Menggunakan panggilan balik (yang sebenarnya bisa diketik)

Contoh penggunaan:

import * as fs from 'fs';

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

Di mana fs.d.ts memberi kita:

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

Oleh karena itu kesalahannya diketik seperti itu

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

2. Jalan yang dijanjikan

Di mana janji dapat diselesaikan atau ditolak, sementara Anda dapat mengetikkan value yang diselesaikan, Anda tidak dapat mengetik penolakan yang sering disebut reason .

Berikut adalah tanda tangan dari konstruktor janji: Perhatikan alasannya diketik any

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

Secara teoritis dimungkinkan untuk mengetiknya sebagai berikut:

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

Dengan cara ini, kita masih bisa secara teoritis membuat konversi 1-1 antara panggilan balik node dan janji, menjaga semua pengetikan sepanjang proses ini. Misalnya:

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. Cara "coba dan tangkap"

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

Meskipun kami membuat kesalahan "Error" 2 baris sebelum blok catch, TS tidak dapat mengetikkan error (nilai) sebagai Error (tipe).

Bukankah demikian juga tidak menggunakan janji dalam fungsi async (tidak ada keajaiban "belum"). Menggunakan panggilan balik simpul yang kami janjikan:

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

Tidak ada informasi yang hilang untuk TypeScript untuk dapat memprediksi jenis kesalahan apa yang dapat terjadi dalam lingkup percobaan.
Namun kami harus mempertimbangkan internal, fungsi asli mungkin menimbulkan kesalahan yang tidak ada dalam kode sumber, namun jika setiap kali kata kunci "lempar" ada di sumbernya, TS harus mengumpulkan jenisnya dan menyarankannya sebagai kemungkinan jenis kesalahan. Jenis-jenis itu tentu saja akan dicakup oleh blok try .

Ini baru langkah pertama dan masih akan ada ruang untuk perbaikan, seperti mode ketat yang memaksa TS bekerja seperti di Java yaitu memaksa pengguna untuk menggunakan metode berisiko (metode yang dapat melempar sesuatu) dalam try blok. Dan jika pembuat kode tidak ingin melakukannya, maka ia akan secara eksplisit menandai fungsi tersebut sebagai function example throw ErrorType { ... } untuk meningkatkan tanggung jawab penanganan kesalahan.

Last but not least: mencegah kesalahan yang hilang

Apa pun bisa dilempar, tidak hanya Error atau bahkan instance dari suatu objek. Berarti yang berikut ini valid

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

Untuk mengetahui bahwa kesalahan bisa berupa tipe number | Error akan sangat membantu. Namun untuk mencegah lupa menangani kemungkinan jenis kesalahan, bukanlah ide terbaik untuk menggunakan blok if/else yang terpisah tanpa serangkaian kemungkinan hasil yang ketat.
Namun, kasus sakelar akan melakukan ini jauh lebih baik karena kami dapat diperingatkan jika kami lupa mencocokkan kasus tertentu (yang mana yang akan mundur ke klausa default). Kita tidak dapat (kecuali kita melakukan sesuatu yang retas) mengganti case tipe instance objek, dan bahkan jika bisa, kita dapat melempar apa saja (tidak hanya objek tetapi juga boolean, string, dan angka yang tidak memiliki "instance"). Namun, kita dapat menggunakan konstruktor instance untuk mengetahui jenisnya. Kita sekarang dapat menulis ulang kode di atas sebagai berikut:

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… satu-satunya masalah yang tersisa adalah bahwa TS tidak mengetikkan error.constructor dan oleh karena itu tidak ada cara untuk mempersempit kasus sakelar (belum?), jika demikian, kita akan memiliki bahasa kesalahan ketik yang aman untuk js.

Silakan berkomentar jika Anda membutuhkan lebih banyak umpan balik

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

wmaurer picture wmaurer  ·  3Komentar

kyasbal-1994 picture kyasbal-1994  ·  3Komentar

blendsdk picture blendsdk  ·  3Komentar

manekinekko picture manekinekko  ·  3Komentar

Antony-Jones picture Antony-Jones  ·  3Komentar