Kesalahan itu baik. Penulis bahan, terjemahan yang kami terbitkan hari ini, mengatakan bahwa ia yakin bahwa ide ini diketahui semua orang. Sepintas, kesalahannya tampak menyeramkan. Mereka mungkin disertai dengan semacam kerugian. Kesalahan yang dibuat di depan umum merusak otoritas orang yang membuatnya. Tetapi membuat kesalahan, kita belajar dari mereka, yang berarti bahwa lain kali kita masuk ke dalam situasi di mana kita sebelumnya berperilaku salah, kita melakukan segala yang diperlukan.

Di atas kami berbicara tentang kesalahan yang dilakukan orang dalam kehidupan sehari-hari. Kesalahan dalam pemrograman adalah hal lain. Pesan kesalahan membantu kami meningkatkan kode, mereka memungkinkan kami untuk memberi tahu pengguna tentang proyek kami bahwa ada kesalahan, dan mungkin memberi tahu pengguna bagaimana berperilaku sehingga kesalahan tidak lagi terjadi.
Materi penanganan kesalahan JavaScript ini dibagi menjadi tiga bagian. Pertama, kami akan memberikan gambaran umum tentang sistem penanganan kesalahan dalam JavaScript dan berbicara tentang objek kesalahan. Setelah itu, kami mencari jawaban untuk pertanyaan apa yang harus dilakukan dengan kesalahan yang terjadi pada kode server (khususnya, ketika menggunakan bundel Node.js + Express.js). Selanjutnya, kita membahas penanganan kesalahan di React.js. Kerangka kerja yang akan dipertimbangkan di sini dipilih karena popularitasnya yang sangat besar. Namun, prinsip-prinsip untuk mengatasi kesalahan yang dibahas di sini bersifat universal, jadi meskipun Anda tidak menggunakan Express dan React, Anda dapat dengan mudah menerapkan apa yang Anda pelajari ke alat yang Anda gunakan.
Kode untuk proyek demo yang digunakan dalam materi ini dapat ditemukan di repositori
ini .
1. Kesalahan dalam JavaScript dan cara universal untuk bekerja dengannya
Jika ada yang salah dalam kode Anda, Anda dapat menggunakan konstruksi berikut.
throw new Error('something went wrong')
Selama eksekusi perintah ini, sebuah instance dari objek
Error akan dibuat dan pengecualian akan dihasilkan (atau, seperti yang mereka katakan, "dilempar") dengan objek ini. Pernyataan
melempar dapat melempar pengecualian yang mengandung ekspresi sewenang-wenang. Dalam hal ini, eksekusi skrip akan berhenti jika langkah-langkah belum diambil untuk menangani kesalahan.
Pemrogram JS pemula biasanya tidak menggunakan pernyataan
throw
. Mereka biasanya menemukan pengecualian yang dilempar oleh runtime bahasa atau pustaka pihak ketiga. Ketika ini terjadi, sesuatu seperti
ReferenceError: fs is not defined
masuk ke konsol
ReferenceError: fs is not defined
dan eksekusi program berhenti.
▍Object Error
Instance dari objek
Error
memiliki beberapa properti yang bisa kita gunakan. Properti pertama yang kami minati adalah
message
. Di sinilah garis mendapatkan yang dapat diteruskan ke konstruktor kesalahan sebagai argumen. Sebagai contoh, berikut ini menunjukkan bagaimana instantiate objek
Error
dan output ke konsol string yang dilewatkan oleh konstruktor dengan mengakses properti
message
.
const myError = new Error('please improve your code') console.log(myError.message)
Properti kedua dari objek, sangat penting, adalah jejak tumpukan kesalahan. Ini adalah properti
stack
. Beralih ke sana, Anda dapat melihat tumpukan panggilan (riwayat kesalahan), yang menunjukkan urutan operasi yang menyebabkan program tidak berfungsi. Secara khusus, ini memungkinkan kita untuk memahami file mana yang berisi kode buruk dan melihat urutan panggilan fungsi yang menyebabkan kesalahan. Ini adalah contoh dari apa yang dapat Anda lihat dengan mengakses properti
stack
.
Error: please improve your code at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79) at Module._compile (internal/modules/cjs/loader.js:689:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10) at Module.load (internal/modules/cjs/loader.js:599:32) at tryModuleLoad (internal/modules/cjs/loader.js:538:12) at Function.Module._load (internal/modules/cjs/loader.js:530:3) at Function.Module.runMain (internal/modules/cjs/loader.js:742:12) at startup (internal/bootstrap/node.js:266:19) at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)
Di sini, di bagian atas, ada pesan kesalahan, diikuti oleh indikasi bagian kode yang pelaksanaannya menyebabkan kesalahan, kemudian menjelaskan tempat dari mana bagian gagal ini dipanggil. Ini berlanjut ke "terjauh" dalam kaitannya dengan fragmen kode kesalahan.
▍ Penanganan generasi dan kesalahan
Membuat turunan dari objek
Error
, yaitu, mengeksekusi perintah dari bentuk
new Error()
, tidak menyebabkan konsekuensi khusus. Hal-hal menarik mulai terjadi setelah menerapkan operator
throw
, yang menghasilkan kesalahan. Seperti yang telah disebutkan, jika kesalahan tersebut tidak diproses, eksekusi skrip akan berhenti. Dalam hal ini, tidak ada bedanya apakah operator
throw
itu digunakan oleh programmer sendiri, apakah kesalahan terjadi di perpustakaan tertentu atau dalam runtime bahasa (di browser atau di Node.js). Mari kita bicara tentang berbagai skenario penanganan kesalahan.
▍bangun coba ... tangkap
try...catch
adalah cara termudah untuk menangani kesalahan yang sering dilupakan. Namun, saat ini, digunakan jauh lebih intensif daripada sebelumnya, karena fakta bahwa itu dapat digunakan untuk menangani kesalahan dalam
async/await
konstruksi.
Blok ini dapat digunakan untuk menangani kesalahan yang terjadi dalam kode sinkron. Pertimbangkan sebuah contoh.
const a = 5 try { console.log(b)
Jika dalam contoh ini kami tidak menyertakan perintah
console.log(b)
gagal dalam
try...catch
, skrip akan dihentikan.
▍ akhirnya diblokir
Kadang-kadang terjadi bahwa beberapa kode perlu dieksekusi terlepas dari apakah kesalahan terjadi atau tidak. Untuk melakukan ini, Anda dapat menggunakan blok ketiga, opsional,
finally
di
try...catch
construct. Seringkali penggunaannya setara dengan beberapa kode yang datang segera setelah
try...catch
, tetapi dalam beberapa situasi dapat berguna. Berikut ini adalah contoh penggunaannya.
const a = 5 try { console.log(b)
▍ Mekanisme tidak sinkron - panggilan balik
Saat memprogram dalam JavaScript, Anda harus selalu memperhatikan bagian kode yang berjalan secara tidak sinkron. Jika Anda memiliki fungsi asinkron dan ada kesalahan di dalamnya, skrip akan terus berjalan. Ketika mekanisme asinkron di JS diimplementasikan menggunakan panggilan balik (omong-omong, ini tidak dianjurkan), panggilan balik yang sesuai (fungsi panggilan balik) biasanya menerima dua parameter. Ini adalah sesuatu seperti parameter
err
, yang mungkin mengandung kesalahan, dan
result
- dengan hasil operasi asinkron. Itu terlihat seperti ini:
myAsyncFunc(someInput, (err, result) => { if(err) return console.error(err)
Jika terjadi kesalahan dalam panggilan balik, itu terlihat di sana sebagai parameter
err
. Jika tidak, parameter ini akan mendapatkan nilai
undefined
atau
null
. Jika ternyata
err
sesuatu, penting untuk merespons ini karena dalam contoh kita, menggunakan perintah
return
, atau menggunakan
if...else
dan menempatkan perintah di blok
else
untuk bekerja dengan hasil operasi asinkron. Intinya adalah, jika terjadi kesalahan, mengecualikan kemungkinan bekerja dengan hasil, parameter
result
, yang dalam hal ini mungkin tidak
undefined
. Bekerja dengan nilai ini, jika diasumsikan, misalnya, bahwa itu berisi objek, itu sendiri dapat menyebabkan kesalahan. Katakanlah ini terjadi ketika Anda mencoba menggunakan
result.data
atau
result.data
mirip dengannya.
Mechanisms Mekanisme tidak sinkron - janji
Untuk melakukan operasi asinkron dalam JavaScript, lebih baik menggunakan janji daripada panggilan balik. Di sini, selain meningkatkan keterbacaan kode, ada mekanisme penanganan kesalahan yang lebih maju. Yaitu, Anda tidak perlu repot dengan objek kesalahan yang mungkin jatuh ke fungsi panggilan balik saat menggunakan janji. Di sini untuk tujuan ini disediakan blok
catch
khusus. Itu mencegat semua kesalahan yang terjadi dalam janji-janji yang ada sebelumnya, atau semua kesalahan yang terjadi dalam kode setelah
catch
sebelumnya. Harap perhatikan bahwa jika terjadi kesalahan dalam janji yang tidak memiliki blok
catch
untuk diproses, ini tidak akan menghentikan skrip dari mengeksekusi, tetapi pesan kesalahan tidak akan dapat dibaca secara khusus.
(node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong (node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */
Akibatnya, Anda selalu dapat merekomendasikan, saat bekerja dengan janji, untuk menggunakan
catch
. Lihatlah sebuah contoh.
Promise.resolve(1) .then(res => { console.log(res)
▍ Mekanisme tidak sinkron dan coba ... tangkap
Setelah konstruk
async/await
muncul di JavaScript, kami kembali ke cara klasik menangani kesalahan - untuk
try...catch...finally
. Menangani kesalahan dengan pendekatan ini sangat mudah dan nyaman. Pertimbangkan sebuah contoh.
;(async function() { try { await someFuncThatThrowsAnError() } catch (err) { console.error(err)
Dengan pendekatan ini, kesalahan dalam kode asinkron ditangani dengan cara yang sama seperti di sinkron. Akibatnya, sekarang, jika perlu, dalam satu
catch
Anda dapat menangani berbagai kesalahan yang lebih luas.
2. Menghasilkan dan memproses kesalahan dalam kode server
Sekarang kita memiliki alat untuk mengatasi kesalahan, mari kita lihat apa yang bisa kita lakukan dengan mereka dalam situasi nyata. Menghasilkan dan menangani kesalahan dengan benar adalah aspek penting dari pemrograman sisi server. Ada beberapa pendekatan untuk mengatasi kesalahan. Di sini kita akan mendemonstrasikan pendekatan menggunakan konstruktor kita sendiri untuk contoh objek
Error
dan kode kesalahan yang mudah diteruskan ke front-end atau mekanisme apa pun yang menggunakan API server. Bagaimana backend dari proyek tertentu terstruktur tidak terlalu penting, karena dengan pendekatan apa pun Anda dapat menggunakan ide yang sama tentang bekerja dengan kesalahan.
Sebagai kerangka kerja server yang bertanggung jawab untuk perutean, kami akan menggunakan Express.js. Mari kita pikirkan struktur apa yang kita butuhkan untuk mengatur sistem penanganan kesalahan yang efektif. Jadi inilah yang kami butuhkan:
- Penanganan kesalahan universal adalah beberapa mekanisme dasar yang cocok untuk menangani kesalahan, di mana pesan seperti
Something went wrong, please try again or contact us
, meminta pengguna untuk mencoba melakukan operasi yang gagal, lagi atau menghubungi pemilik server. Sistem ini tidak terlalu cerdas, tetapi setidaknya ia dapat memberi tahu pengguna bahwa ada kesalahan. Pesan semacam itu jauh lebih baik daripada "unduhan tanpa akhir" atau yang serupa. - Memproses kesalahan khusus - mekanisme yang memungkinkan Anda untuk memberi tahu pengguna informasi terperinci tentang penyebab perilaku sistem yang tidak tepat dan memberinya saran khusus tentang cara menangani masalah tersebut. Misalnya, ini mungkin berhubungan dengan tidak adanya beberapa data penting dalam permintaan yang dikirim pengguna ke server, atau bahwa sudah ada catatan tertentu dalam database yang ia coba tambahkan lagi, dan seterusnya.
▍ Mengembangkan konstruktor sendiri objek kesalahan
Di sini kita akan menggunakan kelas
Error
standar dan memperluasnya. Menggunakan mekanisme pewarisan dalam JavaScript berisiko, tetapi dalam kasus ini, mekanisme ini sangat berguna. Mengapa kita membutuhkan warisan? Faktanya adalah agar kita dapat dengan mudah men-debug kode, kita memerlukan informasi tentang jejak tumpukan kesalahan. Memperluas kelas
Error
standar, kami mendapatkan kemampuan untuk melacak tumpukan tanpa usaha ekstra. Kami menambahkan dua properti ke objek kesalahan kami sendiri. Yang pertama adalah properti
code
, yang dapat diakses menggunakan struktur form
err.code
. Yang kedua adalah properti
status
. Ini akan merekam kode status HTTP, yang rencananya akan dikirim ke bagian klien dari aplikasi.
Inilah yang tampak seperti kelas
CustomError
, yang kodenya dirancang sebagai modul.
class CustomError extends Error { constructor(code = 'GENERIC', status = 500, ...params) { super(...params) if (Error.captureStackTrace) { Error.captureStackTrace(this, CustomError) } this.code = code this.status = status } } module.exports = CustomError
▍ Routing
Sekarang objek kesalahan kita siap digunakan, kita perlu mengkonfigurasi struktur rute. Seperti disebutkan di atas, kita perlu menerapkan pendekatan terpadu untuk penanganan kesalahan, yang memungkinkan kita menangani kesalahan secara sama untuk semua rute. Secara default, kerangka kerja Express.js tidak sepenuhnya mendukung skema semacam itu. Faktanya adalah bahwa semua rutenya dienkapsulasi.
Untuk mengatasi masalah ini, kita dapat mengimplementasikan pengendali rute kita sendiri dan mendefinisikan logika rute dalam bentuk fungsi biasa. Berkat pendekatan ini, jika fungsi rute (atau fungsi lainnya) melempar kesalahan, itu akan jatuh ke pengendali rute, yang kemudian dapat meneruskannya ke bagian klien dari aplikasi. Jika terjadi kesalahan pada server, kami berencana untuk mentransfernya ke front-end dalam format berikut, dengan asumsi bahwa JSON-API akan digunakan untuk ini:
{ error: 'SOME_ERROR_CODE', description: 'Something bad happened. Please try again or contact support.' }
Jika pada tahap ini apa yang terjadi tampaknya tidak dapat dipahami oleh Anda - jangan khawatir - teruslah membaca, cobalah bekerja dengan apa yang sedang dibahas, dan secara bertahap Anda akan mengetahuinya. Bahkan, jika kita berbicara tentang pelatihan komputer, pendekatan "top-down" digunakan di sini, ketika ide-ide umum pertama kali dibahas dan kemudian transisi ke rincian dilakukan.
Seperti inilah kode penanganan rute.
const express = require('express') const router = express.Router() const CustomError = require('../CustomError') router.use(async (req, res) => { try { const route = require(`.${req.path}`)[req.method] try { const result = route(req)
Kami percaya komentar dalam kode menjelaskannya dengan baik. Kami berharap bahwa membacanya lebih nyaman daripada penjelasan kode yang diberikan setelahnya.
Sekarang lihat file rute.
const CustomError = require('../CustomError') const GET = req => {
Dalam contoh ini, tidak ada yang dilakukan dengan kueri itu sendiri. Ini hanya mempertimbangkan berbagai skenario untuk terjadinya kesalahan. Jadi, misalnya, permintaan
GET /city
akan jatuh ke fungsi
const GET = req =>...
, permintaan
POST /city
akan jatuh ke fungsi
const POST = req =>...
dan seterusnya. Skema ini juga berfungsi saat menggunakan parameter kueri. Misalnya, untuk permintaan formulir
GET /city?startsWith=R
Secara umum, telah ditunjukkan di sini bahwa saat memproses kesalahan, frontend bisa mendapatkan kesalahan umum yang hanya berisi penawaran untuk mencoba lagi atau menghubungi pemilik server, atau kesalahan yang dihasilkan menggunakan konstruktor
CustomError
yang berisi informasi terperinci tentang masalah tersebut.
Data kesalahan umum akan datang ke bagian klien dari aplikasi dalam bentuk berikut:
{ error: 'GENERIC', description: 'Something went wrong. Please try again or contact support.' }
Konstruktor
CustomError
digunakan seperti ini:
throw new CustomError('MY_CODE', 400, 'Error description')
Ini memberikan kode JSON berikut yang diteruskan ke frontend:
{ error: 'MY_CODE', description: 'Error description' }
Sekarang kita telah benar-benar bekerja pada bagian server dari aplikasi, log kesalahan yang tidak berguna tidak lagi jatuh ke dalam bagian klien. Sebagai gantinya, klien menerima informasi berguna tentang apa yang salah.
Jangan lupa bahwa di
sinilah letak repositori dengan kode yang dipertimbangkan di sini. Anda dapat mengunduhnya, bereksperimen dengannya, dan, jika perlu, menyesuaikannya dengan kebutuhan proyek Anda.
3. Bekerja dengan kesalahan pada klien
Sekarang saatnya untuk menggambarkan bagian ketiga dari sistem penanganan kesalahan front-end kami. Di sini perlu, pertama, untuk menangani kesalahan yang terjadi di bagian klien dari aplikasi, dan kedua, perlu untuk memberi tahu pengguna tentang kesalahan yang terjadi di server. Kami pertama-tama akan berurusan dengan menampilkan informasi kesalahan server. Seperti yang telah disebutkan, perpustakaan Bereaksi akan digunakan dalam contoh ini.
▍ Simpan informasi kesalahan dalam keadaan aplikasi
Seperti data lainnya, kesalahan dan pesan kesalahan dapat berubah, jadi masuk akal untuk menempatkannya dalam keadaan komponen. Ketika komponen dipasang, data kesalahan diatur ulang, oleh karena itu, ketika pengguna pertama kali melihat halaman, tidak akan ada pesan kesalahan.
Hal berikutnya yang harus diatasi adalah bahwa kesalahan dengan tipe yang sama perlu ditunjukkan dengan gaya yang sama. Dengan analogi dengan server, di sini Anda dapat membedakan 3 jenis kesalahan.
- Kesalahan global - kategori ini mencakup pesan kesalahan yang bersifat umum yang berasal dari server, atau kesalahan yang, misalnya, terjadi jika pengguna tidak masuk ke sistem dalam situasi serupa lainnya.
- Kesalahan spesifik yang dihasilkan oleh sisi server dari aplikasi - ini termasuk kesalahan yang dilaporkan dari server. Misalnya, kesalahan serupa terjadi jika pengguna mencoba masuk dan mengirim nama pengguna dan kata sandi ke server, dan server memberi tahu dia bahwa kata sandi itu salah. Hal-hal seperti itu tidak dicentang di bagian klien dari aplikasi, jadi pesan tentang kesalahan seperti itu harus datang dari server.
- Kesalahan spesifik yang dihasilkan oleh bagian klien dari aplikasi. Contoh kesalahan tersebut adalah pesan tentang alamat email yang tidak valid yang dimasukkan di bidang yang sesuai.
Kesalahan tipe kedua dan ketiga sangat mirip, Anda dapat bekerja dengannya menggunakan state store komponen dari level yang sama. Perbedaan utama mereka adalah bahwa mereka berasal dari sumber yang berbeda. Di bawah ini, menganalisis kode, kita akan melihat bekerja dengan mereka.
Ini akan menggunakan sistem bawaan untuk mengelola keadaan aplikasi di Bereaksi, tetapi, jika perlu, Anda dapat menggunakan solusi khusus untuk mengelola negara - seperti MobX atau Redux.
▍ Kesalahan global
Biasanya, pesan kesalahan tersebut disimpan di komponen tingkat tertinggi dengan status. Mereka ditampilkan dalam elemen antarmuka pengguna statis. Ini bisa berupa kotak merah di bagian atas layar, jendela modal, atau apa pun. Implementasinya tergantung pada proyek spesifik. Seperti inilah tampilan pesan kesalahan.
Pesan kesalahan globalSekarang lihat kode yang disimpan dalam file
Application.js
.
import React, { Component } from 'react' import GlobalError from './GlobalError' class Application extends Component { constructor(props) { super(props) this.state = { error: '', } this._resetError = this._resetError.bind(this) this._setError = this._setError.bind(this) } render() { return ( <div className="container"> <GlobalError error={this.state.error} resetError={this._resetError} /> <h1>Handling Errors</h1> </div> ) } _resetError() { this.setState({ error: '' }) } _setError(newError) { this.setState({ error: newError }) } } export default Application
, ,
Application.js
, . , .
GlobalError
,
x
, .
GlobalError
(
GlobalError.js
).
import React, { Component } from 'react' class GlobalError extends Component { render() { if (!this.props.error) return null return ( <div style={{ position: 'fixed', top: 0, left: '50%', transform: 'translateX(-50%)', padding: 10, backgroundColor: '#ffcccc', boxShadow: '0 3px 25px -10px rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', }} > {this.props.error} <i className="material-icons" style={{ cursor: 'pointer' }} onClick={this.props.resetError} > close </font></i> </div> ) } } export default GlobalError
if (!this.props.error) return null
. , . . , , , . , ,
x
, - , .
, ,
_setError
Application.js
. , , , , (
error: 'GENERIC'
). (
GenericErrorReq.js
).
import React, { Component } from 'react' import axios from 'axios' class GenericErrorReq extends Component { constructor(props) { super(props) this._callBackend = this._callBackend.bind(this) } render() { return ( <div> <button onClick={this._callBackend}>Click me to call the backend</button> </div> ) } _callBackend() { axios .post('/api/city') .then(result => {
, . , , . . -, , -, UX-, , — .
▍ ,
, , , .
, . . .
SpecificErrorReq.js
.
import React, { Component } from 'react' import axios from 'axios' import InlineError from './InlineError' class SpecificErrorRequest extends Component { constructor(props) { super(props) this.state = { error: '', } this._callBackend = this._callBackend.bind(this) } render() { return ( <div> <button onClick={this._callBackend}>Delete your city</button> <InlineError error={this.state.error} /> </div> ) } _callBackend() { this.setState({ error: '', }) axios .delete('/api/city') .then(result => { // - , }) .catch(err => { if (err.response.data.error === 'GENERIC') { this.props.setError(err.response.data.description) } else { this.setState({ error: err.response.data.description, }) } }) } } export default SpecificErrorRequest
, , ,
x
. , , . , , — , , , . , , . , , , — .
▍,
, , , . , , - . .
,SpecificErrorFrontend.js
, .
import React, { Component } from 'react' import axios from 'axios' import InlineError from './InlineError' class SpecificErrorRequest extends Component { constructor(props) { super(props) this.state = { error: '', city: '', } this._callBackend = this._callBackend.bind(this) this._changeCity = this._changeCity.bind(this) } render() { return ( <div> <input type="text" value={this.state.city} style={{ marginRight: 15 }} onChange={this._changeCity} /> <button onClick={this._callBackend}>Delete your city</button> <InlineError error={this.state.error} /> </div> ) } _changeCity(e) { this.setState({ error: '', city: e.target.value, }) } _validate() { if (!this.state.city.length) throw new Error('Please provide a city name.') } _callBackend() { this.setState({ error: '', }) try { this._validate() } catch (err) { return this.setState({ error: err.message }) } axios .delete('/api/city') .then(result => { // - , }) .catch(err => { if (err.response.data.error === 'GENERIC') { this.props.setError(err.response.data.description) } else { this.setState({ error: err.response.data.description, }) } }) } } export default SpecificErrorRequest
▍
, , (
GENERIC
), , . , , , , , , , , . .
Ringkasan
Kami harap Anda sekarang memiliki pemahaman tentang cara menangani kesalahan dalam aplikasi web. Sesuatu seperti ini console.error(err)
harus digunakan hanya untuk keperluan debugging, hal-hal seperti itu dilupakan oleh programmer tidak boleh menembus ke produksi. Menyederhanakan solusi untuk masalah pencatatan menggunakan beberapa perpustakaan yang sesuai seperti loglevel .Pembaca yang budiman! Bagaimana Anda menangani kesalahan dalam proyek Anda?