buffer subscribes to its argument one time, with each emission from that subscription signalling the current buffer to close and emit, restarting the buffer.
The argument to bufferWhen is a function that returns an observable. bufferWhen calls it and subscribes to the result immediately, waiting for an emission to signal the close of its buffer. Crucially, the first emission will be the only one that matters, because upon that event, the buffer closes, the subscription to the signal observable ends, and the steps in this paragraph repeat until the observable we're buffering completes.
When is this useful? Sometimes when buffering an observable, you may need to do some setup before you can emit the signal at the right time. This setup could involve generating some initial value, perhaps based on the current "state of the world", or generated randomly. As of this writing, the Rx docs for bufferWhen describe an example of buffering on a timer that lasts a different random duration each time.
Addendum: to visualize the difference between the two, it's useful to see bufferWhen implemented using buffer:
const myBufferWhen = fn => buffer(
defer(fn).pipe(
take(1),
repeat()
)
);