Getting every match with capture groups meant a while loop over regex.exec, mutating lastIndex, with an infinite loop waiting for anyone who forgets the g flag.
const re = /(?<key>w+)=(?<value>[^;]+)/g;
for (const m of header.matchAll(re)) {
console.log(m.groups.key, m.groups.value);
}
// it is an iterator, so spread it if you need length or indexing
const all = [...header.matchAll(re)];
// and without /g it throws, which is the right failure
Throwing on a non-global regex is a considerable improvement over the old loop, which simply never terminated. The iterator is lazy, so a large input can be processed without materialising every match — and it means the result cannot be iterated twice, which is the one thing to watch. Named capture groups combined with this make a small parser genuinely readable, which was not true of the exec version.