PorównaniePorównania

void vs never

void oznacza, że funkcja nie zwraca użytecznej wartości, a never, że funkcja nigdy nie wraca: rzuca błąd albo działa w nieskończoność.

Na tej stronie

Porównanie w tabeli

#
  • Funkcja kończy się normalnie

    void
    Tak
    never
    Nie, rzuca błąd albo działa w nieskończoność
  • Rzeczywisty wynik w runtime

    void
    undefined
    never
    Brak, funkcja nigdy nie wraca
  • Można przypisać wartość tego typu do zmiennej

    void
    Tak, tylko undefined
    never
    Nie, żadna wartość nie pasuje
  • Sprawdzanie kompletności unii

    void
    Nie wyraża tego
    never
    Tak, w gałęzi default
  • Typ funkcji zwrotnej (callback)

    void
    Luźny: przyjmie też funkcję, która coś zwraca
    never
    Rzadko używany w tej roli
  • Typowe użycie

    void
    Funkcje bez użytecznego wyniku, obsługi zdarzeń
    never
    Funkcje rzucające błąd, assertNever, pętle bez końca

Przykład

#
TypeScript
function logProgress(done: number, total: number): void {
  console.log(`Postęp: ${Math.round((done / total) * 100)}%`)
}

function fail(message: string): never {
  throw new Error(message)
}

const result = logProgress(3, 4)
console.log(result)

try {
  fail('coś poszło nie tak')
} catch (error) {
  console.log((error as Error).message)
}
Wynik
Postęp: 75%
undefined
coś poszło nie tak

Definicja i zastosowanie

#

Oba typy opisują najczęściej wartość zwracaną przez funkcję i używa się ich tam, gdzie zwykła wartość nie ma sensu. Żaden z nich nie pozwala na sensowne dalsze operacje na wyniku.

Różnica jest zasadnicza. Funkcja z typem void normalnie się kończy, po prostu nie zwraca niczego użytecznego, a w praktyce jej wynikiem jest undefined. Funkcja z typem never nigdy nie wraca do miejsca wywołania: albo rzuca wyjątek, albo działa w nieskończonej pętli. never pojawia się też jako typ wartości, które nie mogą wystąpić, np. w gałęzi default po obsłużeniu wszystkich wariantów unii, czego void nigdy nie wyraża.

Typ void dopisuj do funkcji, które wykonują efekt, np. logują, zapisują albo zmieniają stan, i których wyniku nikt nie powinien używać. Typ never zostaw dla funkcji pomocniczych, które zawsze rzucają błąd, i dla sprawdzania kompletności unii w switch. Nigdy nie używaj never jako typu zmiennej, do której chcesz coś przypisać, bo żadna wartość do niego nie pasuje.

Składnia

#
Składnia
function nazwa(): void { … }
function fail(): never { throw new Error() }

Więcej przykładów

#
never przy sprawdzaniu kompletności unii
TypeScript
type Command = { kind: 'start' } | { kind: 'stop' }

function assertNever(value: never): never {
  throw new Error(`Nieznana komenda: ${JSON.stringify(value)}`)
}

function run(command: Command): string {
  switch (command.kind) {
    case 'start':
      return 'Start'
    case 'stop':
      return 'Stop'
    default:
      return assertNever(command)
  }
}

console.log(run({ kind: 'start' }))
console.log(run({ kind: 'stop' }))
Wynik
Start
Stop
void jako typ zwrotny jest luźniejszy niż never
TypeScript
const log: (message: string) => void = (message) => {
  console.log(message.toUpperCase())
  return message.length
}

log('html')
Wynik
HTML

Mimo że przekazana funkcja realnie coś zwraca, typ (message: string) => void każe to zignorować. Dzięki temu funkcje jak arr.forEach(x => list.push(x)) są poprawne, choć push() zwraca liczbę.

Dobre praktyki

#
  • Dopisuj void jawnie do funkcji, które wykonują efekt uboczny. To mówi czytelnikowi, że nie powinien używać wyniku.
  • Funkcja assertNever w gałęzi default to prosty sposób, żeby kompilator przypominał o nowych wariantach unii.
  • Nie próbuj zadeklarować zmiennej typu never, do której chcesz coś przypisać. Żadna wartość, nawet undefined, nie pasuje do tego typu.

Powiązane hasła

#

Widzisz błąd albo brakuje przykładu? Napisz do nas.