Kodokon kodokon.com

Асинхронность на практике

Используй async/await в своих маршрутах и обрабатывай асинхронные ошибки, не роняя сервер.

10 мин · 3 вопросов

Открыть этот урок в Kodokon

Твои настоящие маршруты не возвращают массивы из памяти: они читают файлы, обращаются к базе данных, вызывают другие API. Все эти операции асинхронные. async/await ты знаешь ещё с направления JavaScript; давай посмотрим, как применять их аккуратно на сервере. Первая хорошая новость: функция маршрута может быть async.

JAVASCRIPT
import express from "express";
import { readFile } from "node:fs/promises";

const app = express();

app.get("/report", async (req, res) => {
  const raw = await readFile("report.json", "utf8");
  res.json(JSON.parse(raw));
});

app.listen(3000);

Этот код работает... пока report.json существует. Если чтение упадёт, промис будет отклонён, и, в зависимости от версии Express, запрос останется без ответа или ошибка утечёт в логи. Профессиональное правило: каждый асинхронный маршрут обрабатывает свои ошибки, через try/catch и next(error).

JAVASCRIPT
app.get("/report", async (req, res, next) => {
  try {
    const raw = await readFile("report.json", "utf8");
    res.json(JSON.parse(raw));
  } catch (error) {
    next(error);
  }
});

next(error) передаёт ошибку в централизованный обработчик ошибок. Это middleware с четырьмя параметрами, объявленный после всех маршрутов. Одно-единственное место, где ошибки логируются и форматируются: именно это отличает поддерживаемый API от проекта выходного дня.

JAVASCRIPT
app.use((err, req, res, next) => {
  console.error(err);
  res.status(500).json({
    error: "Internal server error"
  });
});
Всегда в самом конце, после маршрутов

Остался ещё один рефлекс: параллельность. Две независимые операции, выстроенные в цепочку из двух await, выполняются одна за другой. Promise.all запускает их вместе: время ответа равно времени самой медленной, а не сумме двух.

JAVASCRIPT
async function fetchUsers() {
  return [{ id: 1, name: "Ada" }];
}

async function fetchTasks() {
  return [{ id: 1, title: "Review the code" }];
}

app.get("/dashboard", async (req, res, next) => {
  try {
    const [users, tasks] = await Promise.all([
      fetchUsers(),
      fetchTasks()
    ]);
    res.json({ users, tasks });
  } catch (error) {
    next(error);
  }
});

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Что ты делаешь с ошибкой, пойманной в catch, внутри async-маршрута?
    • Логируешь её через console.log и идёшь дальше
    • Вызываешь next(error), чтобы передать её обработчику ошибок
    • Автоматически повторяешь запрос
  2. Два независимых асинхронных вызова должны выполниться как можно быстрее. Какой подход ты выберешь?
    • Выстроить их в цепочку из двух await подряд
    • Запустить их вместе через Promise.all
    • Использовать setTimeout между ними
  3. Как Express распознаёт middleware для обработки ошибок?
    • По сигнатуре из четырёх параметров, где err идёт первым
    • По имени функции, которое должно быть errorHandler
    • По его расположению в начале файла