Promise.prototype.finally()
finally(onFinally) Returns:
Promise · Added in ves2018 · Updated March 15, 2026 · Async APIs promise async es2018 javascript
The finally() method attaches a callback that will be called when the Promise settles — whether it fulfills or rejects. This is useful for cleanup tasks that should run regardless of the outcome, such as hiding loading spinners, closing connections, or releasing resources.
Syntax
promise.finally(onFinally)
promise.finally(() => {
// cleanup code
})
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
onFinally | function | undefined | Called when the Promise settles (fulfills or rejects). Does not receive any arguments. |
Return Value
A new Promise that resolves or rejects with the same result as the original Promise, unless the onFinally callback throws or returns a rejected Promise.
Examples
Basic Cleanup
let isLoading = true;
fetch("/api/data")
.then(data => {
console.log("Data loaded:", data);
return data;
})
.catch(error => {
console.error("Error:", error.message);
})
.finally(() => {
isLoading = false;
console.log("Request complete");
});
Hiding Loading Spinner
async function loadUserData() {
showSpinner();
try {
const response = await fetch("/api/user");
const user = await response.json();
displayUser(user);
} catch (error) {
showError(error.message);
} finally {
hideSpinner(); // Always runs, regardless of success/failure
}
}
Resetting State
let requestInFlight = false;
function makeRequest(url) {
requestInFlight = true;
return fetch(url)
.then(response => response.json())
.finally(() => {
requestInFlight = false; // Always reset state
});
}
Cleaning Up Resources
function readFile(filename) {
const stream = createReadStream(filename);
return new Promise((resolve, reject) => {
stream.on("data", data => process(data));
stream.on("end", resolve);
stream.on("error", reject);
}).finally(() => {
stream.destroy(); // Always close the stream
});
}
Using with then and catch
fetch("/api/resource")
.then(response => {
if (!response.ok) throw new Error("Not found");
return response.json();
})
.then(data => {
console.log("Got data:", data);
})
.catch(error => {
console.error("Failed:", error.message);
})
.finally(() => {
console.log("Cleaning up...");
resetUI();
});
Common Mistakes
Expecting Arguments
// WRONG: onFinally does not receive arguments
promise.finally(value => {
console.log(value); // undefined!
});
// CORRECT: Use then or catch for values
promise
.then(value => {
console.log("Success:", value);
return value;
})
.catch(error => {
console.error("Error:", error.message);
throw error;
})
.finally(() => {
console.log("Done"); // No arguments
});
Not Returning the Original Promise Value
// WRONG: Missing return breaks chaining
fetch("/api/data")
.then(data => process(data))
.finally(() => {
console.log("Done");
}); // The resolved value is lost!
// CORRECT: finally passes through the value
fetch("/api/data")
.then(data => process(data))
.finally(() => {
console.log("Done");
})
.then(processed => {
console.log(processed); // Still available!
});
Using finally for Error Handling
// WRONG: finally cannot handle errors
promise
.then(data => {
throw new Error("Oops");
})
.finally(() => {
console.log("Cleanup"); // Runs
})
.catch(e => console.log("Caught:", e.message)); // Still catches!
// finally is for cleanup, not error handling
Async Functions in finally
// finally can be async but be careful with timing
async function loadData() {
try {
const data = await fetchData();
return data;
} finally {
await cleanup(); // Waits before continuing
console.log("Fully cleaned up");
}
}
See Also
- Promise.prototype.then() — The foundation of Promise chaining
- Promise.prototype.catch() — Handle rejection specifically
- Promise.resolve() — Create resolved Promises
- Promise.reject() — Create rejected Promises