MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Procesarea erorilor și stiva apelurilor de funcții

Dacă în interiorul unei funcții apare o eroare care nu este tratată, interpretorul JavaScript iese din această funcție și revine la codul extern în căutarea unui gestionar de erori. De exemplu:

function A(){
   console.log("func A starts");
   callSomeFunc();
   console.log("func A ends");
}
console.log("program ends");

Aici, funcția A apelează o funcție nedefinită callSomeFunc(). Prin urmare, la executarea programului, când funcția A este apelată, aceasta este întreruptă. Interpretorul iese în codul extern în căutarea unui gestionar de erori.

Totuși, în codul extern în jurul apelului funcției A nu există nicio structură try..catch definită, așa că întreaga execuție a programului se va încheia în mod necorespunzător. Ieșirea la consolă:

func A starts
Uncaught ReferenceError: callSomeFunc is not defined
    at A (index.html:11:2)
    at index.html:30:6

Aceeași situație se aplică și pentru apelurile de funcții înglobate. Dacă într-o funcție înglobată apare o eroare și nu este tratată, interpretorul iese în contextul extern - funcția exterioară.

Dacă eroarea nu este tratată nici în acea funcție, interpretorul iese din nou în contextul extern, până găsește un gestionar de erori. Dacă eroarea nu este tratată în nicio funcție sau în codul global, programul se încheie în mod necorespunzător.

De exemplu:

function A(){
   console.log("func A starts");
   callSomeFunc();
   console.log("func A ends");
}
function B(){
   console.log("func B starts");
   A();
   console.log("func B ends");
}
function C(){
   console.log("func C starts");
   B();
   console.log("func C ends");
}
C();
console.log("program ends");

Aici, funcția C este apelată, care apoi apelează funcția B, care la rândul său apelează funcția A, iar aceasta din urmă apelează o funcție inexistentă callSomeFunc. Ca rezultat, în funcția A apare o eroare.

Deoarece funcția A nu tratează eroarea, interpretorul caută în mod secvențial un gestionar de erori în funcția B, apoi în funcția C și, în final, în contextul global. Totuși, deoarece niciunde eroarea nu este tratată, executarea programului se va încheia după apariția erorii:

func C starts
func B starts
func A starts
Uncaught ReferenceError: callSomeFunc is not defined
    at A (index.html:11:2)
    at B (index.html:16:2)
    at C (index.html:27:2)
    at index.html:31:1

Acum vom defini un gestionar de erori în una dintre funcții, de exemplu, în funcția C:

function A(){
   console.log("func A starts");
   callSomeFunc();
   console.log("func A ends");
}
function B(){
   console.log("func B starts");
   A();
   console.log("func B ends");
}
function C(){
   console.log("func C starts");
   try{
       B();
   }
   catch{
       console.log("Error occured");
   }
   console.log("func C ends");
}

C();
console.log("program ends");

În acest caz, când apare o eroare în funcția A, interpretorul caută un gestionar de erori în acea funcție. Fiindcă funcția A nu tratează eroarea, interpretorul se uită în funcția B și apoi în funcția C. În funcția C se găsește un gestionar de erori, astfel că programul continuă să ruleze după tratarea erorii.

func C starts
func B starts
func A starts
Error occured
func C ends
program ends

În acest context, deoarece funcțiile A și B nu tratează eroarea, ele nu mai continuă execuția.

Propagarea erorii în sus pe stiva apelurilor de funcții

Uneori, erorile sunt tratate într-unul din apelurile înglobate ale altor funcții. De exemplu, luăm următoarea situație:

// clasă pentru o bază de date condițională
class Database{
   constructor(){
       this.data = ["Tom", "Sam", "Bob"];
   }
   // obținerea datelor
   getItem(index){
       if(index > 0 && index < this.data.length)
           return this.data[index];
       else    // dacă indexul este incorect - generăm o eroare
           throw new RangeError("Index invalid");
   }
   // deschiderea bazei de date
   open(){
       console.log("Baza de date a fost deschisă");
   }
   // închiderea bazei de date
   close(){
       console.log("Baza de date a fost închisă");
   }
}
// funcție de înveliș pentru obținerea unui obiect din baza de date pe baza indexului
function get(index) {
 
   const db = new Database();
   db.open();  // deschidem condiționat baza de date
   try {
       return db.getItem(index);   // returnăm elementul obținut
   } catch(err) {
       console.error(err); // dacă apare o eroare, o gestionăm
   }
   db.close(); // închidem condiționat baza de date
}
// afișarea rezultatului
function printResult(){
   const item = get(5);    // încercăm să obținem elementul cu indexul 5
   console.log("Obținut din baza de date:", item); // afișăm elementul obținut pe consolă
}
printResult();

Aici este definită o clasă de bază a unei baze de date fictive numită Database. Pentru a interacționa cu datele, sunt definite trei funcții. Funcțiile simulate de deschidere și închidere a bazei de date - funcțiile open și close, respectiv - și funcția getItem, care returnează un element specificat prin index din array-ul de date.

Totuși, dacă indexul furnizat nu este corect - mai mic decât 0 sau mai mare decât indexul permis - atunci se generează o eroare de tip RangeError.

Mai trebuie menționat că aceasta nu este singura abordare pentru implementarea unui astfel de funcțional. Uneori, se folosește o altă strategie, în care, în cazul unui index sau ID incorect, funcția returnează null, ceea ce în anumite situații poate fi mai preferabil.

Însă, în acest caz, pentru scopuri demonstrative, ne oprim la generarea unei excepții în cazul unui index incorect.

Convenim că, pentru a interacționa cu baza de date, trebuie să o "deschidem" inițial cu ajutorul metodei open, iar după finalizarea lucrului cu ea, să o "închidem" cu ajutorul metodei close - o abordare destul de comună în lucrul cu bazele de date.

Totuși, pentru a abstractiza de toate aceste detalii, definim o funcție suplimentară numită get, care primește un ID și accesează baza de date pentru a obține elementul corespunzător ID-ului.

Și deoarece la apelul metodei getItem poate apărea o eroare, aceasta este gestionată într-o structură try..catch.

try {
   return db.getItem(index);   // returnăm elementul obținut
} catch(err) {
   console.error(err); // dacă apare o eroare, o gestionăm
}

Apoi, această funcție este apelată din altă funcție numită printResult, care afișează rezultatul obținut:

function printResult(){
   const item = get(5);    // încercăm să obținem elementul cu indexul 5
   console.log("Obținut din baza de date:", item); // afișăm elementul obținut pe consolă
}

Dacă privim la ieșirea programului:

Baza de date a fost deschisă
RangeError: Index invalid
    la Database.getItem (index.html:19:19)
    la get (index.html:36:19)
    la printResult (index.html:44:18)
    la index.html:47:1
get @ index.html:38
printResult @ index.html:44
(anonymous) @ index.html:47

Baza de date a fost închisă
Obținut din baza de date: undefined

Dacă observăm că atunci când se furnizează un index incorect, pe de o parte, eroarea este gestionată, dar pe de altă parte, în funcția printResult obținem un rezultat nedefinit (valoare undefined), iar noi nu avem idee despre ce s-a întâmplat.

Această informație poate fi obținută doar din ieșirea consolei în timpul execuției. Cu toate acestea, funcția get nu trebuie neapărat să afișeze eroarea în consolă; eroarea poate fi gestionată în alt mod.

Prin urmare, există nevoia de a transmite informații despre eroare în sus pe stiva de apeluri a funcțiilor (până la funcția printResult).

Pentru a realiza acest lucru, vom modifica codul pentru a propaga eroarea în sus pe stiva de apeluri a funcțiilor:

class Database{
   constructor(){
       this.data = ["Tom", "Sam", "Bob"];
   }
   
   getItem(index){
       if(index > 0 && index < this.data.length)
           return this.data[index];
       else
           throw new RangeError("Index invalid");
   }
   
   open(){
       console.log("Baza de date a fost deschisă");
   }
   
   close(){
       console.log("Baza de date a fost închisă");
   }
}

function get(index) {
   const db = new Database();
   db.open();
   try {
       return db.getItem(index);
   } catch(err) {
       console.error(err);
       throw new Error(err.message);   // generăm din nou aceeași eroare
   } finally {
       db.close();
   }
}

function printResult(){
   try {
       const item = get(5);
       console.log("Obținut din baza de date:", item);
   } catch(err) {
       console.log(err);   // gestionăm eroarea din funcția get
   }
}

printResult();

În funcția get, după gestionarea erorii, se generează din nou o eroare folosind operatorul throw:

try {
   return db.getItem(index);
} catch(err) {
   console.error(err);
   throw new Error(err.message);
}
finally{
   db.close();
}

De asemenea, merită de menționat că apelul db.close(), care în mod condiționat închide baza de date, este plasat în blocul finally. Acest lucru garantează că, chiar dacă se generează o eroare, operația respectivă va fi totuși executată.

Astfel, dacă a avut loc o eroare la apelul db.getItem, atunci când apelăm funcția get, va avea loc, de asemenea, o eroare, astfel încât putem gestiona această eroare în funcția printResult:

function printResult(){
   try {
       const item = get(5);
       console.log("Obținut din baza de date:", item);
   } catch(err) {
       console.log(err);
   }
}
← Lecția anterioară Lecția următoare →