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 74b1a93..b0b716a 100644 --- a/run.js +++ b/run.js @@ -1,21 +1,29 @@ 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. + // 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); + }; - var next = callback ? nextSafe : nextPlain; - next(); check(); - function nextSafe(item) { + function next(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()); + 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; } @@ -25,15 +33,11 @@ function run(generator, callback) { } return callback(null, n.value); } - - function nextPlain(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() { + 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; @@ -49,9 +53,8 @@ function run(generator, callback) { var item = data[1]; data = null; yielded = false; - if (err) return iterator.throw(err); - next(item); - yielded = true; + calledGen = false; + next(err, item); } } diff --git a/test.js b/test.js index 32bde81..8d1dbcf 100644 --- a/test.js +++ b/test.js @@ -68,41 +68,62 @@ 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" + 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 }); - 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 +138,13 @@ function evil() { setTimeout(function () { callback(null, 2); }, 100); - } + }; +} + +function fail() { + return function (callback) { + callback(Error("throwing error into generator")); + }; } function decrement(n) {