Почему логическое ИЛИ не работает с ошибкой в JavaScript?

Это довольно распространенная и полезная практика:

// default via value
var un = undefined
var v1 = un || 1

// default via a function call
var myval = () => 1
var v2 = un || myval()

Но это не работает (SyntaxError) при выдаче ошибки:

var v3 = un || throw new Error('un is not set!')

Есть ли способ, как добиться того же эффекта таким же элегантным способом? Это ИМХО много шаблонного кода:

if (!un) {
    throw new Error('un is not set!')
}
var v3 = un

Или есть какие-то теоретические препятствия, почему это невозможно и никогда не будет возможно?

Ответы

Ответ 1

throw - только утверждение; он может не существовать в позиции, где требуется выражение. По тем же причинам, вы не можете поместить там оператор if, например

var something = false || if (cond) { /* something */ }

неверный синтаксис.

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

Я полагаю, что одним из способов было бы использовать IIFE на правой стороне || , что позволяет вам использовать оператор в первой строке этой функции:

var un = undefined
var v2 = un || (() => { throw new Error('nope') })();

Ответ 2

Ваша проблема в том, что присваивание ожидает выражение, но вы даете ему утверждение

Синтаксис для инициализации/назначения переменной:

var|let|const <variableName> = <expression>

но вы используете

var|let|const <variableName> = <statement>

который имеет неверный синтаксис.

Выражения

Выражение - это то, что производит значение.

Что такое "ценность"?

Значение - это все, что является типом в Javascript

  • Числа
  • Строки
  • Булевы
  • Объекты
  • Массивы
  • Символы

Примеры для выражений:

литералы

var x = 5;

x присваивается значение "5"

Вызов функции

var x = myFunc();

myFunc() создает значение, назначенное для x

Полученное значение функции является ее возвращаемым значением - функция всегда возвращает значение, а если оно не указано явно, оно возвращает значение undefined.

Функции имеют дополнительное преимущество, заключающееся в том, что они могут содержать утверждения в своем теле - что будет решением вашего вопроса - Но об этом позже.

Заявления

Утверждение - это то, что выполняет действие. Например:

Цикл

for (var i = 0; i < 10; i++) { /* loop body */ }

Этот цикл выполняет действие по выполнению тела цикла 10 раз

Выдает ошибку

throw new Error()

Разматывает стек и останавливает выполнение текущего кадра

Так почему мы не можем смешать оба?

Если вы хотите присвоить переменную, вы хотите выражение, потому что вы хотите, чтобы переменная имела значение.

Если вы думаете об этом, должно быть ясно, что оно никогда не будет работать с утверждением. Задавать переменной "действие" - это нонсенс. Что это вообще должно означать?

Поэтому вы не можете использовать оператор throw, так как он не создает значения.

Вы можете иметь только один или другой. Либо вы are (expression) что-то, либо вы do (statement) что-то.

Исправление

Вы можете преобразовать любое выражение в выражение, обернув его в функцию, я предлагаю использовать IIFE (Immediately invoked function expression) - в основном функцию, которая сама себя вызывает - для этого

var x = 5 || (() => throw new Error())()

Это работает, потому что теперь правая сторона - это функция, а функция - это выражение, которое создает значение. В этом случае значением является undefined, но, поскольку мы прекращаем его выполнение, в любом случае это не имеет значения.

Будущие возможности

Технически ничто не мешает этому работать.

Многие языки (c++,...) на самом деле уже рассматривают throw как выражение. Некоторые (kotlin,...) даже полностью пропускают утверждения и воспринимают все как выражение.

Другие (c #, php,...) предоставляют обходные пути, такие как ?? null-concealing или ?. оператор elvis, для решения этого самого варианта использования.

Возможно, в будущем мы добавим одну из этих функций в стандарт ecmascript (есть даже открытое предложение включить этот) до тех пор, пока лучше всего использовать такую функцию:

function assertPresent(value, message)
{
  if(!value) {
    throw new Error(message);
  } else {
    return value;
  }
}

Ответ 3

Вы можете переместить throwing исключения в функцию, потому что throw является оператором потока управления, а не выражением:

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

const throwError = function (e) { throw new Error(e); };

var un = undefined,
    v3 = un || throwError('un is not set!');

Ответ 4

Как уже говорилось в других ответах, это потому, что throw - это оператор, который нельзя использовать в контекстах, ожидающих выражения, например, справа от || , Как утверждают другие, вы можете обойти это, заключив исключение в функцию и немедленно вызвав его, но я собираюсь пояснить, что это плохая идея, поскольку она делает ваше намерение менее ясным. Три лишних строки кода - это не большое дело для того, чтобы сделать смысл вашего кода очень ясным и ясным. Я лично считаю, что throw только для операторов - это хорошая вещь, потому что он поощряет написание более простого кода, который с меньшей вероятностью заставит других разработчиков ломать голову при встрече с вашим кодом.

|| Идиома по умолчанию полезна, когда вы хотите предоставить значения по умолчанию или альтернативные значения для undefined, null и других ложных значений, но я думаю, что при использовании в смысле ветвления они теряют свою ясность. Под "ветвящимся смыслом" я подразумеваю, что если ваше намерение состоит в том, чтобы что-то сделать, если выполняется условие (выполнение чего-либо в этом случае вызывает исключение), то condition || do_something() condition || do_something() на самом деле не является ясным способом выразить это намерение, даже если оно функционально идентично if (!condition) {do_something()}. Оценка короткого замыкания не сразу очевидна для каждого разработчика и || Значение по умолчанию понимается только потому, что это часто используемая идиома в Javascript.

Моё общее практическое правило заключается в том, что если функция имеет побочные эффекты (и да, исключения считаются побочными эффектами, особенно потому, что они в основном нелокальные операторы goto), вы должны использовать оператор if для своего условия, а не || или &&. Ты не играешь в гольф.

Итог: что будет меньше путаницы?

return value || (() => {throw new Error('an error occurred')})()

или же

if (!value) {
    throw new Error('an error occurred')
}
return value

Обычно стоит пожертвовать краткостью ради ясности.

Ответ 5

Как и другие говорили, проблема в том, что throw - это утверждение, а не выражение.

Однако в этой дихотомии нет необходимости. Есть языки, в которых все является выражением (без утверждений), и из-за этого они не "низшие"; он упрощает как синтаксис, так и семантику (например, вам не нужны отдельные операторы if и троичный оператор ?: :).

На самом деле это только одна из многих причин, по которым Javascript (язык) отстой, несмотря на то, что Javascript (среда выполнения) удивительна.

Простой обходной путь (который может использоваться и в других языках с аналогичным ограничением, как Python):

function error(x) { throw Error(x); }

тогда вы можете просто написать

let x = y.parent || error("No parent");

Есть некоторая сложность в том, чтобы throw в качестве выражения для статически типизированных языков: каким должен быть статический тип x()? y(): throw(z) x()? y(): throw(z)?; например, C++ имеет очень специальное правило для обработки выражения throw в тернарном операторе (тип берется из другой ветки, даже если формально throw x считается выражением типа void).