Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Update Q.async to work with current ES6 generators #288

Merged
merged 1 commit into from

4 participants

@andywingo

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: , done: } 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.

@andywingo andywingo Update Q.async to work with current ES6 generators
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.
d69254e
@andywingo

I tested this with FireFox and the js-1.7 directory. For ES6 I tested with a fresh Chrome build, hacked to use bleeding_edge v8, run with --harmony.

@domenic
Collaborator

This is one of the most exciting things I've seen :D. Amazing work sir! Will let @kriskowal do the honors of merging, but I can't wait to start using these.

@kriskowal
Owner

I actually am wondering whether I might beg a code review from @erights. Is this consistent with the direction of the language? Is this mature?

@erights
@kriskowal
Owner

I’m inclined to land this before a thorough review and deal with the consequences as they come. I expect that there will be wonky issues with CSP and the eval to feature test. Not necessarily that it will fail generally, but that it will fail when it should pass. I respect the decision to completely separate and replicate the inner algorithm for Q.async so it’s easier to delete the old path. It will be worth noting that we will not remove the shim when Firefox upgrades its generators but when no one is using the old generators on Firefox, presumably a much later date, unless we force forward and leave such users stranded in a backward version of Q (a good option too).

@erights
@andywingo

It almost goes without stating, but not quite, so just to mention I'm happy to make any changes you suggest. I'm not really familiar with Q and am actually quite a bad JS programmer ;) so it's likely I didn't do things quite right.

Will take a look in the euro-morning. Cheers.

@kriskowal
Owner

I don’t see anything that we can change, unless there’s a way to feature-test for Harmony generators without using eval. I will land this as-is.

@kriskowal kriskowal merged commit d69254e into kriskowal:master
@kriskowal
Owner

@domenic This is prepped for release. Let me know if you want anything else in v0.9.4.

@domenic
Collaborator

@kriskowal if you could look at #238 and maybe #278, a lot of people are getting hurt by those.

@andywingo andywingo deleted the andywingo:harmony-generators branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 8, 2013
  1. @andywingo

    Update Q.async to work with current ES6 generators

    andywingo authored
    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.
This page is out of date. Refresh to see the latest.
View
9 examples/async-generators/0.html
@@ -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();
@@ -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);
View
10 examples/async-generators/1-return.html
@@ -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) {
View
7 examples/async-generators/2-error-propagation.html
@@ -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");
View
35 examples/async-generators/README.md
@@ -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++;
@@ -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;
@@ -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;
@@ -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.
View
43 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>
View
12 examples/async-generators/3-wishful-thinking.html → examples/async-generators/js-1.7/1-return.html
@@ -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);
View
37 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>
View
2  examples/async-generators/4-shim.html → examples/async-generators/js-1.7/3-shim.html
@@ -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() {
View
57 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.
View
87 q.js
@@ -266,6 +266,7 @@ function isObject(value) {
// generator related shims
+// FIXME: Remove this function once ES6 generators are in SpiderMonkey.
function isStopIteration(exception) {
return (
object_toString(exception) === "[object StopIteration]" ||
@@ -273,6 +274,8 @@ function isStopIteration(exception) {
);
}
+// FIXME: Remove this helper and Q.return once ES6 generators are in
+// SpiderMonkey.
var QReturnValue;
if (typeof ReturnValue !== "undefined") {
QReturnValue = ReturnValue;
@@ -282,6 +285,21 @@ if (typeof ReturnValue !== "undefined") {
};
}
+// Until V8 3.19 / Chromium 29 is released, SpiderMonkey is the only
+// engine that has a deployed base of browsers that support generators.
+// However, SM's generators use the Python-inspired semantics of
+// outdated ES6 drafts. We would like to support ES6, but we'd also
+// like to make it possible to use generators in deployed browsers, so
+// we also support Python-style generators. At some point we can remove
+// this block.
+var hasES6Generators;
+try {
+ eval("(function* (){ yield 1; })");
+ hasES6Generators = true;
+} catch (e) {
+ hasES6Generators = false;
+}
+
// long stack traces
Q.longStackJumpLimit = 1;
@@ -995,10 +1013,15 @@ function spread(promise, fulfilled, rejected) {
/**
* The async function is a decorator for generator functions, turning
- * them into asynchronous generators. This presently only works in
- * Firefox/Spidermonkey, however, this code does not cause syntax
- * errors in older engines. This code should continue to work and
- * will in fact improve over time as the language improves.
+ * them into asynchronous generators. Although generators are only part
+ * of the newest ECMAScript 6 drafts, this code does not cause syntax
+ * errors in older engines. This code should continue to work and will
+ * in fact improve over time as the language improves.
+ *
+ * ES6 generators are currently part of V8 version 3.19 with the
+ * --harmony-generators runtime flag enabled. SpiderMonkey has had them
+ * for longer, but under an older Python-inspired form. This function
+ * works on both kinds of generators.
*
* Decorates a generator function such that:
* - it may yield promises
@@ -1013,18 +1036,6 @@ function spread(promise, fulfilled, rejected) {
* every following yield until it is caught, or until it escapes
* the generator function altogether, and is translated into a
* rejection for the promise returned by the decorated generator.
- * - in present implementations of generators, when a generator
- * function is complete, it throws ``StopIteration``, ``return`` is
- * a syntax error in the presence of ``yield``, so there is no
- * observable return value. There is a proposal[1] to add support
- * for ``return``, which would permit the value to be carried by a
- * ``StopIteration`` instance, in which case it would fulfill the
- * promise returned by the asynchronous generator. This can be
- * emulated today by throwing StopIteration explicitly with a value
- * property.
- *
- * [1]: http://wiki.ecmascript.org/doku.php?id=strawman:async_functions#reference_implementation
- *
*/
Q.async = async;
function async(makeGenerator) {
@@ -1033,16 +1044,30 @@ function async(makeGenerator) {
// when verb is "throw", arg is an exception
function continuer(verb, arg) {
var result;
- try {
- result = generator[verb](arg);
- } catch (exception) {
- if (isStopIteration(exception)) {
- return exception.value;
- } else {
+ if (hasES6Generators) {
+ try {
+ result = generator[verb](arg);
+ } catch (exception) {
return reject(exception);
}
+ if (result.done) {
+ return result.value;
+ } else {
+ return when(result.value, callback, errback);
+ }
+ } else {
+ // FIXME: Remove this case when SM does ES6 generators.
+ try {
+ result = generator[verb](arg);
+ } catch (exception) {
+ if (isStopIteration(exception)) {
+ return exception.value;
+ } else {
+ return reject(exception);
+ }
+ }
+ return when(result, callback, errback);
}
- return when(result, callback, errback);
}
var generator = makeGenerator.apply(this, arguments);
var callback = continuer.bind(continuer, "send");
@@ -1051,13 +1076,25 @@ function async(makeGenerator) {
};
}
+// FIXME: Remove this interface once ES6 generators are in SpiderMonkey.
/**
* Throws a ReturnValue exception to stop an asynchronous generator.
- * Only useful presently in Firefox/SpiderMonkey since generators are
- * implemented.
+ *
+ * This interface is a stop-gap measure to support generator return
+ * values in older Firefox/SpiderMonkey. In browsers that support ES6
+ * generators like Chromium 29, just use "return" in your generator
+ * functions.
+ *
* @param value the return value for the surrounding generator
* @throws ReturnValue exception with the value.
* @example
+ * // ES6 style
+ * Q.async(function* () {
+ * var foo = yield getFooPromise();
+ * var bar = yield getBarPromise();
+ * return foo + bar;
+ * })
+ * // Older SpiderMonkey style
* Q.async(function () {
* var foo = yield getFooPromise();
* var bar = yield getBarPromise();
Something went wrong with that request. Please try again.