babel-plugin-async-to-promises
Version:
Transpile ES7 async/await to vanilla ES6 Promise chains
167 lines (142 loc) • 6.13 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _babelTypes = require('babel-types');
var _jsExtend = require('js-extend');
var _utils = require('./utils');
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var PromiseChain = function () {
// add, addCatch and addFinally were designed to be called only one time each
// at most. Call them more at your own risk.
//
// addCatch() and addFinally() are not guaranteed to handle return values
// correctly. FIXME.
function PromiseChain(inner, dirtyAllowed, respName, errName) {
_classCallCheck(this, PromiseChain);
this._inner = inner;
this._dirtyAllowed = dirtyAllowed;
this._respName = respName;
this._errName = errName;
this._ast = (0, _babelTypes.callExpression)((0, _babelTypes.memberExpression)((0, _babelTypes.identifier)('Promise'), (0, _babelTypes.identifier)('resolve')), []);
}
_createClass(PromiseChain, [{
key: 'add',
value: function add(block) {
var _this = this;
if (!block.length) {
return;
}
var current = this._addLink('then', []);
block.forEach(function (path) {
var awaitInfos = [];
path.traverse(PromisifyPrepVisitor, { awaitInfos: awaitInfos, respName: _this._respName });
awaitInfos.forEach(function (awaitInfo) {
current.body.push((0, _babelTypes.returnStatement)(awaitInfo.arg));
var params = awaitInfo.passID ? [(0, _babelTypes.identifier)(_this._respName)] : [];
current = _this._addLink('then', params);
});
if (path.node) {
current.body.push(path.node);
}
});
}
}, {
key: '_addLink',
value: function _addLink(type, params, secondParams) {
this._cleanup();
var current = { body: [] };
var handlerBody = (0, _babelTypes.blockStatement)(current.body);
var handlers = [(0, _babelTypes.functionExpression)(null, params, handlerBody)];
if (secondParams) {
current.secondBody = [];
var secondHandlerBody = (0, _babelTypes.blockStatement)(current.secondBody);
handlers.push((0, _babelTypes.functionExpression)(null, secondParams, secondHandlerBody));
}
var method = (0, _babelTypes.memberExpression)(this._ast, (0, _babelTypes.identifier)(type));
this._ast = (0, _babelTypes.callExpression)(method, handlers);
return current;
}
}, {
key: '_cleanup',
value: function _cleanup() {
// if resolving to non-undefined when there is no return is allowed, and
// the last part of the chain is .then(function () {}), then chop off that
// part
var chopOff = this._dirtyAllowed && this._ast.callee.property.name === 'then' && this._ast.arguments.length === 1 && !this._ast.arguments[0].body.body.length;
if (chopOff) {
this._ast = this._ast.callee.object;
}
}
}, {
key: 'addCatch',
value: function addCatch(block, errID) {
var current = this._addLink('catch', [errID]);
var catchChain = this._subChain();
catchChain.add(block);
current.body.push((0, _babelTypes.returnStatement)(catchChain.toAST()));
}
}, {
key: '_subChain',
value: function _subChain() {
return new PromiseChain(true, true, this._respName, this._errName);
}
}, {
key: 'addFinally',
value: function addFinally(block) {
var errID = (0, _babelTypes.identifier)(this._errName);
var current = this._addLink('then', [], [errID]);
var finallyChain = this._subChain();
// disable optimalizations
finallyChain._inner = false;
finallyChain._dirtyAllowed = false;
finallyChain.add(block);
var secondAST = (0, _babelTypes.cloneDeep)(finallyChain.toAST());
// smuggle in the throw statement
secondAST.arguments[0].body.body.push((0, _babelTypes.throwStatement)(errID));
current.secondBody.push((0, _babelTypes.returnStatement)(secondAST));
// re-enable optimalizations
finallyChain._inner = true;
finallyChain._dirtyAllowed = true;
var ast = (0, _babelTypes.returnStatement)(finallyChain.toAST());
current.body.push(ast);
}
}, {
key: 'toAST',
value: function toAST() {
this._cleanup();
var callee = this._ast.callee.object.callee;
if (this._inner && callee && callee.object.name === 'Promise') {
// only one handler to the promise - because we're in an inner function
// there's no reason to wrap the handler in promise code. Convenienly,
// such a handler is inlineable later on.
//
// Summary:
// ``Promise.resolve().then(function () {...})``
// becomes
// ``function () {...}()``
return (0, _babelTypes.callExpression)(this._ast.arguments[0], []);
}
return this._ast;
}
}]);
return PromiseChain;
}();
exports.default = PromiseChain;
var PromisifyPrepVisitor = (0, _jsExtend.extend)({
AwaitExpression: {
exit: function exit(path) {
// exit so awaits are evaluated inside out if there are multiple in
// the expression
var info = { arg: path.node.argument };
if ((0, _babelTypes.isExpressionStatement)(path.parent)) {
path.remove();
} else {
info.passID = true;
path.replaceWith((0, _babelTypes.identifier)(this.respName));
}
this.awaitInfos.push(info);
}
}
}, _utils.NoSubFunctionsVisitor);