asserts în TypeScript

Narrowing și type guards TypeScript 3.7

O funcție asserts aruncă o eroare dacă verificarea eșuează; după apel, TypeScript știe că tipul este cel cerut.

Sintaxă
function verifica(x: unknown): asserts x is Tip { ... }
function asigura(conditie: unknown, mesaj: string): asserts conditie { ... }

Ce face asserts

O funcție de aserțiune verifică o condiție și aruncă o eroare dacă nu este îndeplinită. Tipul rezultatului se scrie cu asserts, iar TypeScript ține cont de el în codul de după apel: dacă funcția nu a aruncat, condiția este adevărată.

Există două forme. asserts conditie: după apel, expresia dată ca argument este adevărată (de exemplu asigura(x !== null) elimină null). asserts x is Tip: după apel, x are tipul Tip, ca la un predicat de tip, dar fără if.

Funcția trebuie să aibă tipul rezultatului void în practică (nu returnează nimic). Dacă o pui într-o variabilă (const f = ...), variabila trebuie să aibă tipul scris explicit, altfel tsc nu acceptă aserțiunea. Modulul node:assert din Node.js are deja funcții de acest fel.

Exemple

Exemplul 1: asserts conditie

function asigura(conditie: unknown, mesaj: string): asserts conditie {
    if (!conditie) throw new Error(mesaj);
}

function medie(note: number[] | undefined): number {
    asigura(note && note.length > 0, "Lista de note este goală");
    return note.reduce((s, n) => s + n, 0) / note.length;     // note este number[]
}

console.log(medie([9, 10, 8]));
try {
    medie([]);
} catch (e) {
    console.log((e as Error).message);
}

Rezultat

9
Lista de note este goală

Exemplul 2: asserts x is Tip pentru date externe

interface Configurare {
    port: number;
    oras: string;
}

function esteConfigurare(x: unknown): asserts x is Configurare {
    if (typeof x !== "object" || x === null) throw new TypeError("Configurarea trebuie să fie obiect");
    const c = x as Record<string, unknown>;
    if (typeof c.port !== "number") throw new TypeError("port lipsă sau greșit");
    if (typeof c.oras !== "string") throw new TypeError("oras lipsă");
}

for (const text of ['{"port": 8080, "oras": "Iași"}', '{"port": "80"}']) {
    try {
        const date: unknown = JSON.parse(text);
        esteConfigurare(date);
        console.log(`Pornesc pe portul ${date.port + 1} pentru ${date.oras}`);
    } catch (e) {
        console.log("Eroare:", (e as Error).message);
    }
}

Rezultat

Pornesc pe portul 8081 pentru Iași
Eroare: port lipsă sau greșit

Exemplul 3: Funcția din node:assert

assert.ok are deja tipul asserts value.

import assert from "node:assert";

function primaLitera(text: string | null): string {
    assert.ok(text, "textul lipsește");
    return text[0].toUpperCase();
}

console.log(primaLitera("iași"));
try {
    primaLitera(null);
} catch (e) {
    console.log((e as Error).message);
}

Rezultat

I
textul lipsește

Greșeli frecvente

  • Faci funcția de aserțiune să returneze boolean. Rezultatul nu contează; doar aruncarea erorii face aserțiunea corectă.
  • Declari aserțiunea ca funcție săgeată fără tip explicit pe variabilă. tsc dă Assertions require every name in the call target to be declared with an explicit type annotation.

Comenzi înrudite

  • predicat de tip (is): O funcție cu rezultatul x is Tip spune compilatorului că, dacă returnează true, x are tipul Tip.
  • type guards: Verificări la rulare pe care TypeScript le înțelege și după care știe tipul exact al unei valori.
  • never: Tipul fără nicio valoare: pentru funcții care nu se termină normal și pentru cazurile imposibile din cod.
  • narrowing: TypeScript urmărește verificările din cod (if, switch, return) și restrânge tipul unei variabile în fiecare ramură.