Если вы, до того, как заинтересовались JavaScript, писали на традиционных языках с сильной типизацией, то вы, возможно, знакомы с концепцией void. Это — тип, использование которого сообщает программисту о том, что соответствующие функции и методы при их вызове ничего не возвращают.



Сущность void имеется также в JavaScript и TypeScript. В JS это — оператор. В TS это примитивный тип данных. И там и там void ведёт себя не так, как могли бы ожидать многие из тех, кто сталкивался с void в других языках.

Оператор void в JavaScript


В JavaScript оператор void вычисляет переданное ему выражение. При этом, независимо от того, какое именно выражение вычисляется, void всегда возвращает undefined.

let i = void 2; // i === undefined

Зачем вообще нужен подобный оператор? 
Во-первых, надо отметить, что в ранние годы JS-программирования разработчики могли переопределять undefined и записывать в него какое-нибудь своё значение. А вот void всегда возвращает настоящее значение undefined.

Во-вторых — использование оператора void — это интересный способ работы с немедленно вызываемыми функциями:

void function() {
  console.log('What')
}()

И всё это — без загрязнения глобального пространства имён:

void function aRecursion(i) {
  if(i > 0) {
    console.log(i--)
    aRecursion(i)
  }
}(3)

console.log(typeof aRecursion) // undefined

Так как оператор void всегда возвращает undefined и всегда вычисляет переданное ему выражение, в нашем распоряжении оказывается весьма выразительный способ возвращения из функции без возврата какого-то значения, но с вызовом, например, некоего коллбэка:

// возврат чего-то кроме undefined приведёт к аварийной остановке приложения
function middleware(nextCallback) {
  if(conditionApplies()) {
    return void nextCallback();
  }
}

Это приводит нас к самому важному способу использования void. Данный оператор представляет собой нечто вроде «поста охраны» приложения. Если некая функция всегда должна возвращать undefined — обеспечить это можно, воспользовавшись оператором void.

button.onclick = () => void doSomething();

Тип данных void в TypeScript


Тип void в TypeScript можно назвать чем-то вроде противоположности типа any. Функции в JavaScript всегда что-то возвращают. Это может быть либо некое заданное программистом значение, либо undefined:

function iHaveNoReturnValue(i) {
  console.log(i)
} // возвращает undefined

Так как JavaScript функции, из которых явным образом ничего не возвращается, всегда возвращают undefined, void в TypeScript является подходящим типом, сообщающим разработчикам о том, что функция возвращает undefined:

declare function iHaveNoReturnValue(i: number): void

Сущность void в виде типа можно ещё использовать для параметров и для любых других объявлений переменных. Единственное значение, которое всегда можно передать void-параметру — это undefined.

declare function iTakeNoParameters(x: void): void

iTakeNoParameters() // ОК
iTakeNoParameters(undefined) // ОК
iTakeNoParameters(void 2) // ОК

В результате оказывается, что в TS типы void и undefined — это почти одно и то же. Но между ними есть одно маленькое различие, которое, на самом деле, чрезвычайно важно. Возвращаемый тип void может быть заменён на другие типы, что позволяет реализовывать продвинутые паттерны работы с коллбэками.

function doSomething(callback: () => void) {
  let c = callback() //здесь callback всегда возвращает undefined
  //тип c - тоже undefined
}

// эта функция возвращает число
function aNumberCallback(): number {
  return 2;
}

// работает; обеспечивается типобезопасность в doSometing
doSomething(aNumberCallback)

Разработчики ожидают от подобных конструкций, часто используемых в JS-приложениях, именно такого поведения. Вот материал на эту тему.

Если вы хотите, чтобы функция принимала бы лишь функции, которые возвращают undefined — вы можете соответствующим образом изменить сигнатуру метода:

// было 
// function doSomething(callback: () => void) {
// стало
function doSomething(callback: () => undefined) { /* ... */ }

function aNumberCallback(): number { return 2; }

// Ошибка - типы не совпадают
doSomething(aNumberCallback)

Итоги


Оператор void в JavaScript и тип данных void в TypeScript — сущности довольно простые и понятные. Круг ситуаций, в которых они применимы, ограничен. Однако надо отметить, что программист, который их использует, скорее всего, не столкнётся при работе с ними с какими-то проблемами.

Уважаемые читатели! Пользуетесь ли вы void в JS и TS?


Комментарии (15)


  1. soltpain
    30.09.2019 12:57

    что-то не совпадает с исходником про воиды, поясните пожалуйста
    let c = callback() //здесь callback всегда возвращает undefined

    const didItWork = callback(200, results); // ? compile error!


    1. PomanoB
      30.09.2019 23:44

      В статье просто неправильно написано: на самом деле calback(): void "возвращает" void, а не undefined
      И тип у c будет не undefined а void. Оно скомпилится, но ничего осмысленного сделать нельзя с переменной c, тип void никуда не присвоить не получится(кроме другого void).


  1. Akuma
    30.09.2019 13:24
    +1

    без загрязнения глобального пространства имён

    А что мешает сделать (function(){...})();?

    Если некая функция всегда должна возвращать undefined

    логично написать return undefined;


    1. pterodaktil
      30.09.2019 13:59

      логично написать return undefined;

      const fn = () => (doSomething(), undefined)

      так?


      1. Akuma
        30.09.2019 14:09

        Я имею ввиду тело самой функции.

        Как вы ее оформите — дело ваше. Стрелочные функции — просто сахар, суть от них не меняется. И, да, я предпочитаю ими не пользоваться в присваивании. Как-то глаза режет.


      1. Zenitchik
        30.09.2019 16:55

        const fn = () => {doSomething()}


    1. Gennadii_M
      30.09.2019 15:11

      На сколько я себе представляю, человек пишет

      return callback()
      зная, что колбек возвращает undefined. Потом человек увидел, что иногда кто-то передаёт такой колбек, который возвращает что-то другое и тогда человек ставит void и спит спокойно. Другого варианта не придумал ) return undefined, как по мне лучше.


      1. Akuma
        30.09.2019 15:51

        > который возвращает что-то другое
        Иии… да и пофиг как бы. Если вы предполагаете, что вернется undefined (а скорее всего null, т.к. обычно return undefined не пишут), то если вернется «что-то», то ничего страшного не случится.

        На крайняк

        callback();
        return undefined;


      1. Goodkat
        30.09.2019 20:12

        Может быть, использовать void можно в тех случаях, когда колбэк может вернуть false, а false обрывает дальнейшее выполнение, а программисту, в свою очередь, не нужно, чтобы выполнение обрывалось.


      1. rboots
        30.09.2019 20:23

        return undefined; не нужно писать, проще написать return;
        Если не нужно возвращать результат callback — просто пишем два разных выражения:

        callback();
        return;
        

        и не важно, что вернёт callback. Рецензия на статью отрицательная, void по факту архаизм и практического применения, кроме как попонтоваться знанием синтаксиса, не имеет.


  1. Justerest
    30.09.2019 20:42
    +1

    Тип void в typescript имеет смысл, если участники проекта понимают разницу между функцией и процедурой, и помечают возвращаемый тип процедур как makeCoffee(): void. И директиву return в процедуре лучше не писать в принципе. Это и есть отсутствие возвращаемого значения. То что undefined эквивалентен void допущение typescript, о котором пару слов говорится в документации.


    Противоположностью для типа any является тип unknown (а не void).


    1. VolCh
      01.10.2019 07:06

      Почему лучше не писать return в принципе? В своих приложениях мы настаиваем на early return вместо вложенных if/else и флагов досрочного выхода из циклов.


      1. Justerest
        01.10.2019 07:30

        Это достойные причины для return. Я сказал "лучше", подразумевая идеальный случай, когда нет причин для досрочного выхода.


        Но замечу, что в случае с одним условием, на мой взгляд, предпочтительней обернуть тело метода в if, чем писать досрочный return. Таким образом главная часть кода размещается выше, код читается немного лучше, процедура не содержит return. (Но это холиварная тема, и правила проекта устанавливают участники).


  1. VolCh
    01.10.2019 07:09

    Много лет не вспоминал про JS void пока на ревью TS кода не резанула глаз () => undefined как реализация onClose (): void в интерфейсе.


  1. Keenest
    01.10.2019 10:42

    Не понял, зачем вообще огород городить, явно указывая void/undefined. Вот же:

    var f1 = () => {
        // dosmth
        return;
    },
    f2 = () => {
        // dosmth
    };
    console.log(f1(), f2());
    

    undefined undefined