Panduan JavaScript Tingkat Lanjut: Generator. Bagian 2, kasus penggunaan sederhana



Perilaku generator yang dijelaskan di artikel sebelumnya tidak rumit, tetapi sangat mengejutkan dan mungkin tampak membingungkan pada awalnya. Jadi, alih-alih mempelajari konsep baru, sekarang kita akan berhenti sejenak dan melihat contoh menarik dalam menggunakan generator.



Mari kita memiliki fungsi seperti ini:



function maybeAddNumbers() {
    const a = maybeGetNumberA();
    const b = maybeGetNumberB();

    return a + b;
}

      
      





Berfungsi maybeGetNumberA



dan maybeGetNumberB



mengembalikan angka, tetapi terkadang bisa kembali null



atau undefined



. Ini dibuktikan dengan kata "mungkin" di nama mereka. Jika ini terjadi, jangan mencoba memasukkan nilai-nilai ini (misalnya, angka dan null



), lebih baik berhenti dan kembali, katakanlah null



. Yaitu null



, dan bukan beberapa nilai tak terduga yang diperoleh dengan menambahkan null



/ undefined



dengan angka atau lainnya null



/ undefined



.



Jadi, Anda perlu memeriksa apakah angkanya benar-benar ditentukan:



function maybeAddNumbers() {
    const a = maybeGetNumberA();
    const b = maybeGetNumberB();

    if (a === null || a === undefined || b === null || b === undefined) {
        return null;
    }

    return a + b;
}

      
      





Semuanya bekerja, tetapi jika a



ini null



atau undefined



, maka tidak ada gunanya memanggil fungsi maybeGetNumberB



. Kami tahu bahwa itu akan dikembalikan null



.



Mari tulis ulang fungsinya:



function maybeAddNumbers() {
    const a = maybeGetNumberA();

    if (a === null || a === undefined) {
        return null;
    }

    const b = maybeGetNumberB();

    if (b === null || b === undefined) {
        return null;
    }

    return a + b;
}

      
      





Begitu. Alih-alih tiga baris kode sederhana, kami dengan cepat membengkaknya menjadi 10 baris (tidak termasuk yang kosong). Dan fungsi sekarang diterapkan if



, yang harus Anda pelajari untuk memahami apa fungsinya. Dan ini hanyalah contoh pendidikan! Bayangkan basis kode nyata dengan logika yang jauh lebih kompleks, membuat pemeriksaan seperti itu semakin sulit. Saya ingin menggunakan generator di sini dan menyederhanakan kodenya.



Lihatlah:



function* maybeAddNumbers() {
    const a = yield maybeGetNumberA();
    const b = yield maybeGetNumberB();

    return a + b;
}

      
      





Bagaimana jika kita bisa membiarkan ekspresi yield <smething>



menguji apakah itu <smething>



nilai nyata, dan bukan null



atau undefined



? Jika ternyata bukan angka, maka kita tinggal berhenti dan kembali null



, seperti pada kode versi sebelumnya.



Artinya, Anda dapat menulis kode yang terlihat seperti hanya bekerja dengan nyata, nilai yang ditetapkan. Generator dapat memeriksa ini dan mengambil tindakan yang sesuai untuk Anda! Ajaib, bukan? Dan itu tidak hanya mungkin, itu mudah untuk ditulis!



Tentu saja, generator itu sendiri tidak memiliki fungsi ini. Mereka hanya mengembalikan iterator, dan Anda dapat memasukkan nilai kembali ke generator jika Anda mau. Jadi kita perlu menulis pembungkus, biarlah runMaybe



.



Daripada memanggil fungsi secara langsung:



const result = maybeAddNumbers();

      
      





kami akan menyebutnya sebagai argumen pembungkus:



const result = runMaybe(maybeAddNumbers());

      
      





Pola ini sangat umum terjadi pada generator. Sendiri, mereka tidak tahu banyak, tetapi dengan bantuan pembungkus yang ditulis sendiri, Anda dapat memberi generator perilaku yang diinginkan! Inilah yang kita butuhkan sekarang.



runMaybe



- fungsi yang mengambil satu argumen: iterator yang dibuat oleh generator:



function runMaybe(iterator) {

}

      
      





Mari kita jalankan iterator ini dalam satu lingkaran while



. Untuk melakukan ini, Anda perlu memanggil iterator untuk pertama kalinya dan mulai memeriksa propertinya done



:



function runMaybe(iterator) {
    let result = iterator.next();

    while(!result.done) {

    }
}

      
      





Di dalam loop, kami memiliki dua kemungkinan. Jika result.value



adalah null



atau undefined



, maka iterasi harus segera dihentikan dan dikembalikan null



. Mari kita lakukan:



function runMaybe(iterator) {
    let result = iterator.next();

    while(!result.done) {
        if (result.value === null || result.value === undefined) {
            return null;
        }
    }
}

      
      





Di sini, kami return



segera menghentikan iterasi dengan bantuan dan kembali dari pembungkusnya null



. Tetapi jika itu result.value



adalah angka, maka Anda perlu "kembali" ke generator. Misalnya, jika yield maybeGetNumberA()



fungsinya maybeGetNumberA()



adalah angka, maka Anda perlu mengganti yield maybeGetNumberA()



nilai angka tersebut. Biar saya jelaskan: misalkan hasil perhitungannya maybeGetNumberA()



adalah 5, lalu kita ganti const a = yield maybeGetNumberA();



dengan const a = 5;



. Seperti yang Anda lihat, kami tidak perlu mengubah nilai yang diekstraksi, itu cukup untuk meneruskannya kembali ke generator.



Kami ingat bahwa Anda dapat mengganti yield <smething>



dengan beberapa nilai dengan meneruskannya sebagai argumen ke metode next



di iterator:



function runMaybe(iterator) {
    let result = iterator.next();

    while(!result.done) {
        if (result.value === null || result.value === undefined) {
            return null;
        }

        // we are passing result.value back
        // to the generator
        result = iterator.next(result.value)
    }
}

      
      





Seperti yang Anda lihat, hasil baru sekarang disimpan dalam variabel lagi result



. Ini dimungkinkan karena kami secara khusus menyatakan result



menggunakan let



.



Sekarang, jika generator menemui a null



/ saat mengambil nilai undefined



, kita cukup kembali null



dari wrapper runMaybe



.



Tetap menambahkan sesuatu yang lain sehingga proses iterasi berakhir tanpa mendeteksi null



/ undefined



. Lagi pula, jika kita mendapatkan dua angka, maka kita perlu mengembalikan jumlahnya dari pembungkus!



Generator maybeAddNumbers



diakhiri dengan ekspresi return



. Kami memahami kehadiran itu return <smething>



di generator menyebabkannya mengembalikan next



objek dari panggilan { value: <smething>, done: true }



. Ketika ini terjadi, pengulangan while



berhenti karena properti done



mendapat nilai true



. Tetapi nilai terakhir yang dikembalikan (dalam kasus khusus kami, ini a + b



) akan tetap disimpan di properti result.value



! Dan kita bisa mengembalikannya:



function runMaybe(iterator) {
    let result = iterator.next();

    while(!result.done) {
        if (result.value === null || result.value === undefined) {
            return null;
        }

        result = iterator.next(result.value)
    }

    // just return the last value
    // after the iterator is done
    return result.value;
}

      
      





Dan itu semua!



Mari buat fungsi maybeGetNumberA



dan maybeGetNumberB



, dan biarkan mereka mengembalikan bilangan real terlebih dahulu:



const maybeGetNumberA = () => 5;
const maybeGetNumberB = () => 10;

      
      





Mari jalankan kode dan catat hasilnya:



function* maybeAddNumbers() {
    const a = yield maybeGetNumberA();
    const b = yield maybeGetNumberB();

    return a + b;
}

const result = runMaybe(maybeAddNumbers());

console.log(result);

      
      





Seperti yang diharapkan, angka 15 akan muncul di konsol.



Sekarang, ganti salah satu istilah dengan null



:



const maybeGetNumberA = () => null;
const maybeGetNumberB = () => 10;

      
      





Saat menjalankan kode, kami mendapatkan null



!



Namun, penting bagi kita untuk memastikan bahwa fungsi tersebut maybeGetNumberB



tidak dipanggil jika maybeGetNumberA



mengembalikan null



/ undefined



. Mari kita periksa kembali apakah perhitungannya berhasil. Untuk melakukan ini, tambahkan saja ke fungsi kedua console.log



:



const maybeGetNumberA = () => null;
const maybeGetNumberB = () => {
    console.log('B');
    return 10;
}

      
      





Jika kita sudah menulis wrapper dengan benar runMaybe



, maka ketika kode ini dijalankan, huruf tersebut B



tidak akan muncul di konsol.



Memang saat mengeksekusi kodenya, kita akan melihat secara sederhana null



. Ini berarti pembungkus benar-benar menghentikan generator segera setelah mendeteksi null



/ undefined



.



Kode berfungsi sebagaimana mestinya: menghasilkan null



kombinasi apa pun:



const maybeGetNumberA = () => undefined;
const maybeGetNumberB = () => 10;
const maybeGetNumberA = () => 5;
const maybeGetNumberB = () => null;
const maybeGetNumberA = () => undefined;
const maybeGetNumberB = () => null;

      
      





Dll



Tetapi manfaat dari contoh ini tidak terletak pada eksekusi kode khusus ini. Itu terletak pada fakta bahwa kami telah membuat pembungkus universal yang dapat bekerja dengan generator apa pun yang dapat mengekstrak nilai null



/ undefined



.



Mari tulis fungsi yang lebih kompleks:



function* maybeAddFiveNumbers() {
    const a = yield maybeGetNumberA();
    const b = yield maybeGetNumberB();
    const c = yield maybeGetNumberC();
    const d = yield maybeGetNumberD();
    const e = yield maybeGetNumberE();
    
    return a + b + c + d + e;
}

      
      





Anda dapat melakukannya di bungkus kami tanpa masalah runMaybe



! Faktanya, tidak masalah bagi pembungkus bahwa fungsi kita mengembalikan angka. Bagaimanapun, kami tidak menyebutkan tipe numerik di dalamnya. Jadi, Anda dapat menggunakan nilai apa pun di generator - angka, string, objek, array, struktur data yang lebih kompleks - dan ini akan berfungsi dengan pembungkus kami!



Inilah yang menginspirasi pengembang. Generator memungkinkan Anda menambahkan fungsionalitas khusus ke kode Anda, yang terlihat sangat umum (selain panggilan, tentunya yield



). Anda hanya perlu membuat pembungkus yang mengiterasi generator dengan cara khusus. Dengan demikian, pembungkus menambahkan fungsionalitas yang diperlukan ke generator, yang bisa berupa apa saja! Generator memiliki kemungkinan yang hampir tidak terbatas, ini semua tentang imajinasi kita.



All Articles