Skip to content

Instantly share code, notes, and snippets.

@bjouhier
Created May 18, 2012 17:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bjouhier/2726551 to your computer and use it in GitHub Desktop.
Save bjouhier/2726551 to your computer and use it in GitHub Desktop.
Asynchronous hello world with JS generators
(function() {
var GENERATOR_PROTO = Object.getPrototypeOf((function() {
yield;
})());
function isGenerator(val) {
return typeof val === 'object' && Object.getPrototypeOf(val) === GENERATOR_PROTO;
}
var PENDING = {};
window.AsyncGen = {
run: function(fn, args, idx) {
var cb = args[idx],
g;
function resume(err, val) {
while (g) {
try {
val = err ? g.throw(err) : g.send(val);
err = null;
// if we get PENDING, the current call completed with a pending I/O
// resume will be called again when the I/O completes. So just return here.
if (val === PENDING) return;
// if we get [PENDING, e, r], the current call invoked its callback synchronously
// we just loop to send/throw what the callback gave us.
if (val && val[0] === PENDING) {
err = val[1];
val = val[2];
}
// else, if g yielded a value which is not a generator, g is done.
// so we unwind it we send val to the parent generator (or through cb if we are at the top)
else if (!isGenerator(val)) {
g.close();
g = g.prev;
}
// else, we got a new generator which means that g called another generator function
// the new generator become current and we loop with g.send(undefined) (equiv to g.next())
else {
val.prev = g;
g = val;
val = undefined;
}
} catch (ex) {
// the send/throw call failed.
// we unwind the current generator and we rethrow into the parent generator (or through cb if at the top)
g.close();
g = g.prev;
err = ex;
val = undefined;
}
}
// we have exhausted the stack of generators.
// return the result or error through the callback.
cb(err, val);
}
// set resume as the new callback
args[idx] = resume;
// call fn to get the initial generator
g = fn.apply(this, args);
// start the resume loop
resume();
},
invoke: function(that, fn, args, idx) {
// Set things up so that call returns:
// * PENDING if it completes with a pending I/O (and cb will be called later)
// * [PENDING, e, r] if the callback is called synchronously.
var result = PENDING,
sync = true;
var cb = args[idx];
args[idx] = function(e, r) {
if (sync) {
result = [PENDING, e, r];
} else {
cb(e, r);
}
}
fn.apply(that, args);
sync = false;
return result;
}
};
})();
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<script src="asyncgen.js" type="application/javascript;version=1.7"></script>
<script src="hello.js" type="application/javascript;version=1.7"></script>
</head>
<body>
<h1>Asynchronous hello world with generators (Firefox only)</h1>
<button onClick="javascript:startDemo();return;">Start</button>
<div id="output"></div>
</body>
</html>
function print(str) {
document.getElementById('output').innerHTML += "<p>" + str + "</p>";
}
function delay(_, result) {
yield AsyncGen.invoke(this, setTimeout, [_, 1000], 0);
yield result;
}
function helloWorld(_) {
print("starting ...");
print(yield delay(_, 'hello ...'));
print(yield delay(_, '... world'));
print("done!");
yield;
}
function startDemo() {
AsyncGen.run(helloWorld, [function(err) {
if (err) alert(err);
else print("continuing with callback");
}], 0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment