finally() нужен, чтобы выполнить код после завершения промиса независимо от результата. Метод вызывается у объекта Promise, срабатывает и при успехе, и при ошибке, а затем возвращает новый промис.
Синтаксис
Базовая запись выглядит так:
promise.finally(onFinally)
Где:
promise- это объектPromiseonFinally- функция, которая выполнится после завершения промиса
Обычно в 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() возникнет новая ошибка.