DEV Community

Cover image for Exploiting the generator to make a pause/resumable flow.
Hao
Hao

Posted on

Exploiting the generator to make a pause/resumable flow.

Sorry English is not my primary language, please bear with me :p

Recently I've come up with an idea that I've never seen anyone has used it before.

That is, to use generators as a linear flow control.

Such as you have a liner program, but sometimes you need to pause it somewhere and resume it later when some condition is met or waiting for an user input.

You don't want to make that program separate functions since it's messy and can fall into the callback hell easily. Also, that not looks linear at all!

So I came up with this monstrosity:

const next = (_=((function*() {
  // ...
})())).next.bind(_);

Essentially it's just this, but in one line:

const generator = function*() {
  // ...
}
const iterator = generator();
const next = iterator.next.bind(iterator);

What does this thing do? Let's have an example.
Such as this interactive dialog. The first time when you run next(), it will print the first dialogue, and stops at yield.

Then you call the next() function again. It starts where it was before, and print out the next dialogue. how cool is that?

const sarahStyle = 'color: orangered; margin-right: 1em;'
const yourStyle = 'color: lime; margin-right: 1em;'

const next = (_=((function*() {
  console.log('%cSarah:', sarahStyle, 'Hello, my name is Sarah. What is your name?');
  yield;

  console.log('%cYou:', yourStyle, 'Hi, my name is James. Nice to meet you.');
  yield;

  console.log('%cSarah:', sarahStyle, 'Wanna go out for a walk?');
  console.log('%cSarah:', sarahStyle, 'Since it\'s such a nice weather outside.');
  yield;

  console.log('%cYou:', yourStyle, 'Sure, why not?');
})())).next.bind(_);

next();
next();
next();

But you might ask, why not just use await like this?

console.log('%cSarah:', sarahStyle, 'Hello, my name is Sarah. What is your name?');
await somethingIsDone();
console.log('%cYou:', yourStyle, 'Hi, my name is James. Nice to meet you.');
await somethingIsDone();
console.log('%cSarah:', sarahStyle, 'Wanna go out for a walk?');
console.log('%cSarah:', sarahStyle, 'Since it\'s such a nice weather outside.');

That's a very good question.

I think the generator solution has 2 major advantages:

  1. the next() function returns a state object, which looks like this: {value: undefined, done: false}. So you can easily determine whether this flow is done or not. But for async functions, you have to get the instance of the promise first.

  2. It resumes itself by just calling the next() function again. If you're using a promise instead, you have to find a way to resolve the promise to make the flow continue, which is less straightforward.

Here's an example that I use this technique to create an installation simulator:

Hope you find this is useful, happy coding :)

Top comments (0)