Learn JavaScript

finally() - завершающий обработчик промиса в JavaScript

finally() нужен, чтобы выполнить код после завершения промиса независимо от результата. Метод вызывается у объекта Promise, срабатывает и при успехе, и при ошибке, а затем возвращает новый промис.

Синтаксис

Базовая запись выглядит так:

promise.finally(onFinally)

Где:

  • promise - это объект Promise
  • onFinally - функция, которая выполнится после завершения промиса

Обычно в finally() помещают код очистки: скрытие лоадера, остановку спиннера, закрытие соединения или освобождение других ресурсов.

Что возвращает finally()

finally() всегда возвращает новый промис.

const promise = Promise.resolve(5);
const nextPromise = promise.finally(() => {
  console.log("Завершение");
});

console.log(nextPromise instanceof Promise); // true

nextPromise.then((value) => {
  console.log(value); // 5
});

Результат finally() - не обычное значение, а новый промис, поэтому цепочку можно продолжать дальше.

Простой пример

Promise.resolve("Данные готовы")
  .finally(() => {
    console.log("Запрос завершён");
  })
  .then((value) => {
    console.log(value); // "Данные готовы"
  });

Сначала выполняется finally(), а затем цепочка продолжается дальше с исходным результатом.

finally() срабатывает и при успехе, и при ошибке

Это главный смысл метода.

Promise.resolve("ok")
  .finally(() => {
    console.log("Завершилось успешно");
  });

Promise.reject(new Error("boom"))
  .finally(() => {
    console.log("Завершилось с ошибкой");
  })
  .catch((error) => {
    console.log(error.message); // "boom"
  });

То есть finally() не выбирает между успешным и ошибочным сценарием. Он запускается в обоих случаях.

finally() не получает результат или ошибку

Это очень важный нюанс.

В отличие от then() и catch(), колбэк finally() не получает аргументы.

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

Поэтому finally() не подходит, если нужно обработать значение или саму ошибку. Для этого используют then() и catch().

Возврат значения из finally() обычно ничего не меняет

Это ещё один важный момент.

Если в finally() вернуть обычное значение, оно не заменит результат цепочки.

Promise.resolve(5)
  .finally(() => {
    return 100;
  })
  .then((value) => {
    console.log(value); // 5
  });

Исходный результат остаётся прежним.

То же самое относится и к отклонённому промису:

Promise.reject(new Error("boom"))
  .finally(() => {
    return "ignored";
  })
  .catch((error) => {
    console.log(error.message); // "boom"
  });

Если finally() возвращает промис, цепочка его подождёт

Хотя значение из finally() обычно не подменяет результат, цепочка всё равно дождётся промиса, который был возвращён из этого колбэка.

Promise.resolve("ok")
  .finally(() => {
    return Promise.resolve("wait");
  })
  .then((value) => {
    console.log(value); // "ok"
  });

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

Если в finally() выбросить ошибку, она заменит исходный результат

Вот здесь поведение уже меняется.

Если внутри finally() возникает ошибка, новый промис переходит в состояние rejected, даже если до этого всё было успешно.

Promise.resolve("ok")
  .finally(() => {
    throw new Error("Ошибка в finally");
  })
  .catch((error) => {
    console.log(error.message); // "Ошибка в finally"
  });

То же самое произойдёт, если вернуть отклонённый промис:

Promise.reject(new Error("boom"))
  .finally(() => {
    return Promise.reject(new Error("Ошибка из finally"));
  })
  .catch((error) => {
    console.log(error.message); // "Ошибка из finally"
  });

finally() не заменяет then() и catch()

Это полезно держать в голове.

  • then() нужен для обработки успешного результата
  • catch() нужен для обработки ошибки
  • finally() нужен для кода, который должен выполниться в любом случае

Пример:

fetch("/api/user")
  .then((response) => response.json())
  .catch((error) => {
    console.log("Ошибка:", error);
  })
  .finally(() => {
    console.log("Убираем loader");
  });

Здесь finally() не анализирует результат запроса. Он просто выполняет завершающее действие.

finally() не меняет исходный промис

Как и then() с catch(), этот метод создаёт новый промис, но сам исходный объект не изменяет.

const promise = Promise.resolve("ok");
const nextPromise = promise.finally(() => {
  console.log("done");
});

console.log(promise === nextPromise); // false

Это значит, что исходный промис и продолжение цепочки остаются разными объектами.

Когда это полезно

finally() особенно удобен, когда:

  • нужно убрать индикатор загрузки независимо от результата
  • нужно завершить анимацию или разблокировать интерфейс
  • нужно закрыть соединение, таймер или другой ресурс
  • важен общий завершающий шаг после then() и catch()

Пример:

let isLoading = true;

Promise.resolve("Данные")
  .then((value) => {
    console.log(value); // "Данные"
  })
  .finally(() => {
    isLoading = false;
    console.log(isLoading); // false
  });

Такой код хорошо показывает типичную роль finally(): не обработка данных, а завершение работы.

Частые ошибки

Пытаться получить значение прямо в finally()

Promise.resolve(5)
  .finally((value) => {
    console.log(value); // undefined
  });

Колбэк finally() не получает результат промиса.

Думать, что return из finally() заменит результат

Promise.resolve(5)
  .finally(() => {
    return 100;
  })
  .then((value) => {
    console.log(value); // 5
  });

Обычный return внутри finally() не подменяет исходное значение.

Использовать finally() вместо catch() для обработки ошибки

Promise.reject(new Error("boom"))
  .finally(() => {
    console.log("Ошибка была, но здесь её не получить");
  })
  .catch((error) => {
    console.log(error.message); // "boom"
  });

Если нужно именно обработать ошибку, нужен catch().

Не учитывать, что ошибка в finally() ломает успешную цепочку

Promise.resolve("ok")
  .finally(() => {
    throw new Error("fail");
  })
  .catch((error) => {
    console.log(error.message); // "fail"
  });

Ошибка внутри finally() важнее предыдущего успешного результата.

Забывать, что finally() ждёт возвращённый промис

Promise.resolve("ok")
  .finally(() => {
    return Promise.resolve("cleanup");
  })
  .then((value) => {
    console.log(value); // "ok"
  });

Цепочка продолжится только после завершения промиса из finally().

Быстрая памятка

  • finally() вызывается у объекта Promise
  • метод принимает один колбэк без аргументов
  • finally() всегда возвращает новый промис
  • метод срабатывает и при fulfilled, и при rejected
  • finally() не получает результат или ошибку
  • обычный return из finally() обычно не меняет значение цепочки
  • если вернуть промис, цепочка подождёт его завершения
  • если выбросить ошибку или вернуть Promise.reject(...), цепочка станет rejected
  • finally() нужен для завершающих действий, а не для обработки результата

Коротко

finally() в JavaScript нужен, чтобы выполнить завершающий код после промиса независимо от результата. Главное, что стоит запомнить: метод всегда возвращает новый промис, не получает значение или ошибку цепочки, обычно не меняет результат при обычном return, но может сломать цепочку, если внутри finally() возникнет новая ошибка.