async-to-gen
Advanced tools
Comparing version 1.0.6 to 1.1.0
218
index.js
@@ -9,5 +9,8 @@ var babylon = require('babylon'); | ||
* MagicString has two important functions that can be called: .toString() and | ||
* .generateMap() which returns a source map, as well as a property .isEdited | ||
* which is true when any async functions were transformed. | ||
* .generateMap() which returns a source map, as well as these properties: | ||
* | ||
* - .isEdited: true when any functions were transformed. | ||
* - .containsAsync: true when any async functions were transformed. | ||
* - .containsAsyncGen: true when any async generator functions were transformed. | ||
* | ||
* Options: | ||
@@ -24,2 +27,4 @@ * | ||
editor.isEdited = false; | ||
editor.containsAsync = false; | ||
editor.containsAsyncGen = false; | ||
@@ -47,5 +52,5 @@ // Cheap trick for files that don't actually contain async functions | ||
if (ast.isEdited) { | ||
editor.isEdited = true; | ||
} | ||
editor.isEdited = Boolean(ast.containsAsync || ast.containsAsyncGen); | ||
editor.containsAsync = Boolean(ast.containsAsync); | ||
editor.containsAsyncGen = Boolean(ast.containsAsyncGen); | ||
@@ -85,2 +90,54 @@ return editor; | ||
/** | ||
* A helper function which accepts a generator function and returns an | ||
* Async Iterable based on invoking the generating and resolving an iteration | ||
* of Promises. | ||
* | ||
* Automatically included at the end of files containing async generator | ||
* functions, but also exported from this module for other uses. | ||
* See ./async-node for an example of another usage. | ||
*/ | ||
var asyncGenHelper = | ||
'function __asyncGen(g){' + | ||
'var q=[],' + | ||
'T=["next","throw","return"],' + | ||
'I={};' + | ||
'for(var i=0;i<3;i++){' + | ||
'I[T[i]]=a.bind(0,i)' + | ||
'}' + | ||
'Symbol&&(' + | ||
'Symbol.iterator&&(I[Symbol.iterator]=t),' + | ||
'Symbol.asyncIterator&&(I[Symbol.asyncIterator]=t));' + | ||
'function t(){' + | ||
'return this' + | ||
'}' + | ||
'function a(t,v){' + | ||
'return new Promise(function(s,j){' + | ||
'q.push([s,j,v,t]);' + | ||
'q.length===1&&c(v,t)' + | ||
'})' + | ||
'}' + | ||
'function c(v,t){' + | ||
'try{' + | ||
'var r=g[T[t|0]](v),' + | ||
'w=r.value&&r.value.__await;' + | ||
'w?' + | ||
'Promise.resolve(w).then(c,d):' + | ||
'n(r,0)' + | ||
'}catch(e){' + | ||
'n(e,1)' + | ||
'}' + | ||
'}' + | ||
'function d(e){' + | ||
'c(e,1)' + | ||
'}' + | ||
'function n(r,s){' + | ||
'q.shift()[s](r);' + | ||
'q.length&&c(q[0][2],q[0][3])' + | ||
'}' + | ||
'return I' + | ||
'}'; | ||
module.exports.asyncGenHelper = asyncGenHelper; | ||
// A collection of methods for each AST type names which contain async functions to | ||
@@ -117,4 +174,9 @@ // be transformed. | ||
leave: function (editor, node, ast) { | ||
if (ast.isEdited && ast.shouldIncludeHelper) { | ||
editor.append('\n' + asyncHelper + '\n'); | ||
if (ast.shouldIncludeHelper) { | ||
if (ast.containsAsync) { | ||
editor.append('\n' + asyncHelper + '\n'); | ||
} | ||
if (ast.containsAsyncGen) { | ||
editor.append('\n' + asyncGenHelper + '\n'); | ||
} | ||
} | ||
@@ -125,3 +187,3 @@ } | ||
enter: function (editor, node, ast) { | ||
var envRecord = ast.scope[ast.scope.length - 1]; | ||
var envRecord = currentScope(ast); | ||
if (envRecord.async) { | ||
@@ -132,4 +194,17 @@ envRecord.referencesThis = true; | ||
}, | ||
Identifier: { | ||
enter: function (editor, node, ast) { | ||
if (node.name === 'arguments') { | ||
var envRecord = currentScope(ast); | ||
if (envRecord.async) { | ||
envRecord.referencesArgs = true; | ||
} | ||
} | ||
} | ||
}, | ||
MemberExpression: { | ||
leave: leaveMemberExpression | ||
}, | ||
ForAwaitStatement: { | ||
leave: leaveForAwait | ||
} | ||
@@ -157,4 +232,8 @@ }; | ||
// unlike await, yield must not be followed by a new line | ||
if (node.loc.start.line !== node.argument.loc.start.line) { | ||
var envRecord = currentScope(ast); | ||
if (envRecord.generator) { | ||
start += '{__await:'; | ||
end += '}'; | ||
} else if (node.loc.start.line !== node.argument.loc.start.line) { | ||
// unlike await, yield must not be followed by a new line | ||
start += '('; | ||
@@ -177,3 +256,8 @@ end += ')'; | ||
if (node.async) { | ||
ast.isEdited = true; | ||
if (node.generator) { | ||
ast.containsAsyncGen = true; | ||
} else { | ||
ast.containsAsync = true; | ||
} | ||
var idx = findTokenIndex(ast.tokens, node.start); | ||
@@ -185,24 +269,12 @@ while (ast.tokens[idx].value !== 'async') { | ||
var argNames = []; | ||
var argValues = []; | ||
if (node.referencesThis) { | ||
argValues.push('this'); | ||
if (node.generator) { | ||
while (ast.tokens[idx].value !== '*') { | ||
idx++; | ||
} | ||
editor.overwrite(ast.tokens[idx].start, ast.tokens[idx + 1].start, ' '); | ||
} | ||
if (node.referencesSuper) { | ||
argNames.push('$uper'); | ||
argValues.push('p=>super[p]'); | ||
} | ||
if (node.referencesSuperEq) { | ||
argNames.push('$uperEq'); | ||
argValues.push('(p,v)=>(super[p]=v)'); | ||
} | ||
editor.insertLeft( | ||
node.body.start + 1, | ||
'return __async(function*(' + argNames.join(',') + '){' | ||
); | ||
editor.insertRight( | ||
node.body.end - 1, | ||
(node.referencesThis ? '}.call(' : '}(') + argValues.join(',') + '))' | ||
); | ||
var wrapping = createAsyncWrapping(node); | ||
editor.insertLeft(node.body.start + 1, 'return ' + wrapping[0]); | ||
editor.insertRight(node.body.end - 1, wrapping[1]); | ||
} | ||
@@ -219,17 +291,21 @@ } | ||
if (node.async) { | ||
if (node.generator) { | ||
ast.containsAsyncGen = true; | ||
} else { | ||
ast.containsAsync = true; | ||
} | ||
ast.scope.pop(); | ||
if (node.referencesThis) { | ||
ast.scope[ast.scope.length - 1].referencesThis = true; | ||
} | ||
if (node.referencesSuper) { | ||
ast.scope[ast.scope.length - 1].referencesSuper = true; | ||
} | ||
if (node.referencesSuperEq) { | ||
ast.scope[ast.scope.length - 1].referencesSuperEq = true; | ||
} | ||
ast.isEdited = true; | ||
var envRecord = currentScope(ast); | ||
envRecord.referencesThis |= node.referencesThis; | ||
envRecord.referencesArgs |= node.referencesArgs; | ||
envRecord.referencesSuper |= node.referencesSuper; | ||
envRecord.referencesSuperEq |= node.referencesSuperEq; | ||
var wrapping = createAsyncWrapping(node); | ||
editor.remove(node.start, node.start + 6); | ||
if (node.body.type === 'BlockStatement') { | ||
editor.overwrite(node.body.start, node.body.start + 1, '__async(function*(){'); | ||
editor.overwrite(node.body.end - 1, node.body.end, node.referencesThis ? '}.call(this))' : '}())'); | ||
editor.overwrite(node.body.start, node.body.start + 1, wrapping[0]); | ||
editor.overwrite(node.body.end - 1, node.body.end, wrapping[1]); | ||
} else { | ||
@@ -240,5 +316,5 @@ var idx = findTokenIndex(ast.tokens, node.body.start) - 1; | ||
} | ||
editor.insertRight(ast.tokens[idx].end, '__async(function*(){'); | ||
editor.insertRight(ast.tokens[idx].end, wrapping[0]); | ||
editor.insertLeft(node.body.start, 'return '); | ||
editor.insertRight(node.body.end, node.referencesThis ? '}.call(this))' : '}())'); | ||
editor.insertRight(node.body.end, wrapping[1]); | ||
} | ||
@@ -248,2 +324,35 @@ } | ||
function createAsyncWrapping(node) { | ||
var argNames = []; | ||
var argValues = []; | ||
if (node.referencesThis) { | ||
argValues.push('this'); | ||
} | ||
if (node.referencesArgs) { | ||
argNames.push('arguments'); | ||
argValues.push('arguments'); | ||
} | ||
if (node.type !== 'ArrowFunctionExpression') { | ||
if (node.referencesSuper) { | ||
argNames.push('$uper'); | ||
argValues.push('p=>super[p]'); | ||
} | ||
if (node.referencesSuperEq) { | ||
argNames.push('$uperEq'); | ||
argValues.push('(p,v)=>(super[p]=v)'); | ||
} | ||
} | ||
var helperName = node.generator ? '__asyncGen' : '__async'; | ||
return [ | ||
helperName + '(function*(' + argNames.join(',') + '){', | ||
(node.referencesThis ? '}.call(' : '}(') + argValues.join(',') + '))' | ||
]; | ||
} | ||
function leaveMemberExpression(editor, node, ast, stack) { | ||
@@ -254,3 +363,3 @@ // Only transform super member expressions. | ||
// Only within transformed async function scopes. | ||
var envRecord = ast.scope[ast.scope.length - 1]; | ||
var envRecord = currentScope(ast); | ||
if (!envRecord.async) return; | ||
@@ -304,2 +413,19 @@ | ||
function leaveForAwait(editor, node, ast) { | ||
var idx = findTokenIndex(ast.tokens, node.start) + 1; | ||
while (ast.tokens[idx].value !== 'await') { | ||
idx++; | ||
} | ||
editor.remove(ast.tokens[idx].start, ast.tokens[idx + 1].start); | ||
var tmpName = '$await' + (ast.scope.length - 1); | ||
editor.move(node.left.start, node.left.end, node.body.start + 1); | ||
editor.insertLeft(node.left.end, '=yield{__await:' + tmpName + '};'); | ||
editor.insertLeft(node.left.start, 'let ' + tmpName); | ||
} | ||
function currentScope(ast) { | ||
return ast.scope[ast.scope.length - 1]; | ||
} | ||
// Given the AST output of babylon parse, walk through in a depth-first order, | ||
@@ -306,0 +432,0 @@ // calling methods on the given visitor, providing editor as the first argument. |
{ | ||
"name": "async-to-gen", | ||
"version": "1.0.6", | ||
"version": "1.1.0", | ||
"description": "Transform async functions to generator functions with speed and simplicity.", | ||
@@ -21,3 +21,3 @@ "author": "Lee Byron <lee@leebyron.com> (http://leebyron.com/)", | ||
"scripts": { | ||
"test": "DIFF=$(./async-to-gen test/source.js | diff test/expected.js -); if [ -n \"$DIFF\" ]; then echo \"$DIFF\"; exit 1; fi; RES=$(node -e 'require(\"./register\");require(\"./test/test-node-module.js\")'); if [ \"$RES\" != 42 ]; then echo 'Node register hook failed'; exit 1; fi; ASYNC_NODE=$(./async-node ./test/test-node-module.js); if [ \"$ASYNC_NODE\" != 42 ]; then echo 'async-node failed'; exit 1; fi;", | ||
"test": "DIFF=$(./async-to-gen test/source.js | diff test/expected.js -); if [ -n \"$DIFF\" ]; then echo \"$DIFF\"; exit 1; fi; RES=$(node -e 'require(\"./register\");require(\"./test/test-node-module.js\")'); if [ \"$RES\" != 42 ]; then echo 'Node register hook failed'; exit 1; fi; ASYNC_NODE=$(./async-node ./test/test-node-module.js); if [ \"$ASYNC_NODE\" != 42 ]; then echo 'async-node failed'; exit 1; fi; ASYNC_GEN_NODE=$(./async-node ./test/test-async-generator.js); if [ \"$ASYNC_GEN_NODE\" != 42 ]; then echo 'async-node failed for async generators'; exit 1; fi;", | ||
"test-update": "./async-to-gen test/source.js > test/expected.js" | ||
@@ -24,0 +24,0 @@ }, |
@@ -19,3 +19,7 @@ async-to-gen | ||
Also, `async-to-gen` provides support for [async generators](https://github.com/tc39/proposal-async-iteration) | ||
which return Async Iterators, a great syntax and primitive for producing and | ||
operating on streams of data. | ||
## Get Started! | ||
@@ -183,3 +187,46 @@ | ||
#### Streams | ||
Streaming data can be a challenging thing to get right. While [Observables](http://reactivex.io/documentation/observable.html) have provided a great library for | ||
streamed data, Async Generators provides language-level support for this concept! | ||
Consider subscribing to a web socket within an program using async functions: | ||
```js | ||
async function* stockTickerInEuro(symbol) { | ||
var socket = await openWebSocket('ws://stocks.com/' + symbol) | ||
try { | ||
for await (var usd of socket) { | ||
var euro = usd * await loadExchangeRateUSD2EUR() | ||
yield euro | ||
} | ||
} finally { | ||
closeWebSocket(socket) | ||
} | ||
} | ||
``` | ||
Then calling this function produces an Async Iterator (an Iterator of Promises) | ||
of stock ticker values. | ||
```js | ||
var ticker = stockTickerInEuro('AAPL') | ||
ticker.next().then(step => console.log(step.value)) | ||
``` | ||
Or use `for-await` loops within another async function: | ||
```js | ||
async function bloombergTerminal() { | ||
for await (var price of stockTickerInEuro('AAPL')) { | ||
console.log(price) | ||
} | ||
} | ||
``` | ||
NOTE: The behavior of `for-await` loops using this tool is not identical to the | ||
proposed spec addition. Where the proposed spec's `for-await` expects a | ||
`Symbol.asyncIterator` method for the iterated source, this tool expects | ||
`Symbol.iterator` instead since it transforms it to a `for-of` loop. | ||
## Dead-Simple Transforms | ||
@@ -191,4 +238,4 @@ | ||
It also includes a very small (217 character) conversion function at the bottom | ||
of the file. | ||
It also includes a very small conversion function at the bottom of the file. | ||
How small? 217 chars for async functions and 533 chars for async generators. | ||
@@ -195,0 +242,0 @@ **Before:** |
@@ -13,2 +13,12 @@ "use strict" | ||
// async gen function statement | ||
function foo() {return __asyncGen(function*(){ | ||
yield yield{__await: x} | ||
}())} | ||
// async gen function expression | ||
var bar = function () {return __asyncGen(function*(){ | ||
yield{__await: (yield x)} | ||
}())} | ||
// async arrow functions with body | ||
@@ -27,2 +37,6 @@ var arrow1 = () => __async(function*(){ | ||
yield this.x | ||
}.call(this))}, | ||
bazGen() {return __asyncGen(function*(){ | ||
yield yield{__await: this.x} | ||
}.call(this))} | ||
@@ -36,2 +50,6 @@ } | ||
}.call(this))} | ||
woofGen() {return __asyncGen(function*(){ | ||
yield{__await: (yield this.x)}; | ||
}.call(this))} | ||
} | ||
@@ -44,2 +62,6 @@ | ||
}.call(this))} | ||
static woofGen() {return __asyncGen(function*(){ | ||
yield yield{__await: this.x}; | ||
}.call(this))} | ||
} | ||
@@ -98,2 +120,18 @@ | ||
// normal function referencing arguments | ||
function normalThis() { | ||
return arguments; | ||
} | ||
// async function referencing arguments | ||
function asyncThis() {return __async(function*(arguments){ | ||
return arguments; | ||
}(arguments))} | ||
// async arrow function referencing arguments | ||
function within1() {return __async(function*(arguments){ | ||
() => () =>__async(function*(arguments){ return arguments}(arguments)) | ||
}(arguments))} | ||
class SuperDuper extends BaseClass { | ||
@@ -169,2 +207,17 @@ constructor(arg) { | ||
// await gen on its own line | ||
function ownLineGen() {return __asyncGen(function*(){ | ||
yield{__await: | ||
someThing}; | ||
}())} | ||
// for await | ||
function mapStream(stream, mapper) {return __asyncGen(function*(){ | ||
for (let $await1 of stream) {let item=yield{__await:$await1}; | ||
yield yield{__await: mapper(item)}; | ||
} | ||
}())} | ||
function __async(g){return new Promise(function(s,j){function c(a,x){try{var r=g[x?"throw":"next"](a)}catch(e){return j(e)}return r.done?s(r.value):Promise.resolve(r.value).then(c,d)}function d(e){return c(e,1)}c()})} | ||
function __asyncGen(g){var q=[],T=["next","throw","return"],I={};for(var i=0;i<3;i++){I[T[i]]=a.bind(0,i)}Symbol&&(Symbol.iterator&&(I[Symbol.iterator]=t),Symbol.asyncIterator&&(I[Symbol.asyncIterator]=t));function t(){return this}function a(t,v){return new Promise(function(s,j){q.push([s,j,v,t]);q.length===1&&c(v,t)})}function c(v,t){try{var r=g[T[t|0]](v),w=r.value&&r.value.__await;w?Promise.resolve(w).then(c,d):n(r,0)}catch(e){n(e,1)}}function d(e){c(e,1)}function n(r,s){q.shift()[s](r);q.length&&c(q[0][2],q[0][3])}return I} |
@@ -13,2 +13,12 @@ "use strict" | ||
// async gen function statement | ||
async function* foo() { | ||
yield await x | ||
} | ||
// async gen function expression | ||
var bar = async function* () { | ||
await (yield x) | ||
} | ||
// async arrow functions with body | ||
@@ -27,2 +37,6 @@ var arrow1 = async () => { | ||
await this.x | ||
}, | ||
async* bazGen() { | ||
yield await this.x | ||
} | ||
@@ -36,2 +50,6 @@ } | ||
} | ||
async* woofGen() { | ||
await (yield this.x); | ||
} | ||
} | ||
@@ -44,2 +62,6 @@ | ||
} | ||
static async* woofGen() { | ||
yield await this.x; | ||
} | ||
} | ||
@@ -98,2 +120,18 @@ | ||
// normal function referencing arguments | ||
function normalThis() { | ||
return arguments; | ||
} | ||
// async function referencing arguments | ||
async function asyncThis() { | ||
return arguments; | ||
} | ||
// async arrow function referencing arguments | ||
async function within1() { | ||
() => async () => arguments | ||
} | ||
class SuperDuper extends BaseClass { | ||
@@ -168,1 +206,14 @@ constructor(arg) { | ||
} | ||
// await gen on its own line | ||
async function* ownLineGen() { | ||
await | ||
someThing; | ||
} | ||
// for await | ||
async function* mapStream(stream, mapper) { | ||
for await (let item of stream) { | ||
yield await mapper(item); | ||
} | ||
} |
@@ -8,3 +8,3 @@ // @flow | ||
await | ||
42 : | ||
arguments[0] : | ||
await | ||
@@ -17,2 +17,2 @@ 99 | ||
genAnswer().then(function (val) { console.log(val) }); | ||
genAnswer(42).then(function (val) { console.log(val) }); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
42954
12
830
274