Skip to content

Commit

Permalink
Update Q.async to work with current ES6 generators
Browse files Browse the repository at this point in the history
ES6 generators are slightly different from what has been shipped in
SpiderMonkey.  For our purposes, the differences are:

  * they use function* as a token;
  * they box their return value in a { value: <any>, done: <bool> }
    object; and
  * they can return a value.

They are close enough to SpiderMonkey that Q.async can support both.
This commit checks for legacy generators at Q startup, and expects
unboxed values in that case.

This commit also updates the examples.  In particular, the wishful
thinking and shim examples go away, as they are handled naturally with
the new specification.  A copy of the legacy examples has been moved to
the examples/async-generators/js-1.7/ folder.
  • Loading branch information
wingo committed May 8, 2013
1 parent baa0db4 commit d69254e
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 60 deletions.
9 changes: 5 additions & 4 deletions examples/async-generators/0.html
Expand Up @@ -2,14 +2,15 @@
<html>
<head>
<!--
Works in Firefox, or where generators are implemented.
Works in browsers that support ES6 geneartors, like Chromium 29 with
the --harmony flag.
Peter Hallam, Tom van Cutsem, Mark S. Miller, Dave Herman.
Peter Hallam, Tom van Cutsem, Mark S. Miller, Dave Herman, Andy Wingo.
The animation example was taken from
<http://wiki.ecmascript.org/doku.php?id=strawman:deferred_functions>
-->
<script src="../../q.js"></script>
<script type="application/javascript;version=1.7">
<script>

function delay(millis, answer) {
const deferredResult = Q.defer();
Expand All @@ -19,7 +20,7 @@
return deferredResult.promise;
}

var deferredAnimate = Q.async(function(element) {
var deferredAnimate = Q.async(function*(element) {
for (var i = 0; i < 100; ++i) {
element.style.marginLeft = ''+i+'px';
yield delay(20);
Expand Down
10 changes: 5 additions & 5 deletions examples/async-generators/1-return.html
Expand Up @@ -2,22 +2,22 @@
<html>
<head>
<!--
Works in Firefox, or where generators are implemented.
Works in browsers that support ES6 geneartors, like Chromium 29 with
the --harmony flag.
-->
<script src="../../q.js"></script>
<script type="application/javascript;version=1.7">
<script>

function test() {

var generator = Q.async(function () {
var generator = Q.async(function* () {
var ten = yield 10;
console.log(ten, 10);
var twenty = yield ten + 10;
console.log(twenty, 20);
var thirty = yield twenty + 10;
console.log(thirty, 30);
StopIteration.value = thirty + 10;
throw StopIteration;
return thirty + 10;
});

Q.when(generator(), function (forty) {
Expand Down
7 changes: 4 additions & 3 deletions examples/async-generators/2-error-propagation.html
Expand Up @@ -2,14 +2,15 @@
<html>
<head>
<!--
Works in Firefox, or where generators are implemented.
Works in browsers that support ES6 geneartors, like Chromium 29 with
the --harmony flag.
-->
<script src="../../q.js"></script>
<script type="application/javascript;version=1.7">
<script>

function test() {

var generator = Q.async(function () {
var generator = Q.async(function* () {
try {
var ten = yield Q.reject('Rejected!');
console.log("Should not get here 1");
Expand Down
35 changes: 19 additions & 16 deletions examples/async-generators/README.md
Expand Up @@ -8,11 +8,19 @@ decorate a generator function such that ``yield`` is
effectively equivalent to ``await`` or ``defer`` syntax as
supported by languages like Go and, reportedly, C#3.

Generator functions are presently only supported by SpiderMonkey, but
they are (with some changes) on standards track, and very similar down
to details to generators in Python.
Generator functions are presently on the standards track for ES6. As of
May 2013, they are only fully supported by bleeding edge V8, which
hasn't made it out to a released Chromium yet but will probably be in
Chromium 29. Generators have been in SpiderMonkey for years, but in an
older pre-standard form based on Python's design. The Traceur
transpiler also still uses Python-style generators, which were part of
an older ES6 draft.

function count() {
Q's ``async`` function supports both kinds of generators. These
examples will use the ES6 style. See the examples and notes in
[js-1.7](js-1.7/) for more on getting these to work with SpiderMonkey.

function* count() {
var i = 0;
while (true) {
yield i++;
Expand All @@ -27,7 +35,7 @@ to details to generators in Python.
``yield`` can also return a value, if the ``send`` method of
a generator is used instead of ``next``.

var buffer = (function () {
var buffer = (function* () {
var x;
while (true) {
x = yield x;
Expand All @@ -43,7 +51,7 @@ a generator is used instead of ``next``.

We can use ``yield`` to wait for a promise to resolve.

var eventualAdd = Q.async(function (oneP, twoP) {
var eventualAdd = Q.async(function* (oneP, twoP) {
var one = yield oneP;
var two = yield twoP;
return one + two;
Expand All @@ -54,13 +62,8 @@ We can use ``yield`` to wait for a promise to resolve.
three === 3;
});

Or, at least we could. For now, SpiderMonkey does not allow
return values in generators. When they do, ``Q.async`` will
properly fulfill the result of eventualAdd. Until then,
``eventualAdd`` will resolve to ``undefined`` when the job
is done, when the generator throws ``StopIteration``.

As a stop-gap, you can fake the return value by tacking a
``value`` property on an explicitly thrown
``StopIteration``, as in Example 1, in this directory.

To use these in SpiderMonkey, change ``function`` to ``function*``.
Also, in this last example, SpiderMonkey does not allow return values in
generators. To work around that, call the ``Q.return`` function instead
of using a ``return`` statement. ``Q.return`` will go away at some
point when SpiderMonkey switches to ES6 style.
43 changes: 43 additions & 0 deletions examples/async-generators/js-1.7/0.html
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<!--
Works in Firefox, or where generators are implemented.
Peter Hallam, Tom van Cutsem, Mark S. Miller, Dave Herman.
The animation example was taken from
<http://wiki.ecmascript.org/doku.php?id=strawman:deferred_functions>
-->
<script src="../../../q.js"></script>
<script type="application/javascript;version=1.7">

function delay(millis, answer) {
const deferredResult = Q.defer();
setTimeout(function() {
deferredResult.resolve(answer);
}, millis);
return deferredResult.promise;
}

var deferredAnimate = Q.async(function(element) {
for (var i = 0; i < 100; ++i) {
element.style.marginLeft = ''+i+'px';
yield delay(20);
}
});

function test() {
Q.when(
deferredAnimate(document.getElementById('box')),
function () {
alert('Done!');
}
)
}

</script>
</head>
<body onload="test()">
<div id="box" style="width: 20px; height: 20px; background-color: red;"></div>
</body>
</html>
Expand Up @@ -2,25 +2,25 @@
<html>
<head>
<!--
Ought to work in ES.next, if return sugar for
generators gets added.
Works in Firefox, or where generators are implemented.
-->
<script src="../../q.js"></script>
<script src="../../../q.js"></script>
<script type="application/javascript;version=1.7">

function test() {

var eventuallyFourty = Q.async(function () {
var generator = Q.async(function () {
var ten = yield 10;
console.log(ten, 10);
var twenty = yield ten + 10;
console.log(twenty, 20);
var thirty = yield twenty + 10;
console.log(thirty, 30);
return thrity + 10;
StopIteration.value = thirty + 10;
throw StopIteration;
});

Q.when(eventuallyFourty(), function (forty) {
Q.when(generator(), function (forty) {
console.log(forty, 40);
}, function (reason) {
console.log("error", reason);
Expand Down
37 changes: 37 additions & 0 deletions examples/async-generators/js-1.7/2-error-propagation.html
@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<!--
Works in Firefox, or where generators are implemented.
-->
<script src="../../../q.js"></script>
<script type="application/javascript;version=1.7">

function test() {

var generator = Q.async(function () {
try {
var ten = yield Q.reject('Rejected!');
console.log("Should not get here 1");
} catch (exception) {
console.log("Should get here 1");
console.log(exception, "should be", "Rejected!");
throw "Threw!";
}
});

Q.when(generator(), function () {
console.log("Should not get here 2");
}, function (reason) {
console.log("Should get here 2");
console.log(reason, "should be", "Threw!");
});

}

</script>
</head>
<body onload="test()">
<div id="box" style="width: 20px; height: 20px; background-color: red;"></div>
</body>
</html>
Expand Up @@ -4,7 +4,7 @@
<!--
Works in Firefox, or where generators are implemented.
-->
<script src="../../q.js"></script>
<script src="../../../q.js"></script>
<script type="application/javascript;version=1.7">

function test() {
Expand Down
57 changes: 57 additions & 0 deletions examples/async-generators/js-1.7/README.md
@@ -0,0 +1,57 @@

/!\ Warning: The behavior described here corresponds to a dead-end fork
of JavaScript used in FireFox. If you can, use ES6-style generators,
described [in the parent directory](../). Currently Q works with both
kinds of generators, but perhaps in a year or so, support for the older
SpiderMonkey generators will go away.

Examples of SpiderMonkey-style generators:

function count() {
var i = 0;
while (true) {
yield i++;
}
}

var counter = count();
count.next() === 0;
count.next() === 1;
count.next() === 2;

In this case it's just like ES6 generators, but with ``function``
instead of ``function*``. Like ES6 generators, ``yield`` can also
return a value, if the ``send`` method of a generator is used instead of
``next``:

var buffer = (function () {
var x;
while (true) {
x = yield x;
}
}());

buffer.send(1) === undefined
buffer.send("a") === 1
buffer.send(2) === "a"
buffer.next() === 2
buffer.next() === undefined
buffer.next() === undefined

We can use ``yield`` to wait for a promise to resolve.

var eventualAdd = Q.async(function (oneP, twoP) {
var one = yield oneP;
var two = yield twoP;
Q.return(one + two);
});

eventualAdd(eventualOne, eventualTwo)
.then(function (three) {
three === 3;
});

Note! SpiderMonkey does not allow return values in generators. To work
around that, call the ``Q.return`` function, as used above, instead of
using a ``return`` statement. ``Q.return`` will go away at some point
when SpiderMonkey switches to ES6 style.

0 comments on commit d69254e

Please sign in to comment.