Топ ошибок начинающих JavaScript разработчиков: продвинутый уровень

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Топ ошибок начинающих JavaScript разработчиков: продвинутый уровень

Когда вы уже освоили базовый синтаксис JavaScript и написали пару скриптов для браузера или Node.js, кажется, что самое сложное позади. Но именно на этапе перехода к реальным проектам (часто на серверной стороне с Node.js) разработчики сталкиваются с коварными ошибками, которые не ломают код сразу, но приводят к трудноуловимым багам, утечкам памяти и неожиданному поведению. В этой статье мы разберём топ-4 продвинутых ошибки, которые допускают начинающие JavaScript-разработчики, и покажем, как их избежать.

1. Непонимание работы замыканий в циклах (Closures in Loops)

Классическая проблема, которая перекочевала из браузерного JavaScript в Node.js. Многие знают, что var имеет функциональную, а не блочную область видимости, но ошибка возникает даже с let при неправильном использовании асинхронных колбэков.

Проблема: Вы создаёте массив функций в цикле и ожидаете, что каждая функция запомнит своё значение счётчика. Вместо этого все функции ссылаются на последнее значение.

// ❌ Ошибка: все вызовы вернут 3
const functions = [];
for (var i = 0; i < 3; i++) {
    functions.push(() => console.log(i));
}
functions.forEach(fn => fn()); // 3, 3, 3

Почему так происходит? Замыкание захватывает не значение переменной i, а ссылку на саму переменную. К моменту вызова функции цикл уже завершён, и i равна 3.

Решение (для Node.js): Используйте let для блочной области видимости или создайте новое замыкание через IIFE (Immediately Invoked Function Expression).

// ✅ Исправление с let
const functions = [];
for (let i = 0; i < 3; i++) {
    functions.push(() => console.log(i));
}
functions.forEach(fn => fn()); // 0, 1, 2

// ✅ Исправление с замыканием (immediately invoked function)
for (var i = 0; i < 3; i++) {
    (function(j) {
        functions.push(() => console.log(j));
    })(i);
}

2. Игнорирование асинхронности: «Гонка» промисов и потеря контекста this

Node.js — это асинхронная среда. Одна из самых частых ошибок — попытка использовать результат асинхронной операции до её завершения или потеря контекста this внутри методов.

Пример 1: Гонка промисов (Race condition)

// ❌ Ошибка: результат может быть undefined
let userData;
fetchUserData().then(data => {
    userData = data;
});
console.log(userData); // undefined — синхронный код выполнился раньше

Пример 2: Потеря this в колбэке

class Logger {
    constructor(prefix) {
        this.prefix = prefix;
    }
    logLater() {
        setTimeout(function() {
            console.log(this.prefix); // undefined — this ссылается на глобальный объект
        }, 100);
    }
}
const logger = new Logger('INFO:');
logger.logLater(); // undefined

Решение: Всегда используйте async/await для последовательных операций и стрелочные функции для сохранения контекста.

// ✅ Исправление с async/await
async function getUserData() {
    const userData = await fetchUserData();
    console.log(userData); // теперь данные точно есть
}

// ✅ Исправление с arrow function
class Logger {
    constructor(prefix) {
        this.prefix = prefix;
    }
    logLater() {
        setTimeout(() => {
            console.log(this.prefix); // INFO:
        }, 100);
    }
}

3. Неправильная обработка ошибок в цепочках промисов и стримах

В Node.js многие операции возвращают промисы или стримы. Начинающие разработчики часто забывают обрабатывать ошибки в середине цепочки или в стримах, что приводит к «тихим» падениям приложения.

Проблема с промисами:

// ❌ Ошибка: ошибка в then не будет поймана
fetchData()
    .then(data => {
        // Если здесь выбросить исключение, catch не сработает,
        // если он не привязан к этому then
        return processData(data);
    })
    .catch(err => console.error('Ошибка:', err));
// Если processData вернёт rejected промис, catch выше поймает.
// Но если processData выбросит синхронное исключение, то catch не сработает,
// так как исключение произойдёт до возврата промиса.

Решение: Всегда используйте .catch() в конце цепочки и оборачивайте синхронный код в try/catch внутри async функции.

// ✅ Исправление с async/await
async function getProcessedData() {
    try {
        const data = await fetchData();
        const result = await processData(data); // processData может быть синхронной
        return result;
    } catch (error) {
        console.error('Ошибка обработки:', error);
        throw error; // пробрасываем дальше, если нужно
    }
}

Проблема со стримами (Streams): В Node.js часто забывают обрабатывать событие 'error' на стримах.

// ❌ Ошибка: необработанная ошибка в стриме
const fs = require('fs');
const readStream = fs.createReadStream('nonexistent.txt');
readStream.pipe(process.stdout); // Вызовет unhandled error и убьёт процесс
// ✅ Исправление
readStream.on('error', (err) => {
    console.error('Ошибка чтения файла:', err.message);
});
readStream.pipe(process.stdout);

4. Утечки памяти из-за забытых обработчиков событий и глобальных переменных

В долгоживущих Node.js-приложениях (серверах) утечки памяти критичны. Две основные причины — глобальные переменные и неправильное управление слушателями событий (EventEmitter).

Пример 1: Глобальные переменные

// ❌ Ошибка: переменная cache никогда не очищается
function processRequest(data) {
    if (!global.cache) {                

Блоги

Рекомендаций по книгам