Skip to content

Commit

Permalink
Allow using async generators without a Symbol polyfill (but only with…
Browse files Browse the repository at this point in the history
…in code compiled by this same library)
  • Loading branch information
rpetrich committed May 13, 2019
1 parent e087183 commit 48cc45a
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 59 deletions.
2 changes: 1 addition & 1 deletion async-to-promises.test.js
Expand Up @@ -37,7 +37,7 @@ const environments = {
},
};

const helperNames = ["_Pact", "_settle", "_isSettledPact", "_async", "_await", "_awaitIgnored", "_continue", "_continueIgnored", "_forTo", "_forValues", "_forIn", "_forOwn", "_forOf", "_forAwaitOf", "_for", "_do", "_switch", "_call", "_callIgnored", "_invoke", "_invokeIgnored", "_catch", "_finallyRethrows", "_finally", "_rethrow", "_empty", "_earlyReturn", "_catchInGenerator", "_wrapReturnedValue", "_wrapYieldedValue", "_AsyncGenerator"];
const helperNames = ["_Pact", "_settle", "_isSettledPact", "_async", "_await", "_awaitIgnored", "_continue", "_continueIgnored", "_forTo", "_forValues", "_forIn", "_forOwn", "_forOf", "_forAwaitOf", "_for", "_do", "_switch", "_call", "_callIgnored", "_invoke", "_invokeIgnored", "_catch", "_finallyRethrows", "_finally", "_rethrow", "_empty", "_earlyReturn", "_catchInGenerator", "_wrapReturnedValue", "_wrapYieldedValue", "_AsyncGenerator", "_asyncIteratorSymbol"];

const stripHelpersVisitor = {
FunctionDeclaration(path) {
Expand Down
100 changes: 67 additions & 33 deletions async-to-promises.ts
@@ -1,4 +1,4 @@
import { ArrowFunctionExpression, AwaitExpression, BlockStatement, CallExpression, ClassMethod, LabeledStatement, Node, Expression, FunctionDeclaration, Statement, Identifier, ForStatement, ForInStatement, SpreadElement, ReturnStatement, ForOfStatement, Function, FunctionExpression, MemberExpression, NumericLiteral, ThisExpression, SwitchCase, Program, VariableDeclaration, VariableDeclarator, StringLiteral, BooleanLiteral, Pattern, LVal, YieldExpression } from "babel-types";
import { ArrowFunctionExpression, AwaitExpression, BlockStatement, CallExpression, ClassMethod, File, LabeledStatement, Node, Expression, FunctionDeclaration, Statement, Identifier, ForStatement, ForInStatement, SpreadElement, ReturnStatement, ForOfStatement, Function, FunctionExpression, MemberExpression, NumericLiteral, ThisExpression, SwitchCase, Program, VariableDeclaration, VariableDeclarator, StringLiteral, BooleanLiteral, Pattern, LVal, YieldExpression } from "babel-types";
import { NodePath, Scope, Visitor } from "babel-traverse";
import { code as helperCode } from "./helpers-string";

Expand Down Expand Up @@ -176,6 +176,7 @@ declare module "babel-types" {
_originalNode?: Node;
_skip?: true;
_breakIdentifier?: Identifier;
_isHelperDefinition?: true;
}
interface Identifier {
_helperName?: string;
Expand Down Expand Up @@ -2840,6 +2841,54 @@ export default function({ types, template, traverse, transformFromAst, version }
return state.found;
}

function insertHelper(programPath: NodePath<File>, value: Node): NodePath {
const destinationPath = programPath.get("body").find((path: NodePath) => !path.node._isHelperDefinition)!;
if (destinationPath.isVariableDeclaration()) {
const before = destinationPath.get("declarations").filter((path: NodePath) => path.node._isHelperDefinition);
const after = destinationPath.get("declarations").filter((path: NodePath) => !path.node._isHelperDefinition);
if (types.isVariableDeclaration(value)) {
const declaration = value.declarations[0];
declaration._isHelperDefinition = true;
if (before.length === 0) {
const target = after[0];
target.insertBefore(declaration);
return getPreviousSibling(target)!;
} else {
const target = before[before.length-1];
target.insertAfter(declaration);
return getNextSibling(target)!;
}
} else {
value._isHelperDefinition = true;
if (before.length === 0) {
destinationPath.node._isHelperDefinition = true;
destinationPath.insertBefore(value);
return getPreviousSibling(destinationPath)!;
} else if (after.length === 0) {
destinationPath.node._isHelperDefinition = true;
destinationPath.insertAfter(value);
return getNextSibling(destinationPath)!;
} else {
const beforeNode = types.variableDeclaration(destinationPath.node.kind, before.map((path: NodePath) => path.node as VariableDeclarator));
beforeNode._isHelperDefinition = true;
const afterNode = types.variableDeclaration(destinationPath.node.kind, after.map((path: NodePath) => path.node as VariableDeclarator));
destinationPath.replaceWith(afterNode);
destinationPath.insertBefore(beforeNode);
destinationPath.insertBefore(value);
return getPreviousSibling(destinationPath)!;
}
}
} else {
if (types.isVariableDeclaration(value)) {
value.declarations[0]._isHelperDefinition = true;
} else {
value._isHelperDefinition = true;
}
destinationPath.insertBefore(value);
return getPreviousSibling(destinationPath)!;
}
}

// Emits a reference to a helper, inlining or importing it as necessary
function helperReference(state: PluginState, path: NodePath, name: string): Identifier {
const file = path.scope.hub.file;
Expand Down Expand Up @@ -2888,50 +2937,23 @@ export default function({ types, template, traverse, transformFromAst, version }
helpers = newHelpers;
}
const helper = helpers[name];
// Insert helper dependencies first
for (const dependency of helper.dependencies) {
helperReference(state, path, dependency);
}
// Insert the new node
const value = cloneNode(helper.value) as typeof helper.value;
let traversePath = file.path.get("body")[0];
if (types.isVariableDeclaration(value) && traversePath.isVariableDeclaration()) {
// TODO: Support variable declaration that references another variable declaration (this case doesn't exist yet in our helpers, but may in the future)
traversePath.unshiftContainer("declarations", value.declarations[0]);
traversePath = file.path.get("body")[0].get("declarations")[0];
} else {
file.path.unshiftContainer("body", value);
traversePath = file.path.get("body")[0];
}
const destinationPath = file.path.get("body").find((path: NodePath) => !path.node._isHelperDefinition)!;
const newPath = insertHelper(destinationPath, value);
// Rename references to other helpers due to name conflicts
traversePath.traverse({
newPath.traverse({
Identifier(path) {
const name = path.node.name;
if (Object.hasOwnProperty.call(helpers, name)) {
path.replaceWith(file.declarations[name]);
}
}
} as Visitor);
// Rewrite IIFE to a series of statements that assign
if (traversePath.isVariableDeclaration()) {
const init = traversePath.get("declarations")[0].get("init");
if (init.isCallExpression() && init.get("arguments").length === 0) {
const callee = init.get("callee");
if (callee.isFunctionExpression() && callee.node.params.length === 0) {
const body = callee.get("body").get("body");
const last = body[body.length - 1];
if (last.isReturnStatement() && last.node.argument !== null) {
for (const bodyPath of body.slice(0, body.length - 1)) {
traversePath.insertBefore(bodyPath.node);
}
const argument = last.get("argument");
if (argument.isIdentifier() && argument.node._helperName === name) {
traversePath.remove();
} else {
init.replaceWith(argument.node);
}
}
}
}
}
}
}
return result;
Expand Down Expand Up @@ -3267,6 +3289,18 @@ export default function({ types, template, traverse, transformFromAst, version }
}
}

// Get previous sibling
function getPreviousSibling(targetPath: NodePath): NodePath | undefined {
const siblings = targetPath.getAllPrevSiblings();
return siblings.length !== 0 ? siblings[siblings.length-1] : undefined;
}

// Get next sibling
function getNextSibling(targetPath: NodePath): NodePath | undefined {
const siblings = targetPath.getAllNextSiblings();
return siblings.length !== 0 ? siblings[0] : undefined;
}

// Rewrite function arguments with default values to be check statements inserted into the body
function rewriteDefaultArguments(targetPath: NodePath<FunctionExpression> | NodePath<ClassMethod>, pluginState: PluginState) {
const statements: Statement[] = [];
Expand Down
50 changes: 25 additions & 25 deletions helpers.js
Expand Up @@ -214,7 +214,7 @@ export function _forOf(target, body, check) {
}
// No support for Symbol.iterator
if (!("length" in target)) {
throw new TypeError("value is not iterable");
throw new TypeError("Object is not iterable");
}
// Handle live collections properly
var values = [];
Expand All @@ -224,32 +224,31 @@ export function _forOf(target, body, check) {
return _forTo(values, function(i) { return body(values[i]); }, check);
}

export const _asyncIteratorSymbol = typeof Symbol !== "undefined" ? (Symbol.asyncIterator || (Symbol.asyncIterator = Symbol("Symbol.asyncIterator"))) : "Symbol.asyncIterator";

// Asynchronously iterate on a value using it's async iterator if present, or its synchronous iterator if missing
export function _forAwaitOf(target, body, check) {
if (typeof Symbol !== "undefined") {
var asyncIteratorSymbol = Symbol.asyncIterator;
if (asyncIteratorSymbol && (asyncIteratorSymbol in target)) {
var pact = new _Pact();
var iterator = target[asyncIteratorSymbol]();
iterator.next().then(_resumeAfterNext).then(void 0, _reject);
return pact;
function _resumeAfterBody(result) {
if (check && !check()) {
return _settle(pact, 1, iterator.return ? iterator.return().then(function() { return result; }) : result);
}
iterator.next().then(_resumeAfterNext).then(void 0, _reject);
}
function _resumeAfterNext(step) {
if (step.done) {
_settle(pact, 1);
} else {
Promise.resolve(body(step.value)).then(_resumeAfterBody).then(void 0, _reject);
}
if (typeof target[_asyncIteratorSymbol] === "function") {
var pact = new _Pact();
var iterator = target[asyncIteratorSymbol]();
iterator.next().then(_resumeAfterNext).then(void 0, _reject);
return pact;
function _resumeAfterBody(result) {
if (check && !check()) {
return _settle(pact, 1, iterator.return ? iterator.return().then(function() { return result; }) : result);
}
function _reject(error) {
_settle(pact, 2, iterator.return ? iterator.return().then(function() { return error; }) : error);
iterator.next().then(_resumeAfterNext).then(void 0, _reject);
}
function _resumeAfterNext(step) {
if (step.done) {
_settle(pact, 1);
} else {
Promise.resolve(body(step.value)).then(_resumeAfterBody).then(void 0, _reject);
}
}
function _reject(error) {
_settle(pact, 2, iterator.return ? iterator.return().then(function() { return error; }) : error);
}
}
return Promise.resolve(_forOf(target, function(value) { return Promise.resolve(value).then(body); }, check));
}
Expand Down Expand Up @@ -632,9 +631,6 @@ export const _AsyncGenerator = /*#__PURE__*/(function() {
this._resolve = null;
this._return = null;
this._promise = null;
this[Symbol.asyncIterator || (Symbol.asyncIterator = Symbol("Symbol.asyncIterator"))] = function() {
return this;
};
}

function _wrapReturnedValue(value) {
Expand Down Expand Up @@ -732,6 +728,10 @@ export const _AsyncGenerator = /*#__PURE__*/(function() {
_settle(_pact, 2, error);
});
};

_AsyncGenerator.prototype[_asyncIteratorSymbol] = function() {
return this;
};

return _AsyncGenerator;
})();

0 comments on commit 48cc45a

Please sign in to comment.