Современная разработка на JavaScript часто сталкивается с задачами асинхронной работы и обработки ошибок. Как правило, для этих целей используются конструкции try-catch и async-await. Однако они могут утяжелять код и усложнять его восприятие. Чтобы решить эту проблему, был предложен новый оператор безопасного присваивания ?=, который значительно упрощает обработку ошибок и улучшает читаемость кода. В этой статье мы рассмотрим, как работает этот оператор, его особенности и преимущества для разработчиков.

Основные особенности оператора ?=

1. Простая обработка ошибок

Одним из главных преимуществ оператора ?= является возможность удобного и лаконичного контроля ошибок. Вместо того чтобы оборачивать каждый потенциально «опасный» код в блоки try-catch, вы можете использовать оператор безопасного присваивания, который возвращает результат в формате [error, result]. Если ошибка отсутствует, возвращается [null, result]. В противном случае результатом будет [error, null].

Пример:

const [error, data] ?= await fetchData();
if (error) {
  console.log("Произошла ошибка:", error);
} else {
  console.log("Данные:", data);
}

2. Поддержка асинхронных функций и обещаний (Promises)

Оператор ?= работает с обещаниями и асинхронными функциями, что делает его отличным инструментом для управления ошибками в асинхронных процессах. Это особенно полезно в сценариях взаимодействия с API, чтения файлов или любых других операций, которые могут завершиться неудачно.

Пример:

async function getData() {
  const [fetchError, response] ?= await fetch("https://api.example.com/data");
  if (fetchError) {
    handleFetchError(fetchError);
    return;
  }

  const [jsonError, data] ?= await response.json();
  if (jsonError) {
    handleJsonError(jsonError);
    return;
  }

  return data;
}

3. Улучшение читаемости кода

Оператор ?= снижает вложенность кода, убирает избыточные блоки try-catch и делает обработку ошибок более явной и интуитивной. Это способствует лучшей поддержке кода и снижает вероятность упущения критических ошибок.

Пример сравнения:

// Традиционный подход
try {
  const response = await fetch("https://api.example.com/data");
  const json = await response.json();
  const data = parseData(json);
} catch (error) {
  handleError(error);
}

// С использованием ?=
const [fetchError, response] ?= await fetch("https://api.example.com/data");
if (fetchError) {
  handleFetchError(fetchError);
  return;
}

4. Унифицированный подход

Оператор совместим с любыми объектами, которые реализуют метод Symbol.result. Это позволяет использовать единый подход для обработки результатов и ошибок независимо от типа данных или API. Такой метод добавляет гибкости в управлении сложными структурами данных и взаимодействиями с различными сервисами.

Пример:

const obj = {
  [Symbol.result]() {
    return [new Error("Ошибка"), null];
  },
};

const [error, result] ?= obj;
if (error) {
  console.log("Ошибка:", error);
}

Что такое Symbol.result?

Symbol.result — это метод, который можно определить в объектах или функциях для того, чтобы управлять тем, как они возвращают свои результаты в контексте оператора безопасного присваивания. Когда объект или функция сталкиваются с вызовом через оператор ?=, метод Symbol.result автоматически вызывает обработку возвращаемого значения, преобразуя его в удобный кортеж из ошибки и результата: [error, result].

Рассмотрим пример. Допустим, у нас есть объект, который реализует Symbol.result для того, чтобы обрабатывать собственные ошибки:

const obj = {
  [Symbol.result]() {
    return [new Error("Ошибка в объекте"), null];
  }
};

const [error, result] ?= obj;
console.log(error);  // Выведет: Ошибка в объекте

Поддержка Symbol.result позволяет разработчикам настраивать поведение объектов и функций для совместимости с оператором ?=. Это особенно полезно в случаях, когда необходимо централизованно управлять тем, как различные компоненты приложения возвращают результаты и ошибки. Таким образом, Symbol.result помогает стандартизировать подход к обработке результатов, делая код более предсказуемым и согласованным при взаимодействии с разными API и структурами данных.

Этот механизм может быть полезен в контексте библиотек или фреймворков, где требуется единый способ обработки ошибок. Например, объект запроса или ответа от API может реализовывать Symbol.result для того, чтобы возвращать свои ошибки или успешные данные в стандартизированном формате.

Благодаря такой интеграции оператор ?= не только улучшает читаемость и структуру кода, но и позволяет создавать более мощные и гибкие решения для обработки ошибок.

Для чего нужен новый оператор?

Основной мотивацией для введения оператора безопасного присваивания является стремление упростить обработку ошибок и улучшить читаемость кода. Благодаря лаконичному синтаксису и гибкости оператор ?= позволяет эффективно контролировать выполнение функций и работу с асинхронными операциями, снижая вероятность ошибок и делая код более предсказуемым.

Кроме того, его использование исключает необходимость постоянного внедрения блоков try-catch, которые усложняют код, особенно в случаях, когда одна ошибка может возникнуть на разных этапах выполнения программы. Например, запрос данных может завершиться неудачно на уровне сети (ошибка fetch), при обработке JSON или при валидации полученных данных. С помощью оператора ?= все эти шаги могут быть обработаны в едином формате, что упрощает структуру программы.

Заключение

Оператор безопасного присваивания ?= предлагает разработчикам новый способ работы с результатами и ошибками в JavaScript, упрощая управление асинхронными процессами и делая код более читаемым и поддерживаемым. Эта инновация особенно полезна при взаимодействии с обещаниями, API и сложными операциями, где ошибки могут возникать на разных этапах выполнения.

Полную информацию о предложении и примеры его использования можно найти в репозитории GitHub автора Arthur Fiorette.

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


  1. rukhi7
    10.10.2024 08:30

    Уже и присваивание стало опасным! Ужас какой!

    Как в анекдоте:

    Вот никому нельзя доверять! Никому! Даже себе доверять нельзя!

    ...

    Там смысл в том что все пошло не так.


    1. vanxant
      10.10.2024 08:30

      Нет, это просто гоферы решили, что они тоже хотят во фронт


  1. rzcoder
    10.10.2024 08:30

    // Традиционный подход
    try {
      const response = await fetch("https://api.example.com/data");
      const json = await response.json();
      const data = parseData(json);
    } catch (error) {
      handleError(error);
    }
    
    // С использованием ?=
    const [fetchError, response] ?= await fetch("https://api.example.com/data");
    if (fetchError) {
      handleFetchError(fetchError);
      return;
    }

    Потрясающе, с использованием уникального оператора отпадает необходимость получать json и парсить данные! Или дело в том, что если честный код приводить, то размер примеров будет одинаковым? Или, точнее, размер примера с try catch будет меньше, за счет того, что не надо делать проверки на каждый чих

      const [jsonError, data] ?= await response.json();
      if (jsonError) {
        handleJsonError(jsonError);
        return;
      }


    1. ExTalosDx
      10.10.2024 08:30

      не понимаю дают новую прикольную фичу, а люди вместо радости такие "ааа о нет фича полное ... какие же разработчики js глупые".

      Хотя это просто ещё один способ делать обработку ошибок.

      Ты видишь чтобы кто-то принуждал тебя к использованию этой фичи? Они эти принудители здесь с тобой в одной комнате или ты просто захотел потешить своё ЧСВ разработчика?

      Кстати я буду откровенен и честен с собой, я потешил спасибо.


      1. rzcoder
        10.10.2024 08:30

        Потому что обработка ошибок через возвращаемые значения это try catch "для бедных", это как рекламировать колбеки вместо async await. Годятся разве что для хитрых языков с автоматической параллелизацией последовательного кода, или для раста, где просто не смогли подружить свой компилятор с человеческой обработкой ошибок.

        Единственное применение в js которое вижу: вызов методов которые могут фейлить, но нам нет до этого дела, например вызов логгера.

        Ты видишь чтобы кто-то принуждал тебя к использованию этой фичи?

        Вижу как сейчас пиарят фичу, а нам потом иметь дело с таким кодом на проектах или в api внешних библиотек.


        1. radtie
          10.10.2024 08:30

          А я единственное, хоть сколько то полезное, применение вижу в возможности обойти блочную область видимости, частенько вот эти 'let' подбешивают, но стоит ли всё оно того...

          let result
          
          try {
            result = getSome()
          } catch (e) {
            result = null
          }
          
          doSome(result)
          const [e, result] ?= getSome()
          
          doSome(result)


          1. rzcoder
            10.10.2024 08:30

            Тут скорее хочется чтобы блоки делали возврат

            const result = try {getSome()} сatch {null}; 
            //или объявление переменных внутри условий
            if (const data = getSomeData()) {
              process(data)
            } else {
              handleEmpty();
            }
            


            1. radtie
              10.10.2024 08:30

              Вот объявления переменных в условиях очень хочется.

              И еще бросать исключения в тернарниках и ??

              const r = a === b ? ‘result1’ : throw new Error();
              const r = a ?? throw new Error();


              1. Balek
                10.10.2024 08:30

                Это можно. Но вместо throw нужно использовать функцию `(ex) => {throw ex}`


              1. jooher
                10.10.2024 08:30

                Не хватает объявлений внутри if, например:

                if(const a=getA()){
                 useA();
                }


                1. KReal
                  10.10.2024 08:30

                  Похоже на out переменные в дотнет:

                  if (TrySomething(out var a)){
                    DoStuffWithA(a);
                  }


                  1. jooher
                    10.10.2024 08:30

                    Нет, скорей на

                    for(const i in obj) { useIt(i); }

                    out тут ни при чем


                    1. St1ggy
                      10.10.2024 08:30

                      Как раз при чем, пример с for вообще никак не связан с определением переменных в условиях


              1. jooher
                10.10.2024 08:30

                const Fail = e => {console.log("ой божечки"); throw e; };
                let a = getA() || Fail(new Error());


          1. Zenitchik
            10.10.2024 08:30

            обойти блочную область видимости, частенько вот эти 'let' подбешивают

            var ?


            1. radtie
              10.10.2024 08:30

              Ну мы же говорим о том как сделать код лучше, а не наоборот.


              1. Zenitchik
                10.10.2024 08:30

                Тогда не нужно обходить блочную область видимости.


        1. andreyiq
          10.10.2024 08:30

          А что в расте не так с обратной ошибок?


      1. PrinceKorwin
        10.10.2024 08:30

        Это как с Perl который дает 7 вариантов одного и того же функционала.

        Вроде и "дают новую прикольную фичу" и "это просто еще один способ с делать" и вроде никто не принуждает.

        Но это все выливается в ад по сопровожлению крупных проектов. Когда кроме требований по общему форматированию приходится вводить ещё и ограничения на использование тех или иных выкрутасов языка.


      1. Zenitchik
        10.10.2024 08:30

        Ты читать умеешь? Пример кода "с фичёй" - нерабочий.


    1. polRk
      10.10.2024 08:30

      Нет, код с try-catch будет больше. из-за проверок на тип ошибки в catch. ведь мы хотим реагировать по разному на разные ошибки. и с try-catch не маленькая вероятность пропустить обработку какой-то неявной ошибки.


      1. Rsa97
        10.10.2024 08:30

        Оберните в try-catch каждую строку отдельно и вызывайте те же самые handle...Error(e). Будет практически такой же код, что и с "безопасным" присваиванием.


      1. rzcoder
        10.10.2024 08:30

        В 99% случаев мы не хотим реагировать по разному, мы просто логируем ошибку и уходим. В 1% случаев мы проверяем на 1 конкретный тип ошибок вроде "aborted". И примерно в 0% мы реально обрабатываем каждый тип ошибок прямо на месте. Даже если мы в этом самом 0%, то код будет не длиннее проверки возвратов, и, вероятно, даже короче. Кстати, раз уж речь про типы зашла, хочется посмотреть на обработку множества типов ошибок потенциально возвращаемых из одного метода, особенно на typescript.


    1. aamonster
      10.10.2024 08:30

      Угу, пример плохой. Помимо того, что потеряли часть кода – нет смысла использовать ?= там, где дальше пишешь if(error): по сути, это получается другая, корявая запись try-catch.

      Но достаточно легко придумываются нормальные применения – например, мне бы для прокидывания результатов вызовов из js в натив было бы удобно, да и вообще порой приходится делать переход от исключений к кодам ошибок.


    1. nsmcan
      10.10.2024 08:30

      Кстати, замена try/catch на if будет скорее всего замедлять код, если catch происходит гораздо реже, чем нормальный результат без ошибок. Во всяком случае в Python это именно так. Так как if будет отрабатывать всегда


  1. Rsa97
    10.10.2024 08:30

    А что же вы в примере пункта 3 остановились на полпути? Давайте распишем его до конца:

    // Традиционный подход
    try {
      const response = await fetch("https://api.example.com/data");
      const json = await response.json();
      const data = parseData(json);
    } catch (error) {
      handleError(error);
    }
    
    // С использованием ?=
    const [fetchError, response] ?= await fetch("https://api.example.com/data");
    if (fetchError) {
      handleFetchError(fetchError);
      return;
    }
    
    const [jsonError, json] ?= await response.json();
    if (jsonError) {
      handleJsonParseError(jsonError);
      return;
    }
    
    const [dataError, data] ?= parseData(json);
    if (dataError) {
      handleDataParseError(dataError);
      return;
    }
    

    Стало удобнее? Не факт.


    1. rzcoder
      10.10.2024 08:30

      Не забудьте еще каждый handle* метод расписать.


    1. Sadler
      10.10.2024 08:30

      Во-первых, у вас в версии с ?= три разных обработчика ошибок, а не один, и try-catch версия была бы сильно длиннее. Во-вторых, снижение вложенности полезно тем, что вы как программист при отладке можете просматривать меньший объём кода: вместо одного большого блоба, где "что-то пошло не так" у вас явная последовательность действий, в которой разваливается конкретное ветвление. Если же, наоборот, мы упростим обработчики до return false, то будет очень компактно, хотя и менее информативно.

      Интересно, можно ли использовать с ?= встроенные средства отладчика в браузере по остановке на обработанных исключениях. Вызывается ли вообще исключение, или всё это обрабатывается под капотом.


      1. rzcoder
        10.10.2024 08:30

        снижение вложенности полезно тем, что вы как программист при отладке можете просматривать меньший объём кода

        Вы можете заметить, что вложенность обоих примеров одинаковая. А вот размер кода в высоту растет линейно. И делает простейший метод из 3х строк трудночитаемым. А если вызовов будет не 3 а 10, да еще и с 3х уровневой логикой?

        Если же, наоборот, мы упростим обработчики до return false, то будет очень компактно, хотя и менее информативно.

        Будет все еще минимум +1 строка на каждый обработчик, а с вменяемым стилем форматирования +4.


        1. Sadler
          10.10.2024 08:30

          Важна концептуальная сложность кода, а не "размер кода в высоту". Например, regex-выражения записываются очень коротко, но хорошо ли они читаются? Не очень. Если не влезает код, значит пора разбивать функцию на части. Ну, либо купите монитор побольше.


          1. rzcoder
            10.10.2024 08:30

            Всё верно, но чем концептуальная сложность проверки на ошибку каждого вызова в стиле вызова api методов 30 летней давности, меньше чем у try catch? Говоря о сложности, вам еще нужно придумать имя переменной для ошибки в каждом вызове, а GC создать и удалить массив.


            1. Sadler
              10.10.2024 08:30

              Я уже ответил: сложность меньше тем, что вы контролируете каждое ветвление, а не ловите всевозможные ошибки из блока.

              Про GC лучше даже не заикаться в контексте исключений. Исключения -- это дорого, там буквально для throw создаётся целый объект исключения, и производительность там будет проседать значительно, если использовать это под высокой нагрузкой. Куда сильнее, чем создание какого-то там массива на два элемента.


              1. rzcoder
                10.10.2024 08:30

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

                Я уже ответил: сложность меньше тем, что вы контролируете каждое ветвление, а не ловите всевозможные ошибки из блока.

                Ничего не понял. Можно, пожалуйста, наглядный пример с меньшей сложностью?


                1. Sadler
                  10.10.2024 08:30

                  Множество массивов для результатов создаются каждый вызов функции.

                  Это всё хорошо оптимизируется, и влияние на результирующую скорость исполнения здесь околонулевое.

                  Я воздержусь от продолжения бесполезного флуда.


              1. Rsa97
                10.10.2024 08:30

                А как именно будет работать оператор безопасного присваивания, например, с fetch?
                Будет вызываться отдельная версия fetch, возвращающая массив? Но тогда как быть, если исключение выброшено, например, в параметре функции?
                Или же fetch будет выбрасывать исключение, а оператор будет его неявно перехватывать и переделывать в массив с кодом ошибки? Но тогда будет двойная работа.


      1. Rsa97
        10.10.2024 08:30

        Полностью аналогичная try-catch версия с отдельной обработкой каждой ошибки будет длиннее на три строки, но всегда ли необходимо обрабатывать каждую ошибку отдельно?
        К тому же, exception можно выбросить на любой вложенности функций и обработать на самом верху, в то время как явную обработку ошибок придётся писать в каждой из вложенных функций.


        1. aamonster
          10.10.2024 08:30

          Да вроде чисто синтаксический сахар, хотите – заверните всё в одно выражение (можно с вложенными функциями, бросающими исключения) и обрабатывайте результат в одном месте.

          Просто конвертер исключений в коды ошибки (ну, с возможностью срезать углы).


    1. polRk
      10.10.2024 08:30

      А теперь напишите такую же функциональность на try/catch. чего же вы остановились...


  1. gruzoveek
    10.10.2024 08:30

    Я что-то упустил, ее подвезли уже или только планируют?


    1. Sadler
      10.10.2024 08:30

      Если это оно, то даже не планируют. Draft proposal, вместо которого рассматривают другие альтернативы. Так что сугубо разминка для ума.


      1. dagen
        10.10.2024 08:30

        И самое интересное, что в голосовании альтернатив побеждает try expression с таким же синтаксисом, который не взлетел 5 лет назад: https://github.com/dead-claudia/proposal-try-expression


  1. Stonuml
    10.10.2024 08:30

    Собственно то за что не нравиться Go пытаются затащить в Js. Тут где-то рядом целый трэд был про использование exception.


  1. Dhwtj
    10.10.2024 08:30

    // С использованием ?=

    const [fetchError, data] ?= parseData(await fetch("https://api.example.com/data").json());
    if (fetchError) 
    {  
      handleFetchError(fetchError);  
      return;
    }


    1. Rsa97
      10.10.2024 08:30

      И что вернёт [fetchError, response].json() ?


      1. Zenitchik
        10.10.2024 08:30

        А что возвращает await fetch("https://api.example.com/data") ?

        В примере без ?= и с ?= как будто разное...


        1. Rsa97
          10.10.2024 08:30

          Если await fetch возвращает response, а обработка исключений идёт в операторе безопасного присваивания, то надо писать

          const [fetchError, data] ?=
            parseData(await (await fetch("https://api.example.com/data")).json());
          

          Иначе получаем Promise.json(), вызов несуществующей функции.
          Но это означает, что фактически делается двойная работа. fetch выбрасывает исключение, оно неявно перехватывается в операторе, который создаёт массив, заполняя его... А чем, кстати, будет заполняться поле ошибки в этом массиве? Ведь никто не мешает нам написать throw new Error(null);.


      1. aamonster
        10.10.2024 08:30

        Для такого использования придётся писать fetch(...).then(...) :-)


  1. sfi0zy
    10.10.2024 08:30

    Мне кажется, что главный затык всех этих примеров модного и молодежного синтаксиса в том, что с точки зрения конечного пользователя случается "опаньки!". Все сломалось. Данные не грузятся. И мы можем сколько угодно обрабатывать ошибки каждого шага по отдельности, красиво их оформлять в коде, но в конечном счете ничего не работает. Этот компонент не может загрузиться. Лучшее, что мы можем сделать - это выключить и включить. Попробовать повторить всю процедуру с начала. Собственно в примерах, которые автор в предложении приводит - при любой ошибке мы уводим поток выполнения в handleAndDie(), проходим точку невозврата, не предполагая возможности горячего восстановления. А если нет возможности восстановиться, то в чем смысл всего этого огорода? Можно и один большой try-catch сделать с тем же эффектом. Было бы здорово в обсуждения таких инициатив добавить примеров, как все это будет выглядеть, если мы на самом деле будем обрабатывать ошибки, а не просто делать лапки разными способами.


  1. Phuysick
    10.10.2024 08:30

    То есть if (err != nil)?


    1. Zenitchik
      10.10.2024 08:30

      Т.е. if(!!err)


  1. CodeShaman
    10.10.2024 08:30

    Эх, предложили бы сразу монады Result и Maybe. И еще pattern matching с ними сразу бы завезли для их обработки красиво. А там и до Railway oriented programming рукой подать =)


    1. morijndael
      10.10.2024 08:30

      Можно писать фронт на расте :D

      Это даже весело!


  1. Free_ze
    10.10.2024 08:30

    В оригинальном пропозале есть такой аргумент против try-catch, где они дробят вложенные try-catch на последовательные с соответствующим бойлерплейтом. Но в этом конкретном примере, что мешает отталкиваться от типа исключения в catch-блоке?

    почему не сделать так?

    Было

    async function readData(filename) {
        try {
            const fileContent = await fs.readFile(filename, "utf8")
    
            try {
                const json = JSON.parse(fileContent)
        
                return json.data
            } catch (error) {
                handleJsonError(error)
                return
            }
        } catch (error) {
            handleFileError(error)
            return
        }
    }
    

    Стало:

    async function readData(filename) {
        try {
            const fileContent = await fs.readFile(filename, "utf8");
            const json = JSON.parse(fileContent);
            return json.data;
        }
        catch (error) {
            if (error instanceof FileError) {
                handleFileError(error);
            }
            else if (error instanceof JsonError) {
                handleFileError(error);
            } 
            else {
                throw new Error("Unknown error", { cause: error }); // rethrow
            }
        }
    }
    

    В этом случае happy path остается чистым от обработки ошибок и скоуп не замусоривается дополнительными именами, в отличае от.


    1. CodeShaman
      10.10.2024 08:30

      Го не принуждает, к сожалению. Обработку ошибок можно просто скипать. Вот в языках, где есть монады типа Result, тебе уже точно придется распаковать ее и проверить, что внутри — ошибка или результат. И придется обработать все.


      1. Free_ze
        10.10.2024 08:30

        Скипать обработку ошибки все равно нужно явно, а это хорошо видно на ревью и сложно сделать случайно.
        Речь о том, что если функция не возвращала ошибок и вдруг начала (или зеркальная ситуация), то в кодобазе пользователя будет ошибка компиляции - это приятно. Но в JS нет гарантии, что функция при смене фазы Луны не изменит тип возврата.


        1. aamonster
          10.10.2024 08:30

          Ну, для этого те, кто не страдает мазохизмом, давно перешли на TypeScript. Надо, кстати, глянуть – завезли ли туда этот оператор? Обычно завозят раньше, чем в js (и плюс можно юзать, не закладываясь на использование самого нового браузера: всё равно будешь транспилировать, можно собрать под старую версию js).


          1. Free_ze
            10.10.2024 08:30

            Если понимать идею пропозала так, что этот оператор перехватывает исключения (это описано двузначно и явных примеров нет, но судя по комментариям - многие поняли именно так), то и TS не поможет - исключения всплывают не влияя на проверяемые контракты.


            1. Rsa97
              10.10.2024 08:30

              Идея пропозала не до конца проработана. С одной стороны заявляется, что функция обязана реализовать метод Symbol.result:

              If the function or object does not implement a Symbol.result method, the ?= operator should throw a TypeError.

              Но тогда непонятно, как быть с аргументами функций:

              [err, val] ?= foo(bar());
              

              Непонятно, что должно произойти, если исключение будет выброшено в bar().


  1. Free_ze
    10.10.2024 08:30

    Основной мотивацией для введения оператора безопасного присваивания является стремление упростить обработку ошибок и улучшить читаемость кода

    • При том читаемость вызываемой функции - под вопросом, из-за бойлерплейста с Symbol.result.

    • Как избежать замусоривания кодом проброса ошибок выше (? из Rust не завезли)?

    • Как быть с функциями, которые бросают несколько разных (типов) ошибок, чтобы обрабатывать их по-своему, а какие-то игнорировать? Тут даже у try-catch не все ладно.

    • Явность обработки - это прикольно, но если функцию, возвращающую ошибки, вызывать с обычным присваиванием (=), будет сразу ошибка (как была бы при необработанном исключении) или она дрейфует дальше?


  1. alek0585
    10.10.2024 08:30

    Замена try-catch на if error else code выглядит убого, неинтуитивно.

    Из доки https://github.com/arthurfiorette/proposal-safe-assignment-operator

    • Simplified Error Handling: Streamline error management by eliminating the need for try-catch blocks.

    • Enhanced Readability: Improve code clarity by reducing nesting and making the flow of error handling more intuitive.

    • Consistency Across APIs: Establish a uniform approach to error handling across various APIs, ensuring predictable behavior.

    • Improved Security: Reduce the risk of overlooking error handling, thereby enhancing the overall security of the code.

    Вместо того, чтобы настроить нормальные исключения, придумали этот костыль.

    Тут, кстати, есть замечания https://github.com/arthurfiorette/proposal-safe-assignment-operator/issues/35

    А могли бы сделать по-людски:

    const GET_DATA_STATUS = Object.freeze({
      ok: 'ok',
      invalidData: 'invalidData',
      unauthorized: 'unauthorized'
    })
    async function getData() {
      const json = await fetch(
        "https://api.example.com/data"
      ).json()
      if (json.unauthorized) {
        return { status: GET_DATA_STATUS.unauthorized };
      }
      const validationErrors = validationSchema.parse(json)
      if (validationErrors) {
        return { status: GET_DATA_STATUS.invalidData, errors: validationErrors };
      }
      return { status: GET_DATA_STATUS.ok, data: json };
    }
    
    async function main() {
      try {
        const data = await getData();
        if (data.status === GET_DATA_STATUS.unauthorized) {
          // 
        } else if (data.status === GET_DATA_STATUS.invalidData) {
          // 
        } else {
           // success
        }
      } catch (e) {
        if (e instanceOf FetchError) {
          // handle fetch
        } else if (e instanceOf ParseError) {
           // handle converting to json 
        } else {
           // unknown
        }
      }
    }

    Так можно разделить ошибки на две ветки:

    1. код не работает(баг в коде), окружение не работает(нет интернета)

    2. код работает, но данные по позитивному сценарию (неавторизован, данные невалидные)


    1. k4ir05
      10.10.2024 08:30

      А не лучше ли доработать try...catch с возможностью указывать несколько блоков обработки ошибок?

      Например:

      begin
        # ...
      rescue ArgumentError
        # handle ArgumentError
      rescue NameError
        # handle NameError
      rescue
        # handle any StandardError
      end


      1. anaxita
        10.10.2024 08:30

        а разве нельзя в catch блоке свич кейс написать для этого?


  1. pharrell
    10.10.2024 08:30

    Довольно странная статья, буквально представляющая ?= как новый оператор EcmaScript, в то время proposal не то что даже не получил Stage 0 (напомню, фича добавляется в язык на Stage 4, до которого, если очень повезёт, могут пройти многие годы от 0, а выживают сильнейшие), так ещё и на момент написания статьи в самом верху в REAMDE репозитория уже как месяц висит большое объявление красным цветом, что автор отказался от этого proposal и пилит какой-то другой.

    Этого оператора никогда не будет в JS в таком виде, в котором он описан в статье.


  1. maxcat
    10.10.2024 08:30

    Интуитивно кажется будто это "присвоить, если переменная равна null" или "присвоить, если переменная равна true", а не то о чем в статье написано


  1. NetFantomIO
    10.10.2024 08:30

    Кажется, что это не выглядит заменой try/catch, т.к. объем кода и читаемость ухудшается, если применять влоб. Но если миксовать подходы, то кажется удобно использовать в кейсе, когда в try/catch блоке мы какой-то шаг хотим обработать так, чтобы не вываливаться при ошибке в общий catch:

    // Традиционный подход
    try {
      const response = await fetch("https://api.example.com/data");
      const json = await response.json();
      let data, dummyData;
      try {
        const data = parseData(json);
      }
      catch(error) {
        dummyData = {test: 123}
      }
      doSmthWithData(data || dummyData);
    } catch (error) {
      handleError(error);
    }
    
    // С использованием ?=
    try {
      const response = await fetch("https://api.example.com/data");
      const json = await response.json();
      const dummyData = {test: 123}
      const [error, data] ?= parseData(json);
      doSmthWithData(data || dummyData);
    } catch (error) {
      handleError(error);
    }


  1. senfiaj
    10.10.2024 08:30

    Когда читал заголовок думал это о записи значения внутри поля/подполя объекта, который может быть null / undefined или вообще не существовать. То есть аналог оператора ?. . В PHP что-то подобное есть для массивов, когда не объявлена переменное или поле, но автоматически создаются все необходимые переменная и поля/подполя.


    1. Rsa97
      10.10.2024 08:30

      Оператор null-coalescing assignment в JS уже есть.

      x ??= y
      


      1. senfiaj
        10.10.2024 08:30

        Не, не про это. В PHP можно написать такое даже если $arr не объявлена

        $arr[5][6] = 5;
        
        print_r($arr);

        Я думал о схожей фиче в JS.


        1. Rsa97
          10.10.2024 08:30

          Это array autovivification. PHP автоматически создаёт массив из null или unset. JS так не умеет.


          1. senfiaj
            10.10.2024 08:30

            Да. Я думал что-то подобное в JS хотели добавить. Была бы очень годной вещью (аналогом записи optional chaining (?.)).