Как написать лучший и безопасный код JavaScript
JavaScript - это динамический язык. Это делает JavaScript супер-крутым языком для начала. Однако очень сложно написать лучший и безопасный код. Одна маленькая ошибка может привести к более серьезной проблеме. Обработка ошибок играет жизненно важную роль в уменьшении количества ошибок. Если вы будете обрабатывать ошибки элегантным способом, это сэкономит много времени в будущем. Таким образом, более серьезный вопрос заключается в том, как вы должны справиться с ошибкой.
Возьмем один пример.
const express = require("express") const app = express() app.get("/", (_, res) => res.end("OK")) app.listen(80)
Приведенный выше код представляет собой пример кода, написанного на nodejs на экспресс-платформе. В этом коде я пытаюсь запустить сервер на 80-м порту.
Вопросы:
- Что делать, если мы знаем, что порт 80 уже занят каким-то другим приложением, и пытаемся запустить приведенный выше код.
- Как мы узнаем, что произойдет?
- Запустится или сломается с какой-то ошибкой?
Вывод очень неясен при просмотре этого кода. Даже если вы хотите обработать ошибку, вам, возможно, придется прочитать документацию. Но не волнуйтесь, как и любой естественный язык. В языке программирования есть грамматика.
ECMA, не упоминается конкретная спецификация / стандарт. Однако сообщество JavaScript следует определенным правилам кодирования.
1. Типы ошибок
Поскольку JavaScript имеет другой вид компилятора, и большинство из них написаны и поддерживаются другой организацией. За исключением SyntaxError, нет однозначного или последовательного распределения по типам ошибок. Даже сообщение варьируется от компилятора к компилятору. Однако вы можете найти список SyntaxError здесь.
Примечание. JavaScript - это динамический язык, большинство ошибок возникают во время выполнения.
Пример:
console.log(Number(10).toPrecision(200))
Если мы запустим приведенный выше код, он выдаст RangeError.
RangeError: toPrecision() argument must be between 1 and 100
2. Как справиться с ошибкой
В зависимости от характера вызова API (метода) sync / async, ошибка может обрабатываться по-разному.
2.1 Синхронный
try-catch: вы можете использовать блок try-catch для обработки синхронной ошибки.
try { console.log(Number(10).toPrecision(200)); } catch (error) { // RangeError: toPrecision() argument must be between 1 and 100 console.log(error instanceof RangeError); // true }
Если вы не хотите обнаруживать ошибку и выполнять какие-либо операции. В более новой версии компилятора JavaScript это возможно.
try { console.log(Number(10).toPrecision(200)); } catch {}
Если вы хотите выполнить какую-либо операцию по умолчанию при ошибке, вы можете использовать наконец блок после блока catch.
let average; try { average = getAverage(); // Sum function does not exits. } catch { } finally { average = 0; } console.log(`Average is ${average}`); // Average is 0
Наконец, блок в основном используется для очистки ресурсов, таких как некоторый открытый файл, открытое соединение.
2.2 Асинхронный
API называется асинхронным по своей природе, когда результат наступает в каком-то следующем цикле событий EventLoop. Обычно все сетевые вызовы и операции ввода-вывода являются асинхронными по своей природе.
Чтобы получить результат асинхронного вызова, мы используем либо объект обратного вызова, либо обещание.
Обработка ошибок в API асинхронного обратного вызова (данные об ошибке обратного вызова): базовый JavaScript базового браузера имеет очень ограниченные асинхронные API. Вы можете создать асинхронную функцию с помощью API-интерфейсов таймера, например setTimeout и setInterval. Вы также можете запросить сервер с помощью вызова AJAX (используя выборку). setTimeout и setInterval не вызывают таких ошибок, которые можно обработать. А fetch - это асинхронный вызов на основе обещаний (мы узнаем позже, как обрабатывать ошибки, связанные с обещаниями). Однако в nodejs есть множество стандартов и сторонних API, что вызывает ошибку. Как и в нашем первом примере express.js.
const express = require("express"); const app = express(); app.get("/", (_, res) => res.end("OK")); const server = app.listen(80); server.on("error", function handleListen(error) { console.log(error); });
В приведенном выше примере Express не пытается обработать ошибку за вас. Вместо этого он возвращает основной серверный экземпляр nodejs. Вы можете поймать ошибку с помощью функции обратного вызова обработчика ошибок.
Nodejs следует определенным правилам. В качестве стандарта кодирования все асинхронные API принимают обратный вызов. В обратном вызове первым аргументом будет ошибка, сгенерированная API, а вторым аргументом будут данные об успехе. Этому стандарту следовало и все сообщество.
Увидев этот шаблон, мы можем обработать ошибку, как указано ниже.
fs.readFile('a file that does not exist', (err, data) => { if (err) { console.error('There was an error reading the file!', err); return; } // Otherwise handle the data });
Примечание. Вы не можете обработать ошибку асинхронного обратного вызова в блоке try-catch. Однако есть исключение. Последняя версия ECMA Script, использующая async-await, теперь может обрабатывать ошибку в try-catch. Мы узнаем об этом позже.
// This will not work try { app.listen(80); } catch (error) { // never called console.error(error) }
Обработка ошибок в API на основе обещаний (обещание-затем-улов): после ES5 в JavaScript появился новый шаблон проектирования для обработки обратного вызова для асинхронного API. Это шаблон дизайна обещания. Это решает предыдущую проблему ад обратного вызова.
const promise = new Promise((response, reject) => { // some async code here }); promise .then(function onSuccess(data) { console.log("SUCCESS"); }) .catch(function onError(err) { console.error(err); });
Чтобы перехватить ошибку в объекте обещания, вы должны использовать метод catch и передать функцию обратного вызова. Чтобы понять шаблон обещания / отсрочки, вы можете прочитать мой блог здесь.
Обработка ошибок в API на основе обещаний с использованием async-await (try-catch-await): обещание намного чище, чем этот обратный вызов. Однако очень сложно понять поток в большой кодовой базе. В последней версии ECMA Script добавлено async-await. Используя async-await, мы можем синхронно писать асинхронный код.
async function main() { try { await promise1; const data = await promise2; } catch (error) { console.log(error); // SOME ERROR } } main();
Используя try-catch-await, вы можете обрабатывать несколько ошибок в одном блоке, что было невозможно / сложно в шаблоне обещание-затем-поймать.
Теперь мы знаем, как исправить ошибку. Однако при написании кода нам необязательно обрабатывать только ошибки. Мы можем захотеть создать собственную ошибку. Это поможет писать чистый и удобный в сопровождении код. Это хорошая практика, вы должны создавать собственные ошибки для бизнес-логики
3. Создайте настраиваемые ошибки
Создать собственную ошибку очень просто. Вы можете использовать любой настраиваемый класс и выдавать его как ошибку.
class SomeNetworkError { constructor(status) { this.status = status; } } try { throw new SomeNetworkError(4000); } catch (error) { console.log(error instanceof SomeNetworkError); // true console.log(`SomeNetworkError Status: ${error.status}`); // SomeNetworkError Status: 4000 }
Выше у нас есть класс SomeNetworkError, и мы используем экземпляр этого класса, чтобы выдать ошибку. Это действующий код. Однако в качестве практики кодирования мы должны расширить стандартные (стандартные) классы ошибок. Базой всех error-классов является Error и вызов супер-методом с сообщением.
class SomeNetworkError extends Error { constructor(message, status) { super(message); this.status = status; } } try { throw new SomeNetworkError("Network Error", 4000); } catch (error) { console.error(error instanceof Error); // true console.log(`> ${error}`); // > Error: Network Error console.error(error); // SomeNetworkError: Network Error console.error(error.stack); // stacktrace here }
Если вы заметили, расширение класса Error и вызов super автоматически получают метод .toString класса SomeNetworkError. И он печатает красивое сообщение. Точно так же вы можете расширить и другой стандартный класс Error.
class ArithmeticRangeError extends RangeError { constructor(message) { super(message); } } try { const zero = 0; if (zero === 0) { throw new ArithmeticRangeError("zero cant be 0"); } } catch (error) { console.log(error instanceof RangeError); // true console.error(error.toString()); // RangeError: zero cant be 0 }
4. Расширенная обработка ошибок
Обработка ошибок с помощью цикла: Не допускайте попадания в цикл try-catch, если вы хотите прервать цикл при ошибке. В противном случае поместите try-catch в цикл, чтобы продолжить
Разрыв при ошибке:
try { const numbers = [10, 2, 0, 5]; numbers.forEach((num) => { if (num === 0) { throw new ArithmeticRangeError("zero cant be 0"); } console.log(num); }); } catch (error) {}
Продолжить при ошибке или пропустить:
const numbers = [10, 2, 0, 5]; numbers.forEach((num) => { try { if (num === 0) { throw new ArithmeticRangeError("zero cant be 0"); } } catch (error) {} console.log(num); });
Без try-catch, логический дескриптор:
// filter zero, no need handle zero const numbers = [10, 2, 0, 5].filter((num) => num !== 0); numbers.forEach((num) => { console.log(num); }); // Use some to break loop const numbers = [10, 2, 0, 5]; numbers.some((num) => { const isZero = num === 0; if (isZero) return true; // logic here console.log(num); });
Как видите, в зависимости от ваших потребностей вам, возможно, не нужно всегда выдавать ошибку. Вы можете справиться с этим логически.
Множественные ошибки в try-catch:
try { let name ; /// some operation if (name === "") throw new RangeError("Cant be blank"); if (name.match(/\W/)) throw new TypeError("name cant be non alph-numric"); throw new Error("Some other error"); } catch (error) { if (error instanceof RangeError) console.log("RangeError"); else if (error instanceof TypeError) console.log("TypeError"); else console.log("Other Error"); }
Множественные ошибки в обещании-затем-ловле:
new Promise((resolve) => { let name; // some logic resolve(name); }) .then((name) => { if (name === "") throw new RangeError("Cant be blank"); else return name; }) .then((name) => { if (name.match(/\W/)) throw new TypeError("name cant be non alph-numric"); else return name; }) .catch((error) => { if (error instanceof RangeError) console.log("RangeError"); else if (error instanceof TypeError) console.log("TypeError"); else console.log("Other Error"); });
5. Правила кодирования при обработке ошибок
Прежде всего, код - это очень стандартные и простые варианты использования для обработки ошибки. Однако когда вы работаете над проектом. Код может быть не так прост, как здесь. Итак, нам нужно написать несколько шаблонных кодов. Ниже я перечислил некоторые шаблоны, которым я следую в своих проектах.
- Создайте константы класса или ошибки
- Используйте локализацию с самого начала
- Обычный модуль или файл util для обработки логики и генерации ошибки
- Постарайтесь свести к минимуму использование try-catch. Вместо этого напишите больше примеров модульного тестирования.
- Поймать и выдать настраиваемую ошибку при вызовах API
- Используйте машинописный текст, чтобы уменьшить количество ошибок
- Сведите к минимуму использование магического числа / строки
- Избегайте более высокого уровня вложенного объекта
- Избегайте глобального загрязнения объекта
- Максимально используйте E E rror-Boundaries (React)
- Правильное ведение журнала, используйте console.error для регистрации ошибок.
- Уровень журнала, чтобы минимизировать количество сообщений журнала.
- Не печатать учетные данные в журналах
- Используйте больше визуальных элементов, чем консоль в случае веб-приложений.
6. Заключение
Как видите, существует множество шаблонов и практик, которым можно следовать, чтобы улучшить свою кодовую базу. Это уменьшит количество ошибок в коде. и общая производительность приложения.
Надеюсь, вам понравится этот пост. Давайте напишем чистый и безопасный код.