Reentrancy means the same in JS as in other languages: A routine is re-entered when it is called again while it is still running. It's called reentrant when it is safe to do so (where "safe" is a really broad term, usually meaning "everything works still as expected") - or more formally, the function still fulfills its contract when called in that manner.
There are a few scenarios where this is relevant in JS (where a function usually runs to completion without being "interrupted" by something else):
- The function is asynchronous. Parts of the two calls might run interleaved with each other. If that interleaving happens in a way the developer didn't expect, it's called a race condition.
- The function takes (and calls) a callback. The user-supplied callback might call the function again.
- The function is a generator function. It is called multiple times and the generators are consumed alternately.
A function that uses only call-local state (or is completely pure) is always reentrant. Things get messy when your function modifies global state, and especially when it does break the invariants of your data structures "only during the function call".
Here's a simple example of a non-reentrant generator function:
var k = 0;
function* countTo(n) {
while (k < n)
yield k++;
k = 0;
}
for (const x of countTo(3))
console.log(x);
for (const y of countTo(7))
console.log(y);
Works, right? No, it doesn't, as k
is a global variable here. Just consider
for (const x of countTo(3))
for (const y of countTo(7))
console.log(x, y);
Oops. The same functionality written as a reentrant generator function:
function* countTo(n) {
for (var k = 0; k < n; k++)
yield k;
}