Perintah dan konteks
Sebuah kotak pencarian meminta saran setiap kali inputnya berubah. Pengguna mengetik hello dengan cepat, sehingga peramban mengirimkan permintaan untuk h, he, hel, hell, dan hello, tetapi jaringan tidak menjamin bahwa respons tiba dalam urutan tersebut. Permintaan lama mungkin selesai paling akhir dan menggantikan hasil hello dengan hell; pengguna juga dapat mengosongkan input, berpindah jaringan, atau meninggalkan halaman.
Rancang siklus hidup permintaan, aturan commit hasil, strategi pembatalan, kebijakan cache dan kesegaran, state pemuatan dan kesalahan, umpan balik yang aksesibel, serta rencana verifikasi. Jelaskan kueri mana yang diwakili oleh hasil yang terlihat alih-alih hanya mengatakan "tambahkan debounce."
Topik ini cocok untuk wawancara senior frontend, React, dan infrastruktur UI. Dokumentasi resmi React menggunakan pengetikan cepat untuk menjelaskan race condition pengambilan data dan menyarankan pengabaian respons usang selama pembersihan Effect. MDN mendokumentasikan bahwa AbortController.abort() dapat menghentikan fetch, konsumsi response-body, atau stream. Panduan stale-while-revalidate dari web.dev menambahkan trade-off cache dalam menyajikan nilai lama yang dapat diterima saat menyegarkannya. Sumber-sumber ini mendukung representasi teknis dari topik ini, tetapi tidak menetapkan pertanyaan perusahaan yang tetap atau frekuensi wawancara tertentu. Kategorinya adalah frontend karena keahlian intinya adalah state asinkron sisi peramban, konsistensi render, dan umpan balik interaksi.
Apa yang dievaluasi oleh pewawancara
Pertama, apakah kandidat dapat membedakan permintaan yang selesai dari permintaan yang masih memenuhi syarat untuk di-commit? Sebuah Promise yang selesai lebih dulu bukan berarti mewakili kueri saat ini. Setiap permintaan memerlukan identitas yang stabil, dan identitas atau kunci kueri tersebut harus tetap cocok pada saat commit.
Kedua, apakah mereka memahami bahwa pembatalan adalah manajemen sumber daya? AbortController dapat mengurangi pekerjaan yang sia-sia, tetapi pembatalan mungkin terjadi setelah server memproses permintaan. Abort bukanlah rollback bisnis dan tidak dapat menggantikan perlindungan hasil usang.
Ketiga, apakah mereka dapat memodelkan state machine secara lengkap: input kosong, pemuatan pertama, penyegaran dengan hasil yang sudah ada, keberhasilan, hasil kosong, kesalahan yang dapat dicoba lagi, cache usang, dan unmount semuanya memerlukan aturan eksplisit. Mempertahankan hasil lama saat memuat kueri baru atau menampilkan skeleton bergantung pada semantik kueri dan risiko menyesatkan pengguna.
Terakhir, dapatkah mereka memverifikasi perilaku dengan mengontrol urutan respons dan mencakup pengetikan cepat, pengosongan input, percobaan ulang, cache hit, unmount, dan pengumuman aksesibilitas alih-alih hanya menguji respons sukses yang tiba secara berurutan?
Pertanyaan klarifikasi yang harus diajukan terlebih dahulu
- Haruskah setiap penekanan tombol memicu permintaan? Jika produk menunggu jeda, debounce sangat berguna, tetapi itu hanya mengurangi permintaan dan tidak menyelesaikan race condition di antara permintaan yang sudah terkirim.
- Bolehkah hasil lama tetap terlihat? Saran sering kali dapat tetap ditampilkan selama penyegaran jika diberi label sebagai milik kueri sebelumnya; kuotasi finansial atau hasil izin mungkin perlu dikosongkan untuk menghindari pemilihan yang salah.
- Apa yang membatalkan kunci cache? String kueri, filter, bahasa, cakupan akun, dan versi data semuanya dapat berpengaruh. Melakukan caching hanya berdasarkan URL halaman dapat mencampuradukkan izin atau filter yang berbeda.
- Apakah server mendukung pembatalan atau deduplikasi? Klien tetap memerlukan pengaman ketepatan; pembatalan di sisi server meningkatkan penggunaan sumber daya tetapi tidak dapat membuktikan bahwa permintaan lama tidak memiliki efek.
- Apakah input memerlukan umpan balik pembaca layar langsung? Jumlah hasil atau status pemuatan dapat menggunakan region
statusyang sudah ada secara sopan (polite), tetapi setiap karakter tidak boleh menginterupsi pengguna.
Kerangka jawaban 30 detik
"Saya mewakili setiap kueri dengan kunci permintaan dan hanya mengizinkan respons memperbarui hasil jika kunci tersebut masih sama dengan kueri saat ini. Saat pembersihan, saya memanggil AbortController.abort() untuk melepaskan pekerjaan jaringan dan pembacaan body, tetapi saya tidak memperlakukan pembatalan sebagai rollback; bahkan jika abort gagal, pemeriksaan identitas akan membuang respons yang usang. Menerapkan debounce setelah pengguna menjeda ketikan mengurangi noise tetapi tidak menggantikan perlindungan race condition. State membedakan antara kosong, memuat, menyegarkan hasil lama, berhasil, hasil kosong, dan kesalahan yang dapat dicoba lagi. Cache menggunakan kunci kueri lengkap dan jendela kesegaran yang eksplisit. Pengujian memaksa permintaan lama untuk kembali setelah permintaan baru dan mencakup pengosongan, unmounting, percobaan ulang, caching, serta umpan balik pembaca layar."
Jawaban mendalam
1. Tentukan invarian commit hasil
Pertahankan currentKey, status, visibleData, dan cache opsional. currentKey mencakup kueri yang dinormalisasi dan setiap filter, bahasa, serta cakupan akun yang mengubah hasil. Setiap permintaan jaringan mendapatkan requestId yang unik; closure-nya menyimpan kuncinya dan pengontrolnya.
Sebelum melakukan commit hasil, periksa apakah permintaan belum dibatalkan dan kuncinya masih sama dengan kunci saat ini. Hanya setelah itu ia boleh menulis data yang terlihat, kesalahan, atau state keberhasilan. Invarian ini lebih aman daripada berasumsi "permintaan terakhir biasanya selesai paling akhir," karena jaringan tidak memberikan jaminan urutan semacam itu.
Perlakukan UI sebagai proyeksi murni dari kunci kueri saat ini, entri cache terbaru yang dapat digunakan, permintaan aktif, dan kesalahan. Hindari beberapa Effect yang menyinkronkan nilai loading, data, dan error yang independen; mengosongkan atau mengganti kueri dengan cepat dapat menulis kesalahan lama ke dalam state baru.
2. Pisahkan debounce, throttle, dan perlindungan race condition
Debounce menggabungkan input cepat menjadi satu permintaan dan berguna untuk saran pengetikan. Throttle membatasi permintaan dalam jendela waktu dan berguna untuk pengguliran atau pemantauan. Keduanya mengontrol kapan permintaan dimulai; tidak ada yang mencegah permintaan yang sudah dikirim tiba terlambat.
Bahkan dengan debounce 250 milidetik, pengguna dapat mengetik lagi saat permintaan sedang dalam perjalanan. Pertahankan pemeriksaan identitas. Contoh resmi React menetapkan flag ignore dalam pembersihan Effect sehingga respons lama tidak lagi memanggil setResults; urutan yang bertambah (incrementing sequence), perbandingan kunci kueri, atau state machine eksplisit menerapkan aturan yang sama.
3. Perlakukan pembatalan sebagai optimasi, bukan bukti ketepatan
Beri setiap permintaan aktif AbortController-nya sendiri. Ketika kueri berubah, input dikosongkan, komponen di-unmount, atau permintaan pengganti dimulai, panggil abort(). Tangani AbortError sebagai pembatalan yang diharapkan: jangan menampilkannya sebagai kegagalan jaringan dan jangan biarkan itu menimpa state kueri baru.
Sinyal pembatalan mungkin tiba terlambat untuk menghentikan pemrosesan server atau mungkin hanya menghentikan pembacaan di sisi peramban. "Membatalkan permintaan lama" dan "permintaan lama tidak menghasilkan efek sisi server" adalah dua klaim yang berbeda. Permintaan GET pencarian biasanya tidak memiliki efek samping penulisan data, tetapi tetap memerlukan pelindung requestId; operasi yang tidak dapat dibatalkan memerlukan kontrak idempoten yang eksplisit.
4. Pilih antara hasil lama, skeleton, dan cache
Tanpa data pada kueri pertama, tampilkan placeholder pemuatan dan teks status yang aksesibel. Selama penyegaran, pertahankan hasil lama jika tetap aman, beri label kueri pemiliknya, dan tampilkan indikator penyegaran ringan. Jika data lama dapat menyebabkan pemilihan yang merugikan, kosongkan data tersebut atau buat agar tidak dapat dipilih.
Kunci cache harus mencakup input kueri lengkap. Cache hit dapat langsung dirender dan kemudian divalidasi ulang di latar belakang, tetapi catat waktu, sumber, dan kesalahannya agar data yang usang atau telah berubah izinnya tidak ditampilkan sebagai fakta terkini. Stale-while-revalidate menyajikan nilai lama yang dapat diterima terlebih dahulu dan mengambil nilai baru secara asinkron; produk harus menentukan batas jendela kesegaran yang dapat diterima.
5. Tangani kesalahan, hasil kosong, dan percobaan ulang
Hasil kosong adalah state sukses, bukan kesalahan jaringan. Ikat kesalahan ke kunci saat ini; kesalahan dari kueri lama akan dibuang. Kesalahan yang dapat dicoba lagi menyimpan informasi kueri dan backoff. Percobaan ulang membuat requestId baru alih-alih menggunakan kembali Promise yang sudah ditandai usang.
Jika server mengembalikan 401, cakupan izin yang berubah, atau kondisi kueri yang tidak valid, kosongkan atau validasi ulang cache alih-alih mencoba ulang tanpa henti. Saat pemulihan jaringan, minta hanya kunci saat ini sehingga peristiwa pemulihan tidak mengisi kembali kueri yang sudah dikosongkan pengguna.
6. Pertahankan umpan balik keyboard dan teknologi asistif
Jangan memindahkan fokus input saat hasil diperbarui. Gunakan identitas daftar yang stabil sehingga unmounting hasil lama tidak menyebabkan pembaca layar membaca ulang seluruh daftar. Tempatkan pemuatan, jumlah hasil, dan kesalahan di region status sopan (polite) yang sudah ada sebelumnya, dan perbarui hanya untuk perubahan state yang bermakna.
Pengguna keyboard harus dapat terus mengetik, membatalkan, atau memilih hasil saat sedang memuat. Jika suatu hasil usang, konfirmasikan bahwa kunci kuerinya masih cocok sebelum menerapkan pemilihan. Warna tidak boleh menjadi satu-satunya sinyal pemuatan atau kesalahan; kaitkan teks kesalahan dengan input atau daftar.
7. Uji state machine dengan urutan terkontrol
Test double transport harus menjeda setiap permintaan dan melepaskan respons secara manual. Cakup permintaan lama yang berhasil setelah permintaan baru, kegagalan lama yang tiba setelah keberhasilan baru, respons usang setelah pengosongan, kegagalan penyegaran latar belakang setelah cache hit, respons setelah unmount, AbortError, dan pengetikan ulang selama percobaan ulang.
Setelah setiap event, pastikan (assert) kunci saat ini, hasil yang terlihat, status, timestamp cache, dan pengumuman pembaca layar. Verifikasi bahwa muatan yang sama yang tiba dalam urutan berbeda tidak menyebabkan render ganda atau kehilangan fokus. Lacak jumlah permintaan, rasio pembatalan, respons usang yang dibuang, latensi hasil yang terlihat, dan rasio percobaan ulang, tetapi jangan gunakan pengurangan permintaan untuk menyembunyikan hasil yang salah.
Contoh jawaban berkualitas tinggi
"Pertama, saya menormalisasi kueri lengkap menjadi sebuah kunci yang berisi teks, filter, bahasa, dan cakupan izin. Setiap permintaan nyata mendapatkan requestId dan AbortController, serta mencatat kunci saat ini. Respons harus lolos kedua pemeriksaan—requestId-nya masih aktif dan kuncinya masih cocok—sebelum dapat meng-commit keberhasilan, hasil kosong, atau kesalahan. Saat input berubah, saya membatalkan controller lama dan menangani AbortError secara senyap, tetapi saya tidak mengklaim bahwa pembatalan tersebut membatalkan pemrosesan di server.
Saya mengirim permintaan setelah jeda 250 milidetik untuk mengurangi noise. Pemuatan pertama menampilkan placeholder. Selama penyegaran, saya mempertahankan hasil lama hanya jika aman, memberi label bahwa hasil tersebut milik kueri sebelumnya, dan jika tidak aman maka akan dikosongkan. Entri cache menggunakan kunci lengkap dan dapat dirender singkat sebelum validasi latar belakang; entri yang melewati jendela kesegaran hanya menampilkan status memuat.
State machine membedakan antara input kosong, memuat, menyegarkan data lama, sukses, hasil kosong, dan kesalahan yang dapat dicoba lagi. Kesalahan terikat pada kunci, sehingga kesalahan lama yang terlambat akan dibuang. Fokus tetap berada pada input, identitas hasil tetap stabil, dan region polite yang sudah ada mengumumkan perubahan state yang bermakna alih-alih setiap karakter.
Saya kemudian memaksa hell kembali setelah hello, mengirimkan respons usang setelah pengosongan, membuat pembatalan gagal, menggagalkan penyegaran cache, mengirimkan respons setelah unmount, dan mengetik lagi selama percobaan ulang. UI akhir harus dapat dijelaskan hanya oleh kunci saat ini."
Kesalahan umum
- Hanya melakukan debounce → permintaan yang sudah terkirim masih dapat mengalami race condition → pertahankan requestId atau pelindung kunci.
- Menampilkan respons terakhir yang selesai → urutan penyelesaian jaringan bukanlah urutan kueri → hanya kunci saat ini yang boleh di-commit.
- Memperlakukan abort sebagai rollback → server mungkin sudah memprosesnya → gunakan pembatalan untuk pembersihan dan aturan identitas/versi untuk ketepatan.
- Satu entri cache untuk setiap kueri → filter, bahasa, atau cakupan dapat bocor di seluruh hasil → sertakan setiap input hasil dalam kunci.
- Membiarkan kesalahan lama menimpa kueri baru → pengguna melihat kegagalan yang usang → ikat kesalahan ke requestId dan kunci.
- Mengosongkan UI untuk setiap pemuatan → pengguna kehilangan konteks yang berguna → pilih hasil lama atau skeleton berdasarkan risiko data yang menyesatkan.
- Mengumumkan setiap ketukan tombol → pembaca layar terganggu berulang kali → umumkan perubahan pemuatan, hasil, dan kesalahan yang bermakna.
- Hanya menguji keberhasilan yang berurutan → race condition nyata tidak terlihat → kendalikan urutan respons, pembatalan, pengosongan, unmount, dan percobaan ulang.
Pertanyaan lanjutan dan jawaban
Apakah sebuah kunci cukup untuk paginasi atau infinite scrolling?
Sertakan filter, pengurutan, cursor, atau nomor halaman dalam kunci permintaan dan ikat setiap halaman ke versi kueri. Kueri baru akan membuang halaman lama. Memuat halaman lain untuk kueri yang sama dapat mempertahankan visibilitas halaman sebelumnya, tetapi cursor duplikat dan halaman usang tidak boleh menimpa daftar yang telah dikonfirmasi. Gunakan cursor berikutnya yang disediakan server alih-alih menyimpulkan urutan dari nomor halaman di sisi klien.
Apa yang berubah jika pengguna mengetik secara offline lalu terhubung kembali?
Pencarian biasanya tidak perlu menyimpan setiap permintaan lama; pertahankan input saat ini dan cache terakhir yang dapat diterima. Saat terhubung kembali, minta hanya kunci saat ini dan abaikan pekerjaan offline yang usang. Jika saran offline adalah persyaratan produk, usia cache, cakupan data, dan kesegaran harus terlihat sehingga hasil offline tidak disajikan sebagai data waktu nyata (real-time).
Bisakah React Query atau SWR menyelesaikan masalah ini sendirian?
Pustaka cache dapat menyediakan deduplikasi, caching, pembatalan validasi, dan manajemen siklus hidup, tetapi produk tetap menentukan kunci kueri, batas keusangan yang dapat diterima, percobaan ulang, dan state interaksi. Pembatalan pustaka atau stale time default tidak dapat menentukan cakupan izin, risiko data yang menyesatkan, atau umpan balik aksesibilitas. Verifikasi semantik race pustaka dan terapkan aturan tersebut dalam kunci kueri serta state UI.
Apakah pembatalan aman jika pencarian berubah menjadi POST dengan pencatatan audit (audit logging)?
Jangan berasumsi demikian. Permintaan POST mungkin memiliki efek samping di sisi server, dan abort di sisi klien tidak membatalkan transaksi. Gunakan kunci idempotensi (idempotency key), status pengiriman yang eksplisit, dan endpoint untuk memeriksa status; tampilkan keberhasilan hanya setelah konfirmasi otoritatif. POST masih masuk akal untuk kueri kompleks yang bebas efek samping, tetapi kontrak percobaan ulang dan pembatalannya harus eksplisit.