From 337059bf6d3e10a966eff97769dd77a441a29f32 Mon Sep 17 00:00:00 2001 From: Tomi Belan Date: Sat, 24 Aug 2013 15:13:19 -0700 Subject: [PATCH 1/3] Fix throwing errors into the generator Fixes issue #5 --- run.js | 11 +++++------ test.js | 30 +++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/run.js b/run.js index 74b1a93..e505aa8 100644 --- a/run.js +++ b/run.js @@ -10,10 +10,10 @@ function run(generator, callback) { next(); check(); - function nextSafe(item) { + function nextSafe(err, item) { var n; try { - n = iterator.next(item); + n = (err ? iterator.throw(err) : iterator.next(item)); if (!n.done) { if (typeof n.value === "function") n.value(resume()); yielded = true; @@ -26,8 +26,8 @@ function run(generator, callback) { return callback(null, n.value); } - function nextPlain(item) { - var cont = iterator.next(item).value; + function nextPlain(err, item) { + var cont = (err ? iterator.throw(err) : iterator.next(item)).value; // Pass in resume to continuables if one was yielded. if (typeof cont === "function") cont(resume()); yielded = true; @@ -49,8 +49,7 @@ function run(generator, callback) { var item = data[1]; data = null; yielded = false; - if (err) return iterator.throw(err); - next(item); + next(err, item); yielded = true; } } diff --git a/test.js b/test.js index 32bde81..fa5eaa0 100644 --- a/test.js +++ b/test.js @@ -70,7 +70,7 @@ function *run_with_callback(gen) { console.log("Callback"); yield run(function* (gen) { yield sleep(1000); - return "Hello" + return "Hello"; }, function (err, value) { console.log("Callback err: " + err); console.log("Callback value: " + value); @@ -102,7 +102,25 @@ function *run_with_callback_early_exception(gen) { console.log("Callback value: " + value); gen()(null, value); // Intentionally suppress the error }); - console.log("End"); + testRun("run_with_thrown_error", run_with_thrown_error); +} + +function *run_with_thrown_error(gen) { + var inCatch = false; + try { + yield sleep(1); + yield fail(); + console.error("this should not happen!"); + } + catch (err) { + console.log("in catch: " + err); + console.assert(err); + inCatch = true; + } + yield sleep(1); + console.log("yielded after catch"); + console.assert(inCatch); + console.log("\nEnd"); } function sleep(ms) { @@ -117,7 +135,13 @@ function evil() { setTimeout(function () { callback(null, 2); }, 100); - } + }; +} + +function fail() { + return function (callback) { + callback(Error("throwing error into generator")); + }; } function decrement(n) { From 52b86fd0b153168f63919426572e14b051039141 Mon Sep 17 00:00:00 2001 From: Tomi Belan Date: Sat, 24 Aug 2013 17:34:01 -0700 Subject: [PATCH 2/3] Always use an implicit done callback. --- run.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/run.js b/run.js index e505aa8..f3997f0 100644 --- a/run.js +++ b/run.js @@ -5,12 +5,17 @@ function run(generator, callback) { var iterator = generator(resume); var data = null, yielded = false; - var next = callback ? nextSafe : nextPlain; - + if (!callback) callback = function (err) { + // If the generator ended with an error, throw it globally with setTimeout. + // Throwing locally from a callback is not allowed, and swallowing the + // error is a bad idea, so there's no better option. + if (err) setTimeout(function () { throw err; }, 0); + }; + next(); check(); - function nextSafe(err, item) { + function next(err, item) { var n; try { n = (err ? iterator.throw(err) : iterator.next(item)); @@ -25,13 +30,6 @@ function run(generator, callback) { } return callback(null, n.value); } - - function nextPlain(err, item) { - var cont = (err ? iterator.throw(err) : 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; From 0eeee5f894face8b84d6f8ee70710cf0e68546e7 Mon Sep 17 00:00:00 2001 From: Tomi Belan Date: Sat, 24 Aug 2013 20:45:50 -0700 Subject: [PATCH 3/3] Make gen more strict about being called exactly once --- README.md | 2 +- run.js | 18 ++++++++++++------ test.js | 9 ++++++--- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index eb02eab..89f03e8 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ run(function* (gen) { }); ``` -This alternate API can be mixed and matched with continuable style APIs. If you yield a function, it will assume it's a continuable and pass in a callback. If you don't, it's your responsibility to pass in the generated callback manually in the right place. +This alternate API can be mixed and matched with continuable style APIs. If you call `gen()`, you still have to yield afterwards, but the actual yielded value will be ignored. If you don't call `gen()`, you have to yield a continuable function, which will be called by gen-run appropriately. If you want to use delegate yield with the explicit style it's up to you to pass the `gen` function to the child generator. diff --git a/run.js b/run.js index f3997f0..b0b716a 100644 --- a/run.js +++ b/run.js @@ -1,9 +1,9 @@ module.exports = run; function run(generator, callback) { - // Pass in resume for no-wrap function calls - var iterator = generator(resume); - var data = null, yielded = false; + // Pass in gen for no-wrap function calls + var iterator = generator(gen); + var data = null, yielded = false, calledGen = false; if (!callback) callback = function (err) { // If the generator ended with an error, throw it globally with setTimeout. @@ -20,7 +20,10 @@ function run(generator, callback) { try { n = (err ? iterator.throw(err) : iterator.next(item)); if (!n.done) { - if (typeof n.value === "function") n.value(resume()); + if (!calledGen && typeof n.value === "function") n.value(gen()); + if (!calledGen) { + throw Error("generator didn't yield a continuable or call gen()"); + } yielded = true; return; } @@ -31,7 +34,10 @@ function run(generator, callback) { return callback(null, n.value); } - function resume() { + function gen() { + if (yielded) throw Error("gen() can only be called from the generator"); + if (calledGen) throw Error("gen() already called"); + calledGen = true; var done = false; return function () { if (done) return; @@ -47,8 +53,8 @@ function run(generator, callback) { var item = data[1]; data = null; yielded = false; + calledGen = false; next(err, item); - yielded = true; } } diff --git a/test.js b/test.js index fa5eaa0..8d1dbcf 100644 --- a/test.js +++ b/test.js @@ -68,39 +68,42 @@ function* nowrap_sub(gen, n) { function *run_with_callback(gen) { console.log("Callback"); + var resume = gen(); yield run(function* (gen) { yield sleep(1000); return "Hello"; }, function (err, value) { console.log("Callback err: " + err); console.log("Callback value: " + value); - gen()(err, value); + resume(err, value); }); testRun("run_with_callback_exception", run_with_callback_exception); } function *run_with_callback_exception(gen) { console.log("Callback err"); + var resume = gen(); yield run(function *(gen) { yield sleep(1000); throw new Error("Some error"); }, function (err, value) { console.log("Callback err: " + err); console.log("Callback value: " + value); - gen()(null, value); // Intentionally suppress the error + resume(null, value); // Intentionally suppress the error }); testRun("run_with_callback_early_exception", run_with_callback_early_exception); } function *run_with_callback_early_exception(gen) { console.log("Callback err"); + var resume = gen(); yield run(function *(gen) { throw new Error("Some error"); yield sleep(1000); }, function (err, value) { console.log("Callback err: " + err); console.log("Callback value: " + value); - gen()(null, value); // Intentionally suppress the error + resume(null, value); // Intentionally suppress the error }); testRun("run_with_thrown_error", run_with_thrown_error); }