-
-
Save creationix/5762837 to your computer and use it in GitHub Desktop.
| function run(generator) { | |
| // Pass in resume for no-wrap function calls | |
| var iterator = generator(resume); | |
| var data = null, yielded = false; | |
| next(); | |
| check(); | |
| function next(item) { | |
| var cont = iterator.next(item).value; | |
| // Pass in resume to continuables if one was yielded. | |
| if (typeof cont === "function") cont(resume()); | |
| yielded = true; | |
| } | |
| function resume() { | |
| var done = false; | |
| return function () { | |
| if (done) return; | |
| done = true; | |
| data = arguments; | |
| check(); | |
| }; | |
| } | |
| function check() { | |
| while (data && yielded) { | |
| var err = data[0]; | |
| var item = data[1]; | |
| data = null; | |
| yielded = false; | |
| if (err) return iterator.throw(err); | |
| next(item); | |
| yielded = true; | |
| } | |
| } | |
| } |
Using the same evil function as above with the updated version of run, we get the following thunk version:
function sleep(ms) {
return function (callback) {
setTimeout(callback, ms);
};
}
run(function* () {
console.log("Hello");
yield evil;
yield sleep(1000);
console.log("World");
});Which runs with no problem, same as before.
Or if we prefer to wrap setTimeout inline, it's written as:
run(function* () {
console.log("Hello");
yield evil;
yield function (callback) {
setTimeout(callback, 1000);
};
console.log("World");
});But we can also work in resume mode without errors.
run(function* (resume) {
console.log("Hello");
yield evil(resume());
yield setTimeout(resume(), 1000);
console.log("World");
});But if you use resume mode with the shared callback, things get messy
I briefly responded on twitter, but to elaborate a bit more... isn't a nasty exception a good thing if you have async code invoking a callback multiple times? If that's happening, there's clearly a bug and I imagine it would be best to "die early and die often" in that case, rather than simply ignore it. Granted, the thunk-style approach could choose to raise a more descriptive error in that case (+1 on that from me, anyway).
Hi, creationix.
May I ask you a question?
In the following code, you pass an argument to iterator.next() method.
10 var cont = iterator.next(item).value;
But whenever I pass any value to iterator.next(), there's nothing to happen.
var x = function()* {
yield true;
}
var y = x();
y.next('foo'); //return true
What's the meaning of passing an argument to iterator.next() ?
NOTE: This is using an old version of run. See next comment for new example.
I updated the code to implement callback double call checks for thunk mode.
Suppose you have an evil function like this:
Then you can call it using thunk mode:
And everything is fine because of the done flag in each thunk's unique callback.
But if you use resume mode with the shared callback, things get messy:
Which crashes with the following output:
Hello World /home/tim/Downloads/files-node/files.js:201 var cont = iterator.next(item).value; ^ Error: Generator has already finished at GeneratorFunctionPrototype.next (native) at next (/home/tim/Downloads/files-node/files.js:201:25) at check (/home/tim/Downloads/files-node/files.js:220:7) at null._onTimeout (/home/tim/Downloads/files-node/files.js:193:5) at Timer.listOnTimeout [as ontimeout] (timers.js:105:15)Because the second callback in evil causes sleep's yield to resume early and when sleep is finally done, the generator is already finished.