Чему мы можем научиться у Go
Я работаю над кодовой базой, где «законно» смешивать парадигмы обещаний и асинхронности/ожидания. Вот пример из недавнего обзора кода.
Здесь мы делаем вызов API, используя промис, но внутри обработчика catch мы используем async/await. См. строки 6 и 9.
Я нахожу это гротескным, но мои товарищи по команде думают, что это нормально.
Мой аргумент заключается в том, что асинхронный код достаточно сложен для понимания; смешение парадигм делает это еще сложнее. Их аргумент заключается в том, что приведенный выше фрагмент кода короче и легче читается, чем тот, который основан исключительно на промисах или исключительно на основе асинхронности/ожидания.
И знаешь, что? У них есть точка зрения. Рассмотрим тот же фрагмент кода, полностью написанный с помощью промисов.
Мы больше не смешиваем парадигмы, но код по-прежнему довольно сложно читать.
Теперь рассмотрим тот же фрагмент кода, полностью написанный с помощью async/await.
Мы больше не смешиваем парадигмы, и код стал немного легче читать. Это код замены, который я предложил в обзоре кода.
Но что-то все равно кажется неправильным.
Это те самые исключения.
Во всех трех фрагментах кода возможность исключения создает беспорядок. И хотя использование async/await обычно считается лучшим подходом, этот блок try/catch является разрушительным.
Беспорядок в исключениях характерен не только для JavaScript. Другие языки боролись с этим, а некоторые предлагали альтернативы с разной степенью успеха. Я хочу сосредоточиться на одном из этих языков, в частности: Go.
Команда Go описывает уникальный подход языка к обработке ошибок в своем блоге 2011 года Обработка ошибок и Go.
В Go важна обработка ошибок. Структура и соглашения языка побуждают вас явно проверять наличие ошибок там, где они возникают (в отличие от соглашения в других языках о выдаче исключений и иногда об их перехвате).
Короче говоря, это означает, что такие функции, как api.updateFoo()
, возвращают две вещи: результат и ошибку:
const [updatedFoo, err] = api.updateFoo(foo);
Если вызов успешен, updatedFoo
имеет значение, а err
не определено. Если вызов завершается ошибкой, updatedFoo
не определено, а err
имеет значение. Это должно быть решено вызывающим абонентом немедленно.
Вот как это выглядело бы, если бы мы использовали ошибки в стиле Go в JavaScript.
Вы можете возразить, что я создал самый гротескный пример из всех, смешав обработку ошибок Go с JavaScript, но, по моему скромному мнению, этот пример является победителем.
Что вы думаете?