Ben Newman
(Facebook)
JSConf 2014
{ github, twitter, instagram, facebook }.com/benjamn
Why isn't every Python project using Python 3 yet?
Crazy idea: ease into the new language by simulating its most useful features in the current version of JavaScript (ECMAScript 5).
=>
function syntax[3, 1, 10, 28].sort((a, b) => a - b)
var recast = require("recast"); var types = recast.types; var traverse = types.traverse; var n = types.namedTypes; var b = types.builders; var ast = recast.parse( "[3, 1, 10, 28].sort((a, b) => a - b)" );
traverse(ast, function(node) { });
traverse(ast, function(node) { if (n.ArrowFunctionExpression.check(node)) { } });
traverse(ast, function(node) { if (n.ArrowFunctionExpression.check(node)) { var body = node.body; if (node.expression) { node.expression = false; body = b.blockStatement([b.returnStatement(body)]); } } });
traverse(ast, function(node) { if (n.ArrowFunctionExpression.check(node)) { var body = node.body; if (node.expression) { node.expression = false; body = b.blockStatement([b.returnStatement(body)]); } var funExp = b.functionExpression( node.id, node.params, body, node.generator, node.expression ); } });
traverse(ast, function(node) { if (n.ArrowFunctionExpression.check(node)) { var body = node.body; if (node.expression) { node.expression = false; body = b.blockStatement([b.returnStatement(body)]); } var funExp = b.functionExpression( node.id, node.params, body, node.generator, node.expression ); var bindExp = b.callExpression( b.memberExpression(funExp, b.identifier("bind"), false), [b.thisExpression()] ); } });
traverse(ast, function(node) { if (n.ArrowFunctionExpression.check(node)) { var body = node.body; if (node.expression) { node.expression = false; body = b.blockStatement([b.returnStatement(body)]); } var funExp = b.functionExpression( node.id, node.params, body, node.generator, node.expression ); var bindExp = b.callExpression( b.memberExpression(funExp, b.identifier("bind"), false), [b.thisExpression()] ); this.replace(bindExp); } });
console.log(recast.print(ast).code); // Which prints: [3, 1, 10, 28].sort(function(a, b) { return a - b; }.bind(this))
If you already have a build step for static resources, you can be cooking with arrow functions in a matter of minutes!
var recast = require("recast"); var ast = recast.parse(source); transform(ast); // Anything goes. console.log(recast.print(ast).code);
find ~/www/html/js/lib | \ grep "\.js$" | \ time parallel ~/www/scripts/bin/classify --update 228.03s user 12.25s system 1229% cpu 19.548 total
function *permutations(list) { if (list.length < 2) { yield list; } else { var first = list.slice(0, 1); var ps = permutations(list.slice(1)); for (var p of ps) { for (var i = 0; i < list.length; ++i) { yield Array.prototype.concat.call( p.slice(0, i), // prefix first, // middle p.slice(i) // suffix ); } } } }
var g = permutations([1, 3, 2]); g.next().value; // [1, 3, 2] g.next().value; // [3, 1, 2] g.next().value; // [3, 2, 1] g.next().value; // [1, 2, 3] g.next().value; // [2, 1, 3] g.next().value; // [2, 3, 1] g.next().done; // true
function*
or yield
?
function *permutations(list) { if (list.length < 2) { yield list; } else { var first = list.slice(0, 1); var ps = permutations(list.slice(1)); for (var p of ps) { for (var i = 0; i < list.length; ++i) { yield Array.prototype.concat.call( p.slice(0, i), // prefix first, // middle p.slice(i) // suffix ); } } } }
var g = permutations([1, 3, 2]); var count = 0; for (var p of g) { console.log(p); ++count; } console.log(count); // 6
function*
or yield
?
I've been jokingly referring to this side project as "my life's
work" for so long that I'm terrified it might
actually be finished soon.
— Ben Newman (@benjamn)
May 8, 2013
function permutations(list) { if (list.length < 2) { yield list; } else { var first = list.slice(0, 1); var ps = permutations(list.slice(1)); for (var p of ps) { for (var i = 0; i < list.length; ++i) { yield Array.prototype.concat.call( p.slice(0, i), // prefix first, // middle p.slice(i) // suffix ); } } } }
function permutations(list) { var first, ps, p, i; if (list.length < 2) { yield list; } else { first = list.slice(0, 1); ps = permutations(list.slice(1)); for (p of ps) { for (i = 0; i < list.length; ++i) { yield Array.prototype.concat.call( p.slice(0, i), // prefix first, // middle p.slice(i) // suffix ); } } } }
function permutations(list) { var first, ps, p, t$0, t$1, i; if (list.length < 2) { yield list; } else { first = list.slice(0, 1); ps = permutations(list.slice(1)); for (p of ps) { for (i = 0; i < list.length; ++i) { yield Array.prototype.concat.call( p.slice(0, i), // prefix first, // middle p.slice(i) // suffix ); } } } }
function permutations(list) { var first, ps, p, t$0, t$1, i; if (list.length < 2) { yield list; } else { first = list.slice(0, 1); ps = permutations(list.slice(1)); t$0 = wrapGenerator.values(ps); while (!(t$1 = t$0.next()).done) { p = t$1.value; for (i = 0; i < list.length; ++i) { yield Array.prototype.concat.call( p.slice(0, i), // prefix first, // middle p.slice(i) // suffix ); } } } }
function permutations(list) { var first, ps, p, t$0, t$1, i; return function*() { if (list.length < 2) { yield list; } else { first = list.slice(0, 1); ps = permutations(list.slice(1)); t$0 = wrapGenerator.values(ps); while (!(t$1 = t$0.next()).done) { p = t$1.value; for (i = 0; i < list.length; ++i) { yield Array.prototype.concat.call( p.slice(0, i), // prefix first, // middle p.slice(i) // suffix ); } } } }; }
function permutations(list) { var first, ps, p, t$0, t$1, i; return function(context) { while (1) switch (context.next) {
} }; }
function permutations(list) { var first, ps, p, t$0, t$1, i; return function(context) { while (1) switch (context.next) { case 0: if (!(list.length < 2)) { context.next = 5; break; }
} }; }
function permutations(list) { var first, ps, p, t$0, t$1, i; return function(context) { while (1) switch (context.next) { case 0: if (!(list.length < 2)) { context.next = 5; break; } context.next = 3; return list;
} }; }
function permutations(list) { var first, ps, p, t$0, t$1, i; return function(context) { while (1) switch (context.next) { case 0: if (!(list.length < 2)) { context.next = 5; break; } context.next = 3; return list; case 3: context.next = 19; break;
} }; }
function permutations(list) { var first, ps, p, t$0, t$1, i; return function(context) { while (1) switch (context.next) { case 0: if (!(list.length < 2)) { context.next = 5; break; } context.next = 3; return list; case 3: context.next = 19; break;
case 19: case "end": return context.stop(); } }; }
function permutations(list) { var first, ps, p, t$0, t$1, i; return function(context) { while (1) switch (context.next) { case 0: if (!(list.length < 2)) { context.next = 5; break; } context.next = 3; return list; case 3: context.next = 19; break; case 5: first = list.slice(0, 1); ps = permutations(list.slice(1)); t$0 = wrapGenerator.values(ps);
case 19: case "end": return context.stop(); } }; }
function permutations(list) { var first, ps, p, t$0, t$1, i; return function(context) { while (1) switch (context.next) { case 0: if (!(list.length < 2)) { context.next = 5; break; } context.next = 3; return list; case 3: context.next = 19; break; case 5: first = list.slice(0, 1); ps = permutations(list.slice(1)); t$0 = wrapGenerator.values(ps); case 8: if ((t$1 = t$0.next()).done) { context.next = 19; break; } p = t$1.value;
case 19: case "end": return context.stop(); } }; }
function permutations(list) { var first, ps, p, t$0, t$1, i; return function(context) { while (1) switch (context.next) { case 0: if (!(list.length < 2)) { context.next = 5; break; } context.next = 3; return list; case 3: context.next = 19; break; case 5: first = list.slice(0, 1); ps = permutations(list.slice(1)); t$0 = wrapGenerator.values(ps); case 8: if ((t$1 = t$0.next()).done) { context.next = 19; break; } p = t$1.value;
i = 0; case 11: if (!(i < list.length)) { context.next = 17; break; } case 19: case "end": return context.stop(); } }; }
function permutations(list) { var first, ps, p, t$0, t$1, i; return function(context) { while (1) switch (context.next) { case 0: if (!(list.length < 2)) { context.next = 5; break; } context.next = 3; return list; case 3: context.next = 19; break; case 5: first = list.slice(0, 1); ps = permutations(list.slice(1)); t$0 = wrapGenerator.values(ps); case 8: if ((t$1 = t$0.next()).done) { context.next = 19; break; } p = t$1.value;
i = 0; case 11: if (!(i < list.length)) { context.next = 17; break; } context.next = 14; return Array.prototype.concat.call( p.slice(0, i), // prefix first, // middle p.slice(i) // suffix ); case 19: case "end": return context.stop(); } }; }
function permutations(list) { var first, ps, p, t$0, t$1, i; return function(context) { while (1) switch (context.next) { case 0: if (!(list.length < 2)) { context.next = 5; break; } context.next = 3; return list; case 3: context.next = 19; break; case 5: first = list.slice(0, 1); ps = permutations(list.slice(1)); t$0 = wrapGenerator.values(ps); case 8: if ((t$1 = t$0.next()).done) { context.next = 19; break; } p = t$1.value;
i = 0; case 11: if (!(i < list.length)) { context.next = 17; break; } context.next = 14; return Array.prototype.concat.call( p.slice(0, i), // prefix first, // middle p.slice(i) // suffix ); case 14: ++i; context.next = 11; break; case 19: case "end": return context.stop(); } }; }
function permutations(list) { var first, ps, p, t$0, t$1, i; return function(context) { while (1) switch (context.next) { case 0: if (!(list.length < 2)) { context.next = 5; break; } context.next = 3; return list; case 3: context.next = 19; break; case 5: first = list.slice(0, 1); ps = permutations(list.slice(1)); t$0 = wrapGenerator.values(ps); case 8: if ((t$1 = t$0.next()).done) { context.next = 19; break; } p = t$1.value;
i = 0; case 11: if (!(i < list.length)) { context.next = 17; break; } context.next = 14; return Array.prototype.concat.call( p.slice(0, i), // prefix first, // middle p.slice(i) // suffix ); case 14: ++i; context.next = 11; break; case 17: context.next = 8; break; case 19: case "end": return context.stop(); } }; }
function permutations(list) { var first, ps, p, t$0, t$1, i; return wrapGenerator(function(context) { while (1) switch (context.next) { case 0: if (!(list.length < 2)) { context.next = 5; break; } context.next = 3; return list; case 3: context.next = 19; break; case 5: first = list.slice(0, 1); ps = permutations(list.slice(1)); t$0 = wrapGenerator.values(ps); case 8: if ((t$1 = t$0.next()).done) { context.next = 19; break; } p = t$1.value;
i = 0; case 11: if (!(i < list.length)) { context.next = 17; break; } context.next = 14; return Array.prototype.concat.call( p.slice(0, i), // prefix first, // middle p.slice(i) // suffix ); case 14: ++i; context.next = 11; break; case 17: context.next = 8; break; case 19: case "end": return context.stop(); } }, permutations, this); }
ForStatement
case "ForStatement":
case "ForStatement": var head = loc(); var update = loc(); var after = loc();
case "ForStatement": var head = loc(); var update = loc(); var after = loc(); if (stmt.init) { self.explode(path.get("init"), true); }
case "ForStatement": var head = loc(); var update = loc(); var after = loc(); if (stmt.init) { self.explode(path.get("init"), true); } self.mark(head);
case "ForStatement": var head = loc(); var update = loc(); var after = loc(); if (stmt.init) { self.explode(path.get("init"), true); } self.mark(head); if (stmt.test) { self.jumpIfNot(self.explodeExpression(path.get("test")), after); }
case "ForStatement": var head = loc(); var update = loc(); var after = loc(); if (stmt.init) { self.explode(path.get("init"), true); } self.mark(head); if (stmt.test) { self.jumpIfNot(self.explodeExpression(path.get("test")), after); } self.leapManager.withEntry( new leap.LoopEntry(after, update, labelId), function() { self.explodeStatement(path.get("body")); } );
case "ForStatement": var head = loc(); var update = loc(); var after = loc(); if (stmt.init) { self.explode(path.get("init"), true); } self.mark(head); if (stmt.test) { self.jumpIfNot(self.explodeExpression(path.get("test")), after); } self.leapManager.withEntry( new leap.LoopEntry(after, update, labelId), function() { self.explodeStatement(path.get("body")); } ); self.mark(update);
case "ForStatement": var head = loc(); var update = loc(); var after = loc(); if (stmt.init) { self.explode(path.get("init"), true); } self.mark(head); if (stmt.test) { self.jumpIfNot(self.explodeExpression(path.get("test")), after); } self.leapManager.withEntry( new leap.LoopEntry(after, update, labelId), function() { self.explodeStatement(path.get("body")); } ); self.mark(update); if (stmt.update) { self.explode(path.get("update"), true); }
case "ForStatement": var head = loc(); var update = loc(); var after = loc(); if (stmt.init) { self.explode(path.get("init"), true); } self.mark(head); if (stmt.test) { self.jumpIfNot(self.explodeExpression(path.get("test")), after); } self.leapManager.withEntry( new leap.LoopEntry(after, update, labelId), function() { self.explodeStatement(path.get("body")); } ); self.mark(update); if (stmt.update) { self.explode(path.get("update"), true); } self.jump(head);
case "ForStatement": var head = loc(); var update = loc(); var after = loc(); if (stmt.init) { self.explode(path.get("init"), true); } self.mark(head); if (stmt.test) { self.jumpIfNot(self.explodeExpression(path.get("test")), after); } self.leapManager.withEntry( new leap.LoopEntry(after, update, labelId), function() { self.explodeStatement(path.get("body")); } ); self.mark(update); if (stmt.update) { self.explode(path.get("update"), true); } self.jump(head); self.mark(after);
case "ForStatement": var head = loc(); var update = loc(); var after = loc(); if (stmt.init) { self.explode(path.get("init"), true); } self.mark(head); if (stmt.test) { self.jumpIfNot(self.explodeExpression(path.get("test")), after); } self.leapManager.withEntry( new leap.LoopEntry(after, update, labelId), function() { self.explodeStatement(path.get("body")); } ); self.mark(update); if (stmt.update) { self.explode(path.get("update"), true); } self.jump(head); self.mark(after); break;
loc()
and this.mark(loc)
loc()
and this.mark(loc)
// Offsets into this.listing that could be used as targets for branches or // jumps are represented as numeric Literal nodes. function loc() { return { type: "Literal", value: -1 }; }
loc()
and this.mark(loc)
// Offsets into this.listing that could be used as targets for branches or // jumps are represented as numeric Literal nodes. function loc() { return require("recast").types.builders.literal(-1); }
loc()
and this.mark(loc)
// Offsets into this.listing that could be used as targets for branches or // jumps are represented as numeric Literal nodes. function loc() { return b.literal(-1); }
loc()
and this.mark(loc)
// Offsets into this.listing that could be used as targets for branches or // jumps are represented as numeric Literal nodes. function loc() { return b.literal(-1); } // Sets the exact value of the given location to the offset of the next // Statement emitted. Emitter.prototype.mark = function(loc) { assert.strictEqual(loc.type, "Literal"); var index = this.listing.length; if (loc.value === -1) { loc.value = index; } else { // Locations can be marked redundantly, but their values cannot change // once set the first time. assert.strictEqual(loc.value, index); } this.marked[index] = true; return loc; };
loc()
and this.mark(loc)
// Offsets into this.listing that could be used as targets for branches or // jumps are represented as numeric Literal nodes. function loc() { return b.literal(-1); } // Sets the exact value of the given location to the offset of the next // Statement emitted. Emitter.prototype.mark = function(loc) { require("recast").types.namedTypes.Literal.assert(loc); var index = this.listing.length; if (loc.value === -1) { loc.value = index; } else { // Locations can be marked redundantly, but their values cannot change // once set the first time. assert.strictEqual(loc.value, index); } this.marked[index] = true; return loc; };
loc()
and this.mark(loc)
// Offsets into this.listing that could be used as targets for branches or // jumps are represented as numeric Literal nodes. function loc() { return b.literal(-1); } // Sets the exact value of the given location to the offset of the next // Statement emitted. Emitter.prototype.mark = function(loc) { n.Literal.assert(loc); var index = this.listing.length; if (loc.value === -1) { loc.value = index; } else { // Locations can be marked redundantly, but their values cannot change // once set the first time. assert.strictEqual(loc.value, index); } this.marked[index] = true; return loc; };
this.jumpIfNot(expr, loc)
this.jumpIfNot(expr, loc)
// Conditional jump, with the condition negated. Emitter.prototype.jumpIfNot = function(test, toLoc) { };
this.jumpIfNot(expr, loc)
// Conditional jump, with the condition negated. Emitter.prototype.jumpIfNot = function(test, toLoc) { n.Expression.assert(test); n.Literal.assert(toLoc); };
this.jumpIfNot(expr, loc)
// Conditional jump, with the condition negated. Emitter.prototype.jumpIfNot = function(test, toLoc) { n.Expression.assert(test); n.Literal.assert(toLoc); var negatedTest; if (n.UnaryExpression.check(test) && test.operator === "!") { } };
this.jumpIfNot(expr, loc)
// Conditional jump, with the condition negated. Emitter.prototype.jumpIfNot = function(test, toLoc) { n.Expression.assert(test); n.Literal.assert(toLoc); var negatedTest; if (n.UnaryExpression.check(test) && test.operator === "!") { // Avoid double negation. negatedTest = test.argument; } };
this.jumpIfNot(expr, loc)
// Conditional jump, with the condition negated. Emitter.prototype.jumpIfNot = function(test, toLoc) { n.Expression.assert(test); n.Literal.assert(toLoc); var negatedTest; if (n.UnaryExpression.check(test) && test.operator === "!") { // Avoid double negation. negatedTest = test.argument; } else { negatedTest = b.unaryExpression("!", test); } };
this.jumpIfNot(expr, loc)
// Conditional jump, with the condition negated. Emitter.prototype.jumpIfNot = function(test, toLoc) { n.Expression.assert(test); n.Literal.assert(toLoc); var negatedTest; if (n.UnaryExpression.check(test) && test.operator === "!") { // Avoid double negation. negatedTest = test.argument; } else { negatedTest = b.unaryExpression("!", test); } this.emit(b.ifStatement( negatedTest, b.blockStatement([ this.assign(this.contextProperty("next"), toLoc), b.breakStatement() ]) )); };
this.explodeStatement(path)
this.explodeStatement(path)
Emitter.prototype.explodeStatement = function(path) { };
this.explodeStatement(path)
Emitter.prototype.explodeStatement = function(path) { assert.ok(path instanceof require("recast").types.NodePath); };
this.explodeStatement(path)
Emitter.prototype.explodeStatement = function(path) { assert.ok(path instanceof require("recast").types.NodePath); var stmt = path.value; n.Statement.assert(stmt); };
this.explodeStatement(path)
Emitter.prototype.explodeStatement = function(path) { assert.ok(path instanceof require("recast").types.NodePath); var stmt = path.value; n.Statement.assert(stmt); if (n.BlockStatement.check(stmt)) { return path.get("body").each(self.explodeStatement, self); } };
this.explodeStatement(path)
Emitter.prototype.explodeStatement = function(path) { assert.ok(path instanceof require("recast").types.NodePath); var stmt = path.value; n.Statement.assert(stmt); if (n.BlockStatement.check(stmt)) { return path.get("body").each(self.explodeStatement, self); } if (!require("./meta").containsLeap(stmt)) { self.emit(stmt); return; } };
this.explodeStatement(path)
Emitter.prototype.explodeStatement = function(path) { assert.ok(path instanceof require("recast").types.NodePath); var stmt = path.value; n.Statement.assert(stmt); if (n.BlockStatement.check(stmt)) { return path.get("body").each(self.explodeStatement, self); } if (!require("./meta").containsLeap(stmt)) { self.emit(stmt); return; } switch (stmt.type) { } };
this.explodeStatement(path)
Emitter.prototype.explodeStatement = function(path) { assert.ok(path instanceof require("recast").types.NodePath); var stmt = path.value; n.Statement.assert(stmt); if (n.BlockStatement.check(stmt)) { return path.get("body").each(self.explodeStatement, self); } if (!require("./meta").containsLeap(stmt)) { self.emit(stmt); return; } switch (stmt.type) { case "ForStatement": } };
this.explodeStatement(path)
Emitter.prototype.explodeStatement = function(path) { assert.ok(path instanceof require("recast").types.NodePath); var stmt = path.value; n.Statement.assert(stmt); if (n.BlockStatement.check(stmt)) { return path.get("body").each(self.explodeStatement, self); } if (!require("./meta").containsLeap(stmt)) { self.emit(stmt); return; } switch (stmt.type) { case "ForStatement": ... break; ... } };
this.explodeStatement(path)
Emitter.prototype.explodeStatement = function(path) { assert.ok(path instanceof require("recast").types.NodePath); var stmt = path.value; n.Statement.assert(stmt); if (n.BlockStatement.check(stmt)) { return path.get("body").each(self.explodeStatement, self); } if (!require("./meta").containsLeap(stmt)) { self.emit(stmt); return; } switch (stmt.type) { case "ForStatement": ... break; ... default: throw new Error( "unknown Statement of type " + JSON.stringify(stmt.type)); } };
function *attempt(block) { console.log("before"); try { var result = block(); } catch (thrown) { yield thrown; } console.log("after"); return result; }
function attempt(block) { var result; return wrapGenerator(function(context) { while (1) switch (context.prev = context.next) { } }, attempt, this); }
function attempt(block) { var result; return wrapGenerator(function(context) { while (1) switch (context.prev = context.next) { case 0: console.log("before"); context.prev = 1; result = block(); context.next = 9; break; } }, attempt, this); }
function attempt(block) { var result; return wrapGenerator(function(context) { while (1) switch (context.prev = context.next) { case 0: console.log("before"); context.prev = 1; result = block(); context.next = 9; break; case 5: context.prev = 5; context.t0 = context.catch(1); context.next = 9; return context.t0; } }, attempt, this); }
function attempt(block) { var result; return wrapGenerator(function(context) { while (1) switch (context.prev = context.next) { case 0: console.log("before"); context.prev = 1; result = block(); context.next = 9; break; case 5: context.prev = 5; context.t0 = context.catch(1); context.next = 9; return context.t0; case 9: console.log("after"); return context.abrupt("return", result); } }, attempt, this); }
function attempt(block) { var result; return wrapGenerator(function(context) { while (1) switch (context.prev = context.next) { case 0: console.log("before"); context.prev = 1; result = block(); context.next = 9; break; case 5: context.prev = 5; context.t0 = context.catch(1); context.next = 9; return context.t0; case 9: console.log("after"); return context.abrupt("return", result); case 11: case "end": return context.stop(); } }, attempt, this); }
function attempt(block) { var result; return wrapGenerator(function(context) { while (1) switch (context.prev = context.next) { case 0: console.log("before"); context.prev = 1; result = block(); context.next = 9; break; case 5: context.prev = 5; context.t0 = context.catch(1); context.next = 9; return context.t0; case 9: console.log("after"); return context.abrupt("return", result); case 11: case "end": return context.stop(); } }, attempt, this, [[1, 5]]); }
while (true) { }
while (true) { if (state === GenStateSuspendedYield) { context.sent = arg; } else { delete context.sent; } }
while (true) { if (state === GenStateSuspendedYield) { context.sent = arg; } else { delete context.sent; } state = GenStateExecuting; }
while (true) { if (state === GenStateSuspendedYield) { context.sent = arg; } else { delete context.sent; } state = GenStateExecuting; try { } catch (thrown) { } }
while (true) { if (state === GenStateSuspendedYield) { context.sent = arg; } else { delete context.sent; } state = GenStateExecuting; try { var value = innerFn.call(self, context); } catch (thrown) { } }
while (true) { if (state === GenStateSuspendedYield) { context.sent = arg; } else { delete context.sent; } state = GenStateExecuting; try { var value = innerFn.call(self, context); // If an exception is thrown from innerFn, we leave state === // GenStateExecuting and loop back for another invocation. state = context.done ? GenStateCompleted : GenStateSuspendedYield; } catch (thrown) { } }
while (true) { if (state === GenStateSuspendedYield) { context.sent = arg; } else { delete context.sent; } state = GenStateExecuting; try { var value = innerFn.call(self, context); // If an exception is thrown from innerFn, we leave state === // GenStateExecuting and loop back for another invocation. state = context.done ? GenStateCompleted : GenStateSuspendedYield; return { value: value, done: context.done }; } catch (thrown) { } }
while (true) { if (state === GenStateSuspendedYield) { context.sent = arg; } else { delete context.sent; } state = GenStateExecuting; try { var value = innerFn.call(self, context); // If an exception is thrown from innerFn, we leave state === // GenStateExecuting and loop back for another invocation. state = context.done ? GenStateCompleted : GenStateSuspendedYield; return { value: value, done: context.done }; } catch (thrown) { state = GenStateCompleted; context.dispatchException(thrown); } }
That's pretty much as hard as transpilation gets.
async
functionsasync function chainAnimations(elem, animations) { var ret = null; for (var i = 0; i < animations.length; ++i) ret = await animations[i](elem); return ret; } chainAnimations($("#card"), [ flip(100), slide(200), ... ]).then(function() { console.log("all done!"); });
function chainAnimations(elem, animations) { var ret, i; return wrapGenerator.async(function(context) { while (1) switch (context.next) { case 0: ret = null; i = 0; case 2: if (!(i < animations.length)) { context.next = 9; break; } context.next = 5; return animations[i](elem); case 5: ret = context.sent; case 6: ++i; context.next = 2; break; case 9: return context.abrupt("return", ret); case 10: case "end": return context.stop(); } }, null, this); }
wrapGenerator.async = function(innerFn, self, tryList) { return new Promise(function(resolve, reject) { var generator = wrapGenerator(innerFn, self, tryList); var callNext = step.bind(generator.next); var callThrow = step.bind(generator.throw); function step(arg) { try { var info = this(arg); var value = info.value; } catch (error) { return reject(error); } if (info.done) { resolve(value); } else { Promise.resolve(value).then(callNext, callThrow); } } callNext(); }); };
async
functions and the await
keyword?
Incremental transpilation is the key to avoiding the Python 3 trap.
It's how we bring about the future without choking on it.
It's how we know, sooner rather than later, how great the future will be.
It is, quite literally, linguistic time travel.
Not everything can be transpiled.
AST transforms don't always play well together.
Niceties like source maps become an absolute necessity when debugging generated code.
The language specification can change out from under you.
It's all going to be open-source.
We're only just getting started.
You are more than welcome to help.
github.com/{ benjamn/jsconf-2014, facebook/jstransform, benjamn/recast, benjamn/ast-types, facebook/regenerator, square/esnext } code.facebook.com/projects What about Traceur?