What Is New in Node.js 15?
| Category | Change |
|---|---|
| New Features | npm 7 bundled |
| New Features | Unhandled promise rejections now throw by default |
| New Features | V8 8.6 -- logical assignment operators, Promise.any, String.replaceAll |
| Improvements | QUIC (experimental) network protocol support |
| Improvements | AbortController (experimental) added |
| Improvements | crypto: experimental Web Crypto API added |
| Deprecated | --unhandled-rejections=throw no longer needed (it is now the default) |
Unhandled Promise Rejections Now Crash the Process
In Node.js 15, an unhandled promise rejection terminates the process with a non-zero exit code by default. Previously this was a warning. This is the most impactful breaking change in v15 and affects any code where rejected promises are not caught.
// This will now CRASH the process in Node.js 15+
async function fail() {
throw new Error('oops');
}
fail(); // no await, no .catch() -- EXIT 1
// Fix: always handle rejections
fail().catch(console.error);
// or:
process.on('unhandledRejection', (err) => { /* log + exit */ });
This forces better error handling discipline but will expose existing bugs in codebases that silently ignored rejected promises.
String.replaceAll() and Promise.any()
String.prototype.replaceAll() replaces all occurrences of a substring without a regex. Promise.any() resolves as soon as the first promise in the iterable fulfills -- the inverse of Promise.race() for success cases.
// No more regex for global replace
'foo-bar-baz'.replaceAll('-', '_'); // 'foo_bar_baz'
// First successful result
const result = await Promise.any([fetchA(), fetchB(), fetchC()]);
Logical Assignment Operators
Three new operators landed: &&=, ||=, and ??=. They combine logical evaluation with assignment, reducing boilerplate for common patterns like setting defaults.
let config = { retries: null };
config.retries ??= 3; // sets to 3 because null
config.timeout ||= 5000; // sets to 5000 because falsy
config.debug &&= verbose; // assigns verbose only if config.debug is truthy
Experimental Web Crypto API
The Web Cryptography API (globalThis.crypto) lands experimentally in Node.js 15. You can use it to generate keys, sign data, hash values, and derive secrets using the same API as browsers -- without importing node:crypto.
FAQ
Will existing code break from the unhandled rejection change?
Only if you have promises that reject without a .catch() handler or await in a try/catch. Run your test suite on Node.js 15 first -- unhandled rejections that were previously warnings will now cause test failures.
Is QUIC ready to use in Node.js 15?
No -- QUIC is marked experimental. The implementation was incomplete at v15 and later removed from core in subsequent versions. Do not use it in production.
Does Promise.any() throw if all promises reject?
Yes, it throws an AggregateError containing all the individual errors. You can inspect err.errors to see each rejection reason.
Is Node.js 15 an LTS version?
No. Node.js 15 reached end-of-life in June 2022. Migrate to Node.js 18 LTS or Node.js 20 LTS.
How do I suppress unhandled rejection exits temporarily during debugging?
Start Node.js with --unhandled-rejections=warn to revert to the old warning-only behavior. This is useful when debugging without full promise coverage in test environments.