Promise-цепочки и обработка ошибок

Библиотека bcrypt.js реализует асинхронные операции хеширования и проверки паролей через промисы, что позволяет выстраивать последовательные цепочки обработки без вложенных колбэков. Основные методы, такие как hash и compare, возвращают Promise, что делает их совместимыми с .then() и async/await.

При работе с цепочками важно понимать, что каждая операция в bcrypt является вычислительно затратной. Это означает, что корректное управление последовательностью вызовов и обработкой ошибок напрямую влияет на стабильность всего потока аутентификации.

Пример базовой цепочки хеширования и последующего сравнения:

bcrypt.hash(password, 10)
  .then((hash) => {
    return bcrypt.compare(password, hash);
  })
  .then((isMatch) => {
    console.log(isMatch);
  });

Каждый .then() получает результат предыдущего шага и возвращает новый промис. Таким образом формируется линейный поток обработки данных, где результат одного этапа становится входом следующего.


Передача данных между этапами цепочки

В реальных сценариях цепочки редко ограничиваются двумя шагами. Чаще они включают подготовку данных, хеширование, сохранение в базу и последующую проверку.

bcrypt.hash(password, 12)
  .then((hash) => {
    return saveUser({ username, passwordHash: hash });
  })
  .then((user) => {
    return bcrypt.compare(password, user.passwordHash);
  })
  .then((result) => {
    return result;
  });

Каждый .then() не только получает результат, но и обязан возвращать либо значение, либо новый промис. Отсутствие return приводит к разрыву цепочки и потере управляемости потоком выполнения.


Обработка ошибок в Promise-цепочках

Любая операция bcrypt может завершиться отклонением промиса. Причины включают некорректные входные данные, повреждённые хеши или внутренние ошибки вычислений.

Классическая обработка ошибок реализуется через .catch():

bcrypt.hash(password, 10)
  .then((hash) => bcrypt.compare(password, hash))
  .then((result) => {
    console.log(result);
  })
  .catch((err) => {
    console.error(err);
  });

.catch() перехватывает любую ошибку, возникшую на любом этапе цепочки. Это обеспечивает централизованную обработку отказов без необходимости проверять каждый шаг отдельно.


Проброс ошибок внутри цепочки

Ошибка, возникшая внутри .then(), автоматически превращается в отклонённый промис. Это позволяет явно управлять условиями прерывания цепочки.

bcrypt.hash(password, 10)
  .then((hash) => {
    if (!hash) {
      throw new Error('Hash generation failed');
    }
    return hash;
  })
  .then((hash) => bcrypt.compare(password, hash))
  .catch((err) => {
    console.error('Ошибка:', err.message);
  });

Выбрасывание исключения внутри .then() эквивалентно Promise.reject(). Это важный механизм для контроля логики валидации промежуточных результатов.


Особенности распространения ошибок

Ошибки в цепочке распространяются вниз до первого обработчика .catch(). Все промежуточные .then() пропускаются до момента перехвата.

bcrypt.hash(password, 10)
  .then(() => {
    throw new Error('Ошибка на этапе цепочки');
  })
  .then(() => {
    // этот код не выполнится
  })
  .catch((err) => {
    console.log('Перехвачено:', err.message);
  });

Такой механизм позволяет строить линейные потоки без необходимости проверять результат каждого шага вручную.


Использование нескольких catch-обработчиков

Цепочка может содержать несколько уровней обработки ошибок, если требуется локальная и глобальная реакция на сбои.

bcrypt.hash(password, 10)
  .then((hash) => {
    return bcrypt.compare(password, hash)
      .catch((err) => {
        console.log('Ошибка сравнения');
        throw err;
      });
  })
  .then((result) => {
    console.log(result);
  })
  .catch((err) => {
    console.log('Глобальная обработка:', err.message);
  });

Локальный .catch() перехватывает ошибку только внутри вложенного промиса, после чего ошибка может быть повторно выброшена для внешнего уровня.


Асинхронные цепочки с async/await как альтернатива

Хотя Promise-цепочки остаются базовым механизмом, async/await упрощает чтение и управление ошибками через try/catch.

async function checkPassword(password, hash) {
  try {
    const result = await bcrypt.compare(password, hash);
    return result;
  } catch (err) {
    console.error(err);
  }
}

Под капотом async/await использует те же промисы, но скрывает необходимость явного построения цепочек .then().


Распространённые ошибки при построении цепочек

Неправильное использование промисов в bcrypt.js чаще всего связано с потерей возврата значений:

bcrypt.hash(password, 10)
  .then((hash) => {
    bcrypt.compare(password, hash); // отсутствует return
  })
  .then((result) => {
    // result будет undefined
  });

Отсутствие return разрывает поток данных и приводит к неконтролируемому состоянию цепочки.

Другой тип ошибки — игнорирование обработки отклонений:

bcrypt.hash(password, 10)
  .then((hash) => bcrypt.compare(password, hash));

Без .catch() любая ошибка останется необработанной, что может привести к аварийному завершению процесса в средах Node.js.


Согласованность типов возвращаемых значений

Каждый .then() должен возвращать либо:

  • примитивное значение
  • объект
  • промис (включая bcrypt.hash и bcrypt.compare)

Несоблюдение этого правила приводит к потере связности цепочки:

bcrypt.hash(password, 10)
  .then((hash) => {
    return { hash };
  })
  .then((data) => {
    return bcrypt.compare(password, data.hash);
  });

Явное структурирование промежуточных данных повышает предсказуемость цепочки и упрощает отладку.


Поведение цепочек при высокой нагрузке

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

Параллелизм в bcrypt ограничен характером алгоритма, поэтому оптимизация чаще достигается не через распараллеливание, а через корректное построение минимально необходимой цепочки промисов.