Как написать лучший и безопасный код 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. Заключение
Как видите, существует множество шаблонов и практик, которым можно следовать, чтобы улучшить свою кодовую базу. Это уменьшит количество ошибок в коде. и общая производительность приложения.
Надеюсь, вам понравится этот пост. Давайте напишем чистый и безопасный код.