Learn JavaScript

Promise в JavaScript: then, catch и finally

Promise - это объект, который представляет результат асинхронной операции: он появится позже (успех) или операция завершится ошибкой.

У Promise есть три основных состояния:

  • pending - в процессе
  • fulfilled - успешно
  • rejected - с ошибкой

В повседневном коде важнее другое: как правильно читать результат и ловить ошибки.

1) then: получить успешный результат

then принимает функцию, которая получит результат, когда Promise выполнится успешно.

Promise.resolve("OK").then((value) => {
  console.log(value); // "OK"
});

2) catch: обработать ошибку

catch срабатывает, если Promise завершился с ошибкой.

Promise.reject(new Error("Boom")).catch((error) => {
  console.log(error.name);    // "Error"
  console.log(error.message); // "Boom"
});

3) finally: выполнить в любом случае

finally запускается и при успехе, и при ошибке. Обычно там убирают loading, закрывают ресурсы, включают кнопки обратно.

Promise.resolve("OK")
  .then((value) => {
    console.log(value);
  })
  .finally(() => {
    console.log("done");
  });

4) Цепочки then: шаги один за другим

Главная сила Promise - цепочки. Если из then вернуть значение, оно станет результатом следующего then.

Promise.resolve(2)
  .then((n) => n * 3)
  .then((n) => n + 1)
  .then((result) => {
    console.log(result); // 7
  });

5) Возврат Promise из then

Если вернуть Promise, цепочка подождет его завершения.

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

wait(200)
  .then(() => {
    console.log("step 1");
    return wait(200);
  })
  .then(() => {
    console.log("step 2");
  });

6) Ошибки: как они “падают” в catch

Если внутри then произойдет ошибка, она автоматически перейдет в catch.

Promise.resolve("data")
  .then(() => {
    throw new Error("Broken");
  })
  .catch((error) => {
    console.log(error.message); // "Broken"
  });

То же самое, если вернулся rejected Promise.

Promise.resolve("data")
  .then(() => Promise.reject(new Error("Rejected")))
  .catch((error) => {
    console.log(error.message); // "Rejected"
  });

7) catch можно ставить в конце

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

Promise.resolve(10)
  .then((x) => x * 2)
  .then((x) => {
    if (x > 10) {
      throw new Error("Too big");
    }
    return x;
  })
  .then((x) => {
    console.log(x);
  })
  .catch((error) => {
    console.log(error.message);
  })
  .finally(() => {
    console.log("finish");
  });

8) Продолжить цепочку после catch

catch тоже возвращает Promise. Если из catch вернуть значение, цепочка продолжится как успешная.

Promise.reject(new Error("Fail"))
  .catch(() => {
    return 0;
  })
  .then((value) => {
    console.log(value); // 0
  });

Если внутри catch снова бросить ошибку, цепочка останется с ошибкой.

Promise.reject(new Error("Fail"))
  .catch(() => {
    throw new Error("Still fail");
  })
  .catch((error) => {
    console.log(error.message); // "Still fail"
  });

9) Типовые ловушки

1) Забыть return в then

Если внутри then нужно передать значение дальше, оно должно быть возвращено.

Promise.resolve(5)
  .then((x) => {
    x * 2;
  })
  .then((value) => {
    console.log(value); // undefined
  });

Правильно:

Promise.resolve(5)
  .then((x) => x * 2)
  .then((value) => {
    console.log(value); // 10
  });

2) Пытаться ловить ошибки через try/catch вокруг Promise

try/catch работает только для синхронного кода. Для Promise нужен catch или async/await с try/catch.

try {
  Promise.reject(new Error("Fail"));
} catch (e) {
  console.log("not works");
}

Здесь catch не поймает ошибку Promise.

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

  • then - обработка успешного результата.
  • catch - обработка ошибки (в том числе ошибок внутри then).
  • finally - выполняется всегда, удобно для “убрать загрузку”.
  • Возврат значения из then передает его дальше по цепочке.
  • Возврат Promise из then заставляет цепочку ждать.
  • Чтобы передать значение дальше, в then нужен return.