Treci la conținutul principal

Programare asincronă în TypeScript

TypeScript adaugă tipuri statice funcționalităților asincrone din JavaScript. Astfel, codul asincron devine mai ușor de anticipat și de întreținut.

Această lecție pornește de la async/await de bază și ajunge la modele mai avansate.

Notă: Această lecție presupune că știi deja bazele Promise-urilor din JavaScript și ale programării asincrone. Dacă aceste idei sunt noi pentru tine, învață mai întâi despre async în JavaScript.


Promise-uri în TypeScript

TypeScript face Promise-urile sigure din punctul de vedere al tipurilor, prin generice.

Un Promise<T> este o operațiune care se va încheia mai târziu cu o valoare de tip T sau va eșua cu un motiv de tip any.

Puncte importante:

  • Promise<T> — tip generic în care T este tipul valorii rezolvate.
  • Promise<void> — pentru Promise-uri care nu întorc o valoare.
  • Promise<never> — pentru Promise-uri care nu se rezolvă niciodată (rar).

Stările unui Promise și fluxul tipurilor: un Promise pornește în starea pending și trece fie în fulfilled (cu o valoare de tip T), fie în rejected (cu un motiv de tip any). TypeScript urmărește aceste stări prin sistemul de tipuri, așa că tratezi atât cazul de succes, cât și cazul de eroare.

Parametrul de tip din Promise<T> îi spune lui TypeScript la ce se va rezolva Promise-ul, ceea ce oferă o verificare a tipurilor mai bună și un suport mai bun în editor.

Exemplu de bază cu Promise

fetchGreeting întoarce Promise<string>, așa că în interiorul lui .then TypeScript știe că greeting este un șir de caractere. Promise-ul se rezolvă sau se respinge aleatoriu, așa că rezultatul este fie salutul scris cu majuscule, fie linia cu eroarea.

Prompt
Creează o funcție săgeată fetchGreeting, tipizată să întoarcă Promise<string>. În interiorul ei, construiește un Promise care așteaptă o secundă cu setTimeout, apoi, aleatoriu (folosind Math.random() > 0.5), fie se rezolvă cu "Salut, TypeScript!", fie se respinge cu Error("Preluarea salutului a eșuat"). Consumă-l cu .then — afișând salutul scris cu majuscule, de vreme ce TypeScript știe că este un șir de caractere — și cu .catch, având eroarea tipizată ca Error, afișând "Eroare:" și mesajul ei. Rezultatul este aleatoriu: fie salutul scris cu majuscule, fie linia cu eroarea.
Ieșire: program TypeScript și rezultatul capturat

Async/Await cu TypeScript

Sintaxa async/await este un mod mai curat de a lucra cu Promise-uri. Codul asincron se citește ca un cod obișnuit, pas cu pas, iar TypeScript continuă să verifice tipurile.

Principalele avantaje ale async/await:

  • Lizibilitate: cod secvențial, mai ușor de urmărit.
  • Tratarea erorilor: folosești try/catch atât pentru erorile sincrone, cât și pentru cele asincrone.
  • Depanare: urmele de stivă arată ca cele sincrone.
  • Siguranța tipurilor: inferență și verificare complete în TypeScript.

Tipurile de retur ale funcțiilor async. Orice funcție async întoarce un Promise, iar tipul de retur declarat trebuie să fie învelit într-unul: async function getString(): string { } este o eroare, în timp ce async function getString(): Promise<string> { } este corect.

Notă: Parametrul de tip al Promise-ului întors este tipul pe care îl scrii după Promise în tipul de retur.

Exemplu de bază cu async/await

fetchUsers întoarce Promise<User[]>, așa că după await variabila users este de tip User[], iar accesul la proprietățile fiecărui utilizator este verificat de tipuri.

Prompt
Definește o interfață User (id, name, email și role limitat la 'admin' | 'user' | 'guest') și o async function fetchUsers(): Promise<User[]> care afișează 'Se preiau utilizatorii...', așteaptă cu await un setTimeout de o secundă pentru a simula un apel de API, apoi întoarce pe Alice (admin) și pe Bob (user). Scrie async function processUsers() care așteaptă cu await rezultatul lui fetchUsers() într-un try/catch, afișează câți utilizatori au fost preluați, filtrează administratorii și îi transformă în adresele lor de email, afișează 'Emailurile administratorilor:' împreună cu acel tablou și rearuncă orice eroare după ce o afișează (verificând error instanceof Error). La final, apelează processUsers() și afișează 'Procesare finalizată' în caz de succes sau 'Procesarea a eșuat:' în caz de eșec.
Ieșire: program TypeScript și rezultatul capturat

Execuție paralelă cu Promise.all

Rulează mai multe operațiuni asincrone în paralel și așteaptă ca toate să se încheie. Cele trei produse sunt preluate în același timp, iar prețurile lor aleatorii sunt adunate.

Prompt
Definește o interfață Product (id, name, price) și o async function fetchProduct(id: number): Promise<Product> care afișează Se preia produsul <id>..., așteaptă un timp aleatoriu de până la o secundă și întoarce un produs numit Produsul <id>, cu un preț aleatoriu, număr întreg, mai mic de 100. Scrie fetchMultipleProducts(), care pornește fetchProduct(1), (2) și (3) în paralel cu Promise.all, destructurează cele trei rezultate, adună prețurile lor și afișează Preț total: $<total cu două zecimale>; afișează orice eroare cu 'Eroare la preluarea produselor:'. Apelează funcția. Totalul este aleatoriu.
Ieșire: program TypeScript și rezultatul capturat

Tipizarea callback-urilor pentru operațiuni asincrone

Pentru codul asincron mai vechi, scris în stil callback, TypeScript te ajută să tipizezi parametrii callback-ului, astfel încât apelantul să știe exact ce primește.

Exemplu

Tipul FetchCallback spune că acel callback primește un Error sau null, plus date opționale de tip șir de caractere. În interiorul callback-ului, TypeScript știe că data este un șir de caractere sau undefined.

Prompt
Definește un alias de tip FetchCallback pentru un callback (error: Error | null, data?: string) => void și o funcție fetchDataWithCallback(url: string, callback: FetchCallback): void care simulează o operațiune asincronă cu un setTimeout de o secundă, apelând callback-ul cu null și "Date de răspuns" (sau cu un Error, dacă apare o excepție). Apeleaz-o cu 'https://api.example.com' și cu un callback care afișează 'Eroare:' și se întoarce în caz de eroare, iar altfel afișează datele scrise cu majuscule atunci când sunt definite.
Ieșire: program TypeScript și rezultatul capturat

Combinații de Promise-uri

TypeScript îți oferă versiuni tipizate ale metodelor care combină mai multe Promise-uri. Ele te ajută să rulezi operațiuni concurent și să tratezi rezultatele în siguranță.

Metode de combinare a Promise-urilor:

  • Promise.all() — așteaptă ca toate promise-urile să se rezolve.
  • Promise.race() — întoarce primul promise stabilizat.
  • Promise.allSettled() — așteaptă ca toate să se stabilizeze (succes sau eșec).
  • Promise.any() — întoarce primul promise îndeplinit.

Atenție: Când îi dai lui Promise.all promise-uri de tipuri diferite, TypeScript deduce un tuplu cu tipurile rezultatelor individuale. Dacă tipul dedus nu este destul de precis, folosește o aserțiune de tip sau definește tu structura așteptată.

Promise.all - execuție paralelă

Rulează mai multe promise-uri în paralel și le așteaptă pe toate. Eșuează imediat dacă vreun promise este respins. Aici user, posts și stats primesc fiecare propriul tip precis din tuplul rezultat.

Prompt
Creează trei funcții săgeată tipizate care întorc Promise-uri deja rezolvate: fetchUser(id), care dă { id, name: "Utilizatorul <id>" }, fetchPosts(userId), care dă două postări (Postarea 1, Postarea 2) cu id-uri, și fetchStats(userId), care dă { views: 100, likes: 25 }. Scrie async function loadUserDashboard(userId: number) care așteaptă cu await toate cele trei prin Promise.all, afișează Utilizator: <name>, Postări: <count> și Aprecieri: <likes>, întoarce cele trei valori într-un obiect și afișează 'Încărcarea tabloului de bord a eșuat:' înainte de a rearunca eroarea. Apeleaz-o cu id-ul de utilizator 1.
Ieșire: program TypeScript și rezultatul capturat

Promise.race - primul stabilizat

Util pentru expirări sau pentru a lua primul răspuns reușit dintre mai multe surse. fetchWithTimeout pune un promise în cursă cu un cronometru care respinge. Funcțiile sunt doar definite, așa că nu se afișează nimic.

Prompt
Scrie o funcție ajutătoare timeout(ms: number): Promise<never> care se respinge cu Error("Expirare după <ms>ms") după ms milisecunde, și o funcție generică async function fetchWithTimeout<T>(promise: Promise<T>, timeoutMs = 5000): Promise<T> care pune promise-ul dat în cursă cu expirarea, aruncând Cererea a expirat după <ms>ms dacă expirarea câștigă. Apoi definește (dar nu apela) fetchUserData(), care folosește fetchWithTimeout în jurul lui fetch('https://api.example.com/user/1') cu o limită de 3 secunde, parsează JSON-ul, iar în caz de eroare afișează 'Eroare:' împreună cu mesajul (convertit la Error) și rearuncă eroarea. Nu se afișează nimic, pentru că nicio funcție nu este apelată.
Ieșire: program TypeScript și rezultatul capturat

Promise.allSettled - tratarea tuturor rezultatelor

Folosește-l când vrei să aștepți fiecare promise, fie că reușește, fie că eșuează. Fiecare rezultat este verificat cu un predicat de tip înainte de a fi numărat ca reușit sau eșuat. Unele cereri eșuează aleatoriu, așa că numerele variază.

Prompt
Creează o funcție asincronă fetchData(id: number) care aruncă Error("Preluarea datelor pentru ID-ul <id> a eșuat") atunci când Math.random() > 0.7 și altfel întoarce { id, data: "Date pentru <id>" }. Scrie async function processBatch(ids: number[]) care transformă fiecare id în fetchData(id), învelit astfel încât să se rezolve cu { status: 'fulfilled' as const, value } sau cu { status: 'rejected' as const, reason }, așteaptă cu await un Promise.allSettled pe ele, separă rezultatele reușite de cele eșuate prin filtre cu predicate de tip (PromiseFulfilledResult / PromiseRejectedResult), afișează Procesate cu succes: <n> și Eșuate: <n> și întoarce ambele liste. Apeleaz-o cu id-urile de la 1 la 5. Numerele sunt aleatorii.
Ieșire: program TypeScript și rezultatul capturat

Tratarea erorilor în codul asincron

TypeScript îți oferă instrumente bune pentru tratarea erorilor în siguranță din punctul de vedere al tipurilor, în codul asincron. Iată câteva modele și bune practici.

Strategii de tratare a erorilor:

  • Blocuri try/catch: pentru tratarea erorilor în async/await.
  • Error boundaries: pentru componentele React.
  • Tipuri de tip Result: o abordare funcțională, cu valori de succes/eșec.
  • Subclase de erori: pentru erori specifice domeniului.

Modele de tratare a erorilor pe care merită să le ai în vedere:

  1. Error boundaries în React, pentru tratare la nivel de componentă.
  2. Obiecte Result în locul aruncării erorilor pentru cazurile așteptate.
  3. Gestionar global de erori pentru excepțiile netratate.
  4. Jurnalizarea erorilor pentru a captura și a raporta erorile.

Clase de eroare personalizate

Creează tipuri de eroare specifice domeniului pentru o tratare mai bună a erorilor. AppError este clasa de bază, iar NetworkError, ValidationError și NotFoundError poartă fiecare un cod. Un mockFetch local ține locul lui fetch, astfel încât exemplul să ruleze fără rețea.

Prompt
Construiește o mică ierarhie de erori: o clasă de bază AppError extends Error, al cărei constructor primește message, un public readonly code: string și un public readonly details?: unknown opțional, setează this.name la numele clasei și captează urma stivei atunci când este disponibilă; apoi NetworkError (cod 'NETWORK_ERROR'), ValidationError (cod 'VALIDATION_ERROR', cu un public readonly field) și NotFoundError (cod 'NOT_FOUND', mesaj <resource> cu ID-ul <id> nu a fost găsit). Scrie async function fetchUserData(userId: string): Promise<{ id: string; name: string }> care așteaptă cu await un mockFetch local (un Promise care se rezolvă cu un răspuns fals { ok, status, json() }, astfel încât rețeaua să nu fie atinsă), aruncă NotFoundError la 404, NetworkError la 5xx, o eroare simplă Error pentru alte statusuri care nu sunt ok și ValidationError('name', ...) atunci când JSON-ul nu are name; erorile neașteptate sunt învelite într-un AppError cu codul 'UNEXPECTED_ERROR'. Adaugă displayUserProfile(userId), care afișează 'Profilul utilizatorului:' împreună cu utilizatorul sau afișează prin console.error un prefix diferit (Problemă de rețea:, Validarea a eșuat:, Nu a fost găsit:, Eroare neașteptată:) pentru fiecare clasă de eroare, și apeleaz-o cu '123'.
Ieșire: program TypeScript și rezultatul capturat

Iterare asincronă cu TypeScript

TypeScript acceptă iteratori asincroni și generatoare asincrone cu tipizare completă. O buclă for await extrage valorile una câte una, pe măsură ce devin disponibile.

Exemplu

generateNumbers este tipizată ca AsyncGenerator<number, void, unknown>, așa că în interiorul buclei for await variabila num este un număr. Aici funcțiile sunt doar definite, așa că nu se afișează nimic.

Prompt
Scrie un generator asincron generateNumbers() tipizat ca AsyncGenerator<number, void, unknown>, care produce valorile de la 0 la 4, așteptând o secundă (printr-un setTimeout într-un Promise așteptat cu await) înainte de fiecare valoare. Adaugă async function consumeNumbers(), care folosește for await peste generator și afișează fiecare număr dublat. Doar definește funcțiile; nimic nu este apelat, așa că nu se afișează nimic.
Ieșire: program TypeScript și rezultatul capturat