Topik wawancara representatif

Wawancara Frontend: Bagaimana Cara Mengimplementasikan Optimistic UI Secara Aman?

FrontendSulit
Tim Redaksi Offer.ccDipublikasikan Diperbarui

Pertanyaan

Editor tugas langsung menampilkan judul yang disimpan, tetapi pengguna dapat mengirim A lalu B sebelum salah satu permintaan selesai. Respons bisa tiba tidak berurutan, salah satu penyimpanan bisa gagal, dan pengguna lain mungkin mengedit tugas tersebut. Bagaimana Anda merancang optimistic state, pengurutan mutasi, rollback, rekonsiliasi, umpan balik aksesibilitas, dan pengujian agar UI dan server tidak berbeda secara diam-diam?

Konteks dan skenario yang berlaku

Sebuah editor tugas langsung menampilkan judul yang dikirimkan. Pengguna dapat mengirim judul A lalu judul B sebelum salah satu permintaan selesai. Respons kedua mungkin tiba lebih dulu, salah satu permintaan bisa gagal, refetch di latar belakang mungkin selesai saat keduanya masih tertunda, dan pengguna lain mungkin memperbarui tugas yang sama. Server mengembalikan tugas kanonikal dan versi sumber daya yang meningkat secara monoton setelah setiap penulisan yang diterima.

Rancang state klien, kontrak permintaan, kebijakan pengurutan, pemulihan kegagalan, pengalaman konflik, umpan balik aksesibilitas, dan rencana validasi. Syaratnya bukan sekadar membuat antarmuka terasa cepat. Setelah setiap urutan penyelesaian, state yang terlihat harus dapat dijelaskan dari state server otoritatif ditambah intensi pengguna yang masih tertunda.

Pertanyaan ini cocok untuk wawancara senior frontend, infrastruktur UI, dan desain sistem frontend. Materi wawancara frontend publik secara eksplisit mencakup pembaruan optimis, race condition permintaan, status kesalahan, dan rollback, sementara catatan wawancara publik tahun 2025 memuat pertanyaan lanjutan tentang prinsip dan kasus penggunaan pembaruan optimis. Sumber-sumber tersebut mendukung relevansi topik saat ini; mereka tidak menetapkan pertanyaan atau frekuensi wawancara khusus perusahaan. Kategorinya adalah frontend karena tugas utamanya adalah state asinkron sisi peramban dan desain interaksi. Kontrol konkurensi server merupakan input untuk desain tersebut, bukan target implementasi utama.

Apa yang dievaluasi oleh pewawancara

Sinyal pertama adalah apakah kandidat memisahkan state otoritatif dari state spekulatif. Mengganti satu objek dalam cache dan menyimpan snapshot lama untuk rollback berfungsi untuk satu permintaan yang terisolasi. Cara ini rusak ketika pekerjaan optimis berikutnya bergantung pada objek yang sama. Model yang kuat mempertahankan basis terkonfirmasi terbaru dan kumpulan terurut dari intensi yang tertunda, lalu menghasilkan tampilan yang dirender dari keduanya.

Sinyal kedua adalah semantik mutasi. "Hanya simpan respons terbaru" melindungi satu jalur render klien, tetapi tidak dapat mencegah permintaan yang lebih lama diproses terakhir oleh server. Kandidat harus memutuskan apakah operasi diserialisasi, digabungkan (coalesced), dibuat komutatif, atau diberi urutan yang diberlakukan oleh server. Pilihannya berbeda untuk "atur judul ke B," "tambah satu," "toggle," "hapus," dan perintah pembayaran.

Sinyal ketiga adalah pemulihan selektif. Ketika operasi A gagal setelah operasi B dikirimkan, memulihkan snapshot dari sebelum A dapat menghapus B. Jawaban yang kuat menghapus atau menandai operasi yang gagal, memajukan basis terkonfirmasi hanya dari respons otoritatif yang valid, dan memutar ulang (replay) intensi yang tersisa. Jika konflik versi server membuat pemutaran ulang tidak aman, UI memunculkan nilai server saat ini dan meminta penyelesaian yang disengaja.

Sinyal terakhir adalah disiplin produksi: status tertunda dan kesalahan tetap dapat dioperasikan dengan keyboard dan teknologi asistif; pembatalan tidak disalahartikan sebagai rollback server; dan pengujian memaksakan setiap urutan respons, urutan kegagalan, race condition refetch, percobaan ulang, dan konflik daripada hanya memvalidasi jalur sukses (happy path).

Pertanyaan untuk diklarifikasi sebelum menjawab

  • Apa arti dari sebuah operasi? setTitle("B") mutlak dapat menggantikan draf judul sebelumnya, sedangkan increment(1) mungkin memerlukan kedua operasi untuk di-commit. Perintah toggle() ambigu dalam percobaan ulang; nilai target yang eksplisit lebih aman. Semantik operasi menentukan apakah penggabungan (coalescing) valid.
  • Bolehkah pengguna mengirimkan lagi saat penyimpanan sedang berlangsung? Menonaktifkan kontrol memberikan serialisasi sederhana tetapi dapat mengganggu pengalaman pengeditan. Jika input berkelanjutan diperlukan, simpan draf lokal secara terpisah dan antrekan/gabungkan pengiriman atau gunakan protokol paralel berversi.
  • Sistem mana yang menentukan urutan penulisan? Jika server hanya menawarkan penulisan tanpa syarat pemenang-kedatangan-terakhir (last-arrival-wins), klien harus menserialisasi penyimpanan yang bergantung pada urutan. Jika API menerima versi basis atau urutan klien dan menolak pekerjaan usang, paralelisme terkontrol menjadi memungkinkan.
  • Apakah tindakan optimis dapat dibatalkan dan berisiko rendah? Suka, label, dan draf sering kali cocok untuk umpan balik optimis. Pembayaran, tindakan destruktif, perubahan sensitif izin, dan tindakan dengan efek eksternal yang tidak dapat diubah mungkin memerlukan konfirmasi atau status tertunda alih-alih berpura-pura sukses.
  • Bisakah sumber latar belakang memperbarui catatan yang sama? Refetch, langganan, tab peramban lain, dan kolaborator dapat mengubah basis. Model state harus mengidentifikasi versi sumber daya dan menentukan apakah intensi yang tertunda dapat diputar ulang secara aman ke basis yang lebih baru.
  • Apa yang harus dirasakan pengguna? Klarifikasi apakah baris individual memerlukan indikator tertunda, percobaan ulang, dan konflik; apakah fokus boleh berpindah; dan pesan penyimpanan atau kegagalan mana yang harus diumumkan tanpa menghasilkan pesan live-region untuk setiap penekanan tombol.

Kerangka jawaban 30 detik

"Saya akan mempertahankan tugas terkonfirmasi server terbaru sebagai basis dan merepresentasikan setiap pengiriman lokal sebagai intensi yang teridentifikasi. UI merender intensi yang tertunda di atas basis tersebut. Untuk penggantian judul, default saya adalah satu penyimpanan yang sedang berjalan per tugas dan menggabungkan draf yang diantrekan ke judul terbaru; ID permintaan saja tidak dapat menghentikan server dari menerapkan permintaan lama terakhir. Jika berhasil, saya mengadopsi versi kanonikal yang dikembalikan, menghapus intensi tersebut, dan memutar ulang intensi yang tersisa. Jika gagal, saya hanya menghapus intensi yang gagal, bukan memulihkan seluruh snapshot usang. Konflik versi menjeda pemutaran ulang otomatis dan menampilkan kedua nilai. Saya akan mengekspos status tertunda dan kesalahan per item, mempertahankan fokus, dan menguji respons yang diurutkan ulang, keberhasilan dan kegagalan campuran, race condition refetch, percobaan ulang, konflik, dan pemulihan luring."

Pembahasan mendalam langkah demi langkah

1. Tentukan satu invarian sebelum memilih pustaka

Untuk setiap sumber daya, simpan base yang terkonfirmasi dan daftar pending yang terurut. Setiap entri yang tertunda memiliki ID operasi klien yang stabil, intensi dan payload, urutan pengirimannya, serta statusnya saat ini. State yang ditampilkan adalah proyeksi murni:

text
view = fold(base, pending in logical order, applyIntent)

Invariannya adalah: tampilan yang dirender sama dengan state server terbaru yang diterima ditambah setiap intensi lokal yang masih memenuhi syarat untuk diterapkan. Model ini membuat refetch atau respons yang terlambat menjadi input untuk rekonsiliasi alih-alih instruksi untuk menimpa layar.

Pola reducer optimis milik React mendukung pemisahan yang sama: ketika nilai basis berubah saat suatu Action sedang tertunda, React dapat menjalankan kembali reducer terhadap basis baru. Pustaka cache dapat mengelola callback siklus hidup mutasi, tetapi tidak memilih semantik operasi produk. Invarian ini tetap berguna baik implementasi menggunakan state React, TanStack Query, cache klien lain, atau store kustom.

Jangan mempertahankan tiga salinan yang tidak terkait bernama serverTask, formTask, dan optimisticTask dengan efek sinkronisasi ad hoc. Pisahkan draf formulir yang belum dikirim karena mengetik belum menjadi mutasi. Setelah dikirimkan, ubah menjadi intensi dengan identitas yang stabil.

2. Pilih pengurutan dari operasi, bukan dari latensi

Ada tiga kebijakan yang berguna:

KebijakanKasus yang sesuaiBiaya atau risiko
Serialisasi per sumber dayaPenulisan bergantung urutan; API tidak memiliki pelindung pengurutanPekerjaan berikutnya menunggu, tetapi urutan server dapat dibuktikan
Serialisasi dan gabungkanHanya nilai terbaru yang belum dikirim yang penting, seperti pengiriman judul berulangNilai perantara yang dikirim sengaja diabaikan
Paralel dengan kontrak serverOperasi independen atau komutatif, atau API memberlakukan versi basis/urutan klienThroughput lebih tinggi, tetapi rekonsiliasi dan konflik bersifat eksplisit

Untuk editor judul ini, serialisasikan berdasarkan ID tugas dan gabungkan perubahan judul yang belum dikirim dalam antrean. Jika A sedang berjalan dan B dikirimkan, tampilkan B secara optimis tetapi simpan hanya B sebagai penulisan jaringan berikutnya. Setelah A selesai, kirim B terhadap versi terbaru yang diterima. TanStack Query mendokumentasikan bahwa mutasi berjalan secara paralel secara default dan menyediakan cakupan mutasi untuk eksekusi serial; kebijakan yang sama dapat diimplementasikan tanpa pustaka tersebut.

Permintaan paralel hanya aman dengan semantik yang lebih kuat. API mungkin menolak versi basis yang usang, menerima urutan klien yang meningkat secara monoton untuk satu sesi pengeditan, atau mengekspos operasi yang benar-benar komutatif. Dokumentasi React juga memperingatkan bahwa Transition asinkron kustom tidak menjamin urutan permintaan; Action terurut tingkat lebih tinggi atau antrean eksplisit tetap diperlukan. Melacak ID permintaan terbaru hanya di peramban mencegah respons lama menimpa B yang terlihat, tetapi server mungkin masih menyimpan A terakhir. Membatalkan (abort) A juga tidak membuktikan bahwa server tidak me-commit-nya.

3. Rekonsiliasi keberhasilan tanpa memercayai urutan kedatangan

Setiap respons harus mengidentifikasi operasi dan mengembalikan sumber daya kanonikal beserta versinya. Dengan serialisasi, tangani operasi yang aktif, adopsi basis yang dikembalikan, hapus operasi tersebut, lalu turunkan tampilan dengan memutar ulang intensi yang diantrekan. B yang diantrekan tetap terlihat saat A selesai, sehingga layar tidak melompat kembali ke A.

Dengan paralelisme terkontrol, cocokkan respons dengan ID operasinya dan validasi versi sumber daya atau konfirmasinya. Jangan langsung menetapkan data respons hanya karena promise berhasil diselesaikan (resolved). Respons yang lebih lama dari versi terkonfirmasi saat ini tidak dapat menggantikan basis. Hapus hanya operasi yang benar-benar diakui oleh respons tersebut. Jika server mengembalikan transformasi kanonikal, seperti memotong spasi pada judul, gunakan hasil tersebut sebagai basis baru sebelum memutar ulang intensi lokal berikutnya.

Refetch di latar belakang mengikuti aturan yang sama. Jika mengembalikan versi 12 saat basis saat ini adalah versi 11, versi 12 dapat memajukan basis; intensi yang tertunda kemudian diterapkan kembali jika semantiknya mengizinkan. Refetch dengan versi 10 adalah bukti usang dan tidak dapat memundurkan basis.

4. Pulihkan berdasarkan operasi, bukan berdasarkan snapshot

Misalkan A dan B terlihat secara optimis, lalu A gagal. Memulihkan objek yang diambil sebelum A akan menghapus B sebagai dampak ikutan. Sebagai gantinya, tandai A gagal atau hapus A, pertahankan B, dan hitung ulang proyeksi. Untuk penetapan judul mutlak, B masih dapat dikirim terhadap basis saat ini. Untuk delta yang bergantung pada urutan, B mungkin perlu menunggu, dihitung ulang, atau ditolak karena premis aslinya tidak lagi berlaku.

Pisahkan kegagalan ke dalam status yang dapat ditindaklanjuti:

  • Kegagalan validasi atau izin bersifat final sampai pengguna mengubah input atau akses; tampilkan nilai yang ditolak dan alasan server di dekat kontrol.
  • Kegagalan jaringan sementara dapat mengekspos opsi coba lagi. Gunakan kembali identitas operasi yang sama hanya jika kontrak server membuat percobaan ulang tersebut aman; jika tidak, rekonsiliasi terlebih dahulu apakah penulisan asli telah di-commit.
  • Konflik versi berarti basis berubah di tempat lain. Adopsi atau ambil nilai server saat ini, bandingkan dengan intensi lokal, dan putar ulang secara otomatis hanya operasi dengan aturan penggabungan yang terbukti. Untuk konflik judul, sajikan nilai saat ini dan nilai yang diusulkan alih-alih memilih salah satu secara diam-diam.
  • Hasil yang tidak diketahui berarti permintaan mungkin telah di-commit meskipun responsnya hilang. "Rollback secara lokal dan coba lagi sebagai baru" dapat menduplikasi tindakan non-idempoten.

Beberapa tindakan sebaiknya tidak dilakukan secara optimis. Jika kegagalan sulit dibatalkan, mengubah otorisasi, menagih uang, atau membuat status hukum atau bisnis yang menyesatkan, tampilkan pengakuan tertunda segera dan konfirmasikan hanya setelah server menerimanya.

5. Buat state spekulatif terlihat dan dapat diakses

Optimis bukan berarti tidak dapat dibedakan dari yang terkonfirmasi. Tandai tugas yang terpengaruh sebagai sedang menyimpan, pertahankan nilai yang dikirimkan, dan sediakan tindakan coba lagi atau resolusi konflik lokal. Jangan nonaktifkan tugas yang tidak terkait. Jika serialisasi digunakan, bedakan penyimpanan aktif dari nilai antrean yang lebih baru sehingga instrumentasi dan pesan kesalahan terhubung ke intensi yang tepat.

Pertahankan fokus keyboard saat penyimpanan berhasil, gagal, atau cache direkonsiliasi. Area status dapat mengumumkan "Menyimpan judul tugas," "Judul tugas disimpan," atau "Penyimpanan gagal" tanpa memindahkan fokus. Peran status dari W3C memiliki semantik polite live-region; buat wadah status sebelum pesan berubah dan hindari mengumumkan setiap penekanan tombol. Hubungkan kesalahan khusus bidang ke bidang tersebut dan jaga agar status visual dapat dipahami tanpa hanya bergantung pada warna.

6. Uji state machine dan amati kebijakannya

Uji kebijakan pengurutan yang dipilih alih-alih berpura-pura bahwa setiap kebijakan mengizinkan jejak yang sama. Untuk jalur serial, pastikan B terlihat tetapi tidak dikirim sampai A selesai; lalu cakup keberhasilan atau kegagalan A yang diikuti oleh keberhasilan atau kegagalan B, kehilangan respons yang diikuti oleh rekonsiliasi, dan transformasi server. Untuk setiap jalur paralel yang didukung, gunakan transport yang dapat dikontrol dan stub server untuk memaksakan urutan pemrosesan dan respons A-lalu-B serta B-lalu-A. Pastikan proyeksi yang dirender, operasi yang diantrekan, versi terkonfirmasi, dan nilai akhir server setelah setiap peristiwa.

Kemudian masukkan refetch yang lebih baru dan lebih lama saat mutasi sedang tertunda, konflik kolaborator, pemulihan luring-ke-daring, unmount dan remount komponen, pengiriman berulang, dan pembatalan setelah server melakukan commit. Verifikasi fokus keyboard, pengumuman status, label coba lagi, dan bahwa kesalahan terhubung ke tugas dan operasi yang benar.

Telemetri produksi harus memisahkan latensi yang dirasakan dari kebenaran: latensi render optimis, latensi konfirmasi, tingkat kegagalan dan rollback, tingkat konflik, waktu tunggu antrean, jumlah operasi yang digabungkan, jumlah percobaan ulang, dan ketidakcocokan rekonsiliasi. Antarmuka cepat yang sering mengoreksi dirinya sendiri ke nilai yang mengejutkan telah gagal memenuhi kontrak produk.

Contoh jawaban berkualitas tinggi

"Saya akan mulai dengan menanyakan apakah setiap judul yang dikirimkan harus disimpan atau hanya intensi terbaru pengguna yang penting. Di sini judul terbaru yang penting, API mengembalikan versi sumber daya, dan saya tidak memerlukan penulisan paralel untuk satu tugas. Oleh karena itu, saya akan mengizinkan pengeditan berkelanjutan tetapi menserialisasi penyimpanan jaringan berdasarkan ID tugas. Jika A sedang berjalan dan pengguna mengirimkan B, layar langsung menampilkan B dan B menjadi intensi antrean yang digabungkan.

State untuk tugas tersebut memiliki basis terkonfirmasi, operasi aktif, dan paling banyak satu intensi judul yang diantrekan. Setiap operasi memiliki ID klien dan versi basis yang akan dikirimkannya. Judul yang dirender adalah nilai yang diantrekan, jika tidak ada maka nilai optimis aktif, jika tidak ada maka judul yang terkonfirmasi. Ketika A berhasil, saya mengadopsi tugas kanonikal dan versi dari responsnya. Saya tidak merender A karena B masih tertunda; saya mengirim B terhadap versi baru. Ketika A gagal, saya hanya menghapus A dan tetap menawarkan untuk menyimpan B jika kegagalannya bersifat sementara. Kegagalan validasi atau izin tetap terikat pada intensi yang ditolak.

Jika server melaporkan bahwa pengguna lain memajukan versi, saya menghentikan pengiriman otomatis, mengambil atau mengadopsi judul saat ini, dan menampilkan nilai server serta nilai yang diusulkan untuk resolusi. Saya tidak akan mengandalkan pengabaian respons lama, karena itu tidak dapat mencegah permintaan lama menulis terakhir di server. Saya juga tidak akan memperlakukan pembatalan (abort) sebagai rollback.

Setiap tugas mengekspos statusnya sendiri: menyimpan, diantrekan, gagal, atau konflik. Fokus tetap pada editor, dan polite status region yang sudah ada sebelumnya mengumumkan hasil pengiriman tanpa mengumumkan setiap karakter. Pengujian mengontrol resolusi promise dan pemrosesan server secara independen, sehingga saya dapat membuktikan nilai UI dan server akhir untuk keberhasilan yang diurutkan ulang, kegagalan campuran, refetch usang, konflik, respons yang hilang, percobaan ulang, pemulihan luring, dan unmount."

Kesalahan umum

  • Menyimpan satu snapshot pra-mutasi dan memulihkannya pada kesalahan apa pun → kegagalan yang terlambat menghapus pekerjaan optimis yang lebih baru → hapus operasi yang gagal dan hitung ulang dari basis terkonfirmasi ditambah intensi yang tersisa.
  • Hanya menyimpan ID respons terbaru → UI mungkin terlihat benar sementara server me-commit permintaan lama terakhir → serialisasikan penulisan yang bergantung pada urutan atau wajibkan semantik versi atau urutan yang diberlakukan oleh server.
  • Menggunakan satu flag loading global → kontrol yang tidak terkait membeku dan kegagalan tidak dapat dikaitkan dengan sumber dayanya → lacak status tertunda dan kesalahan berdasarkan sumber daya dan ID operasi.
  • Memperlakukan setiap mutasi dapat diputar ulang (replayable) → toggle, delta, penghapusan, dan perintah yang tidak dapat diubah memiliki semantik kegagalan yang berbeda → tentukan intensi eksplisit dan aturan penggabungan sebelum mengaktifkan perilaku optimis.
  • Mengasumsikan pembatalan membatalkan permintaan di server → server mungkin melakukan commit sebelum melihat pembatalan → rekonsiliasi state otoritatif dan rancang percobaan ulang untuk hasil yang tidak diketahui.
  • Membiarkan fetch apa pun menimpa cache → refetch usang atau respons terlambat memundurkan state terkonfirmasi → bandingkan versi sumber daya dan putar ulang intensi tertunda yang valid di atas basis terbaru.
  • Menyembunyikan semua status tertunda untuk membuat UI terasa instan → pengguna tidak dapat memahami koreksi di kemudian hari atau mencoba lagi tindakan yang terpengaruh → tampilkan status penyimpanan lokal, kegagalan, dan konflik sambil mempertahankan nilai optimis.
  • Hanya menguji keberhasilan dalam urutan pengiriman → kondisi race condition tersulit tidak teruji → kendalikan urutan respons, urutan server, kegagalan, refetch, percobaan ulang, dan remount dalam matriks pengujian.

Pertanyaan lanjutan dan jawabannya

Apa yang berubah jika pengguna dapat mengedit secara luring selama beberapa jam?

Operasi yang tertunda harus tahan lama (durable), dicakupkan ke akun dan sumber daya yang terautentikasi, dan diputar ulang hanya setelah klien menyegarkan otorisasi dan versi otoritatif. Simpan semantik intensi dan ID operasi yang stabil, bukan snapshot UI yang ditangkap. Saat terhubung kembali, ambil basis terlebih dahulu, buang operasi yang dibatalkan pengguna secara eksplisit, dan putar ulang hanya operasi dengan aturan penggabungan yang valid. Izin yang kedaluwarsa, sumber daya yang dihapus, dan perubahan skema memerlukan status terblokir yang terlihat. Untuk kolaborasi luring yang lama, antrean optimis sederhana mungkin tidak cukup; produk mungkin memerlukan operasi penggabungan khusus domain atau protokol pengeditan kolaboratif.

Bisakah setiap mutasi berjalan secara paralel jika server mengembalikan versi?

Tidak. Versi memberi tahu klien state mana yang diwakili oleh suatu respons, tetapi tidak secara otomatis menentukan bagaimana dua penulisan harus diurutkan atau digabungkan. Paralelisme aman ketika server secara atomik memeriksa versi basis yang dikirimkan, memberlakukan urutan, atau mengekspos operasi yang independen atau komutatif. Jika tidak, dua penulisan mutlak masih dapat tiba dan di-commit dalam urutan yang salah. Serialisasi sering kali merupakan kontrak yang lebih jelas untuk satu sumber daya yang bergantung pada urutan.

Bagaimana Anda menangani pembuatan optimis ketika server menetapkan ID asli?

Buat ID klien yang stabil sebelum merender dan gunakan itu sebagai identitas operasi dan kunci daftar sementara. Jika berhasil, catat pemetaan ke ID server dan ganti entitas terkonfirmasi tanpa me-remount baris yang tidak terkait. Lakukan deduplikasi pada percobaan ulang berdasarkan identitas operasi jika server mendukungnya. Jika pembuatan gagal, hapus atau tandai hanya entitas optimis tersebut. Operasi selanjutnya yang menargetkan entitas sementara harus menunggu pemetaan atau diekspresikan dalam antrean yang dapat menulis ulang target setelah konfirmasi.

Kapan Anda sengaja memilih UI pesimis?

Pilih UI pesimis ketika menampilkan keberhasilan akan menyesatkan pengguna secara material, pemulihan tidak bersifat lokal, konflik sering terjadi, atau tindakannya tidak dapat diubah atau berisiko tinggi. Pembayaran, pemberian izin, pengajuan hukum, atau tindakan massal yang destruktif dapat langsung mengakui klik sambil menampilkan status tertunda yang sebenarnya, lalu menampilkan keberhasilan hanya setelah konfirmasi otoritatif. Antarmuka masih dapat terasa responsif tanpa mengklaim hasil yang belum terjadi.

Sumber publik

Pertanyaan terkait