Learn JavaScript

async/await в JavaScript

async/await - это синтаксис, который делает работу с Promise проще для чтения. По сути это “удобная обертка” над then/catch, но с более прямым стилем кода.

1) async: функция, которая возвращает Promise

Если перед функцией стоит async, она всегда возвращает Promise.

async function getValue() {
  return 123;
}

getValue().then((value) => {
  console.log(value); // 123
});

Даже если внутри возвращается обычное число, снаружи это будет Promise.

2) await: дождаться результата Promise

await можно использовать только внутри async-функции. Он “останавливает” выполнение этой функции до тех пор, пока Promise не выполнится.

function wait(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function run() {
  console.log("A");
  await wait(300);
  console.log("B");
}

run();

Важный нюанс: await не блокирует весь браузер. Он приостанавливает только выполнение текущей async-функции, а страница продолжает работать.

3) Сравнение с then: один и тот же смысл

Через then:

function wait(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

wait(200).then(() => {
  console.log("done");
});

Через await:

function wait(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function run() {
  await wait(200);
  console.log("done");
}

run();

4) Обработка ошибок: try/catch

Если Promise будет rejected, await выбросит ошибку. Ее ловят через try/catch.

async function run() {
  try {
    const value = await Promise.reject(new Error("Fail"));
    console.log(value);
  } catch (e) {
    console.log(e.name);
    console.log(e.message);
  }
}

run();

5) Пример с fetch: читаемый шаблон

async function loadJSON(url) {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error("HTTP " + response.status);
  }

  const data = await response.json();
  return data;
}

loadJSON("/api/data")
  .then((data) => {
    console.log(data);
  })
  .catch((e) => {
    console.log(e.message);
  });

Внутри loadJSON - стиль async/await, снаружи можно продолжать использовать then/catch.

6) Несколько await подряд: последовательность

Если написать await один за другим, операции будут идти по очереди.

async function run() {
  const a = await loadJSON("/api/a");
  const b = await loadJSON("/api/b");
  console.log(a, b);
}

Такой вариант логичный, если второй запрос зависит от первого.

7) Параллельные операции: Promise.all

Если зависимости нет, чаще выгоднее запускать параллельно.

async function run() {
  const p1 = loadJSON("/api/a");
  const p2 = loadJSON("/api/b");

  const [a, b] = await Promise.all([p1, p2]);
  console.log(a, b);
}

Здесь оба запроса стартуют сразу, а await Promise.all ждет, пока завершатся оба.

8) Типовые ошибки

1) await вне async

const data = await fetch("/api/data");

Так нельзя. Нужна async-функция:

async function run() {
  const response = await fetch("/api/data");
  const data = await response.json();
  console.log(data);
}

run();

2) Забыть await

async function run() {
  const response = fetch("/api/data");
  console.log(response.ok);
}

response здесь Promise, у него нет ok. Нужен await:

async function run() {
  const response = await fetch("/api/data");
  console.log(response.ok);
}

3) try/catch не ловит ошибки без await

async function run() {
  try {
    fetch("/api/data");
  } catch (e) {
    console.log("not caught");
  }
}

fetch возвращает Promise, ошибка появится позже. Чтобы ловить, нужно await:

async function run() {
  try {
    await fetch("/api/data");
  } catch (e) {
    console.log("caught");
  }
}

Что стоит запомнить

  • async-функция всегда возвращает Promise.
  • await “ждет” Promise и возвращает его результат.
  • await не блокирует страницу, он приостанавливает только текущую async-функцию.
  • Ошибки с await ловят через try/catch.
  • Для параллельных задач удобно Promise.all.