When calling a generator function, an iterator object is created and returned (in your code example referenced by "gen_"). No generator function code is executed.
This iterator object is used to control the generator function and keeps alive the execution context of the generator function. The iterator object exposes a [[GeneratorLocation]] property which I assume keeps track of where the program execution of the generator is paused.
Through this reference of the iterator object to the generator context, the data of the generator is kept alive whereas normally after finishing program execution of a normal function, this data would be discarded through garbage collection (because nothing would point to it).
Upon your first .next() call on your iterator, the generator executes code until it reaches a yield statement. Then it returns a new object with (in your code example) {value: 1, done: false}. Because done is set to false, the iterator object knows that work is not completed yet so the generator data is kept alive.
When program execution is paused, the generator is non-blocking by taking the generator execution context off the call stack. It is however not discarded because the reference of the iterator object to the generator context keeps the generator data alive.
When calling .next() on the iterator what happens is that the execution context of the generator function is again placed on top of the call stack, continuing the execution where it left off until it reaches no more yield statements, returning an object with {value: undefined, done: true}. Now the iterator object won't point to the generator anymore and the generator function is discarded from memory.