Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
41 changes: 22 additions & 19 deletions run.js
Original file line number Diff line number Diff line change
@@ -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;
}
Expand All @@ -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;
Expand All @@ -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);
}
}

Expand Down
39 changes: 33 additions & 6 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down