До сих пор генератор был у нас "фонтаном значений": ты просишь next(), он отдаёт следующее значение, и всё общение с ним идёт в одну сторону. Но генератор в Python устроен глубже - в него можно отправлять значения снаружи, бросать внутрь исключения и закрывать его досрочно. Именно на этой возможности изначально строился asyncio, до того как в языке появились async/await как отдельный синтаксис.