JavaScriptMiddleExperience

Какие ошибки делают разработчики, переходящие на JavaScript с другого языка или стека?

Переходя на JS с Java/Python/C#, разработчики чаще всего ошибаются: сериализуют параллельные запросы через await в цикле, теряют this в callback, путают CJS с ESM и игнорируют unhandled Promise rejections.

Перенос синхронного мышления в асинхронный код

Разработчики с Java/C#/PHP фоном пишут псевдосинхронный код, не понимая event loop:

// Типичная ошибка: блокирующий цикл
const results = [];
for (const id of ids) {
  const item = await fetchItem(id); // запросы идут последовательно!
  results.push(item);
}

// Правильно: параллельные запросы
const results = await Promise.all(ids.map(id => fetchItem(id)));

// Или с ограничением конкурентности (p-limit):
import pLimit from 'p-limit';
const limit = pLimit(5); // не более 5 параллельных запросов
const results = await Promise.all(
  ids.map(id => limit(() => fetchItem(id)))
);

Игнорирование прототипной типизации (приходя из Java/C++)

В Java класс — жёсткий контракт. В JavaScript объект можно изменить в любой момент, а instanceof ненадёжен при передаче объектов между realms (iframe, vm.runInNewContext):

// Ловушка: проверка типа через constructor name
function processArray(arr) {
  if (arr instanceof Array) { // ломается для Array из другого iframe!
    return arr.map(x => x * 2);
  }
}

// Правильно: Array.isArray() или Object.prototype.toString
if (Array.isArray(arr)) { /* ... */ }
if (Object.prototype.toString.call(arr) === '[object Array]') { /* ... */ }

Мутация объектов как в Python/Ruby

Python-разработчики привыкают к явному deep copy. В JS передача объектов по ссылке вызывает трудноуловимые баги:

// Баг: мутация входного аргумента
function addDefaults(config) {
  config.timeout = config.timeout || 5000; // мутирует оригинал!
  return config;
}

// Правильно: иммутабельный паттерн
function addDefaults(config) {
  return { timeout: 5000, ...config }; // spread создаёт shallow copy
}

// Для deep copy:
const deep = structuredClone(original); // Node.js 17+, доступно нативно

this binding — камень преткновения для Java/C# разработчиков

class Timer {
  constructor() {
    this.count = 0;
  }

  start() {
    // Ошибка: this теряется в callback
    setInterval(function() {
      this.count++; // this === undefined в strict mode!
    }, 1000);

    // Правильно: стрелочная функция сохраняет lexical this
    setInterval(() => {
      this.count++; // this === Timer instance
    }, 1000);
  }
}

Непонимание module system (CJS vs ESM)

Разработчики с Python/Java фона путаются в двух системах модулей Node.js:

// CommonJS (require) — синхронный, динамический
const fs = require('fs'); // работает внутри if-блоков

// ESM (import) — статический, асинхронный, tree-shakeable
import { readFile } from 'fs/promises';

// Проблема: смешивание CJS и ESM без правильного package.json
// package.json: { "type": "module" } — весь проект ESM
// package.json: { "type": "commonjs" } — весь проект CJS
// .mjs / .cjs расширения для явного указания формата

Обработка ошибок без учёта async контекста

Java-разработчики ставят один try/catch на весь блок. В async JS непойманный rejection крашит процесс:

// Проблема: promise без .catch
async function riskyOperation() {
  await someAsyncCall(); // если бросает — unhandledRejection!
}
riskyOperation(); // нет await, нет catch — тихая потеря ошибки

// Правильно:
try {
  await riskyOperation();
} catch (err) {
  logger.error({ err }, 'Operation failed');
}

// Глобальная страховка:
process.on('unhandledRejection', (reason) => {
  logger.fatal({ reason }, 'Unhandled rejection');
  process.exit(1);
});

Подводные камни

  • Использование == вместо === из-за привычки к языкам без coercion — 0 == false, null == undefined, "" == 0 все возвращают true.
  • Полагаться на порядок ключей объекта (не гарантирован спецификацией для нецелочисленных ключей до ES2015).
  • Не учитывать hoisting: var поднимается в начало функции, let/const — нет. Разработчики с C#/Java фона удивляются использованию var до объявления.
  • Игнорировать разницу между null и undefined — в TypeScript это разные типы, JSON.stringify() убирает undefined из объектов, что меняет поведение API.
  • Копировать паттерн «один файл — один класс» из Java в JS без необходимости — в JS модуль может экспортировать несколько функций, и это идиоматично.
  • Не использовать деструктуризацию, optional chaining (?.) и nullish coalescing (??) — код становится многословным и хрупким.

What hurts your answer

  • Перечислять ошибки без объяснения причин
  • Не отличать beginner mistakes от production failure modes
  • Не предлагать процесс, который предотвращает повторение ошибок

What they're listening for

  • Знает типичные ошибки при работе с JavaScript
  • Понимает причины ошибок
  • Предлагает практики prevention и early detection

Related topics