如何使用带有异步函数的 Babel 7 插件提案装饰器来装饰异步方法?
How do I decorate an async method using Babel 7's plugin-proposal-decorators with an async function?
我创建了一个专门由装饰器驱动的 AOP 库,它支持 Before、AfterReturning、AfterThrowing、AfterFinally 和 Around 建议(la AspectJ). It's called @scispike/aspectify.
使用所有同步代码效果很好。全部 synchronous tests pass just fine.
当我试图用异步通知(即,用一个评估为异步函数的装饰器)装饰一个异步方法时出现问题。我在发布 mocha --opts mocha.opts 'src/test/unit/**/*.spec.js' | npx bunyan
时从 Babel 收到语法错误 -- (通过 npm run u
-- 参见 package.json and my mocha.opts
):
$ npm --version
6.4.1
$ node --version
v10.14.2
$ npm run u
npm WARN lifecycle The node binary used for scripts is /Users/matthewadams/.asdf/shims/node but npm is using /Users/matthewadams/.asdf/installs/nodejs/10.14.2/bin/node itself. Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed with.
> @scispike/aspectify@0.1.0-pre.1 u /Users/matthewadams/dev/scispike/aspectify
> mocha --opts mocha.opts 'src/test/unit/**/*.spec.js' | npx bunyan
/Users/matthewadams/dev/scispike/aspectify/src/test/unit/async/before.spec.js:50
go(delayMillis, value) {
^^
SyntaxError: Unexpected identifier
at new Script (vm.js:79:7)
at createScript (vm.js:251:10)
at Object.runInThisContext (vm.js:303:10)
at Module._compile (internal/modules/cjs/loader.js:657:28)
at Module._compile (/Users/matthewadams/dev/scispike/aspectify/node_modules/pirates/lib/index.js:99:24)
at Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Object.newLoader [as .js] (/Users/matthewadams/dev/scispike/aspectify/node_modules/pirates/lib/index.js:104:7)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Module.require (internal/modules/cjs/loader.js:637:17)
at require (internal/modules/cjs/helpers.js:22:18)
at /Users/matthewadams/dev/scispike/aspectify/node_modules/mocha/lib/mocha.js:250:27
at Array.forEach (<anonymous>)
at Mocha.loadFiles (/Users/matthewadams/dev/scispike/aspectify/node_modules/mocha/lib/mocha.js:247:14)
at Mocha.run (/Users/matthewadams/dev/scispike/aspectify/node_modules/mocha/lib/mocha.js:576:10)
at Object.<anonymous> (/Users/matthewadams/dev/scispike/aspectify/node_modules/mocha/bin/_mocha:637:18)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
at startup (internal/bootstrap/node.js:282:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:743:3)
^Cmake: *** [.] Interrupt: 2
npm ERR! code ELIFECYCLE
npm ERR! errno 130
npm ERR! @scispike/aspectify@0.1.0-pre.1 u: `mocha --opts mocha.opts 'src/test/unit/**/*.spec.js' | npx bunyan`
npm ERR! Exit status 130
npm ERR!
npm ERR! Failed at the @scispike/aspectify@0.1.0-pre.1 u script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! /Users/matthewadams/.npm/_logs/2019-04-01T17_17_04_757Z-debug.log
对于那些仍然和我在一起的人,包含 before.spec.js:50 的测试是:
describe('parameterless before advice', function () {
it('should work', async function () {
let count = 0
const delay = 10
const val = 1
const ParameterlessBeforeCount = Before(async thisJoinPoint => {
await pause(delay + 100)
count++
})
class Class {
@ParameterlessBeforeCount
async go (delayMillis, value) { // <-- THIS IS LINE 50
await pause(delayMillis)
return value
}
}
const c = new Class()
const v = await c.go(delay, val)
expect(count).to.equal(1)
expect(v).to.equal(val)
})
根据@hackape 的要求编辑 1:
如果你克隆 the git repo,cd 进去,发出 npm install
然后 npm run transpile
,所有代码都可以在 lib/main
和 lib/test
中找到。要 运行 单元测试到位,运行 npm run u
; 运行 转译的单元测试,运行 npm run unit
。无论如何,这是转译后的代码。
转译后的文件lib/main/Advice.js
:
'use strict';
/**
* Returns a decorator that applies advice of any type.
* @param modify [{function}] Function that takes a `thisJoinPointStaticPart` that can be used to modify the decorated member.
* @param before [{function}] Function that takes a `thisJointPoint` that runs before execution proceeds.
* @param afterReturning [{function}] Function that takes a `thisJointPoint` that runs after execution normally completes.
* @param afterThrowing [{function}] Function that takes a `thisJointPoint` that runs after execution completes with an error.
* @param afterFinally [{function}] Function that takes a `thisJointPoint` that runs after execution completes via `finally`.
* @param around [{function}] Function that takes a `thisJointPoint` that leaves it to the developer to control behavior; no other advice functions are called.
* @return {Function}
* @private
*/
const Advice = ({ modify, before, afterReturning, afterThrowing, afterFinally, around } = {}) => {
return (clazz, name, originalDescriptor) => {
const advisedDescriptor = { ...originalDescriptor };
let value;
let get;
let set;
if (originalDescriptor.value && typeof originalDescriptor.value === 'function') value = originalDescriptor.value;
if (originalDescriptor.get && typeof originalDescriptor.get === 'function') get = originalDescriptor.get;
if (originalDescriptor.set && typeof originalDescriptor.set === 'function') set = originalDescriptor.set;
const thisJoinPointStaticPart = {
clazz,
name,
descriptors: {
original: originalDescriptor,
advised: advisedDescriptor } };
if (get || set) thisJoinPointStaticPart.accessor = true;
if (value) thisJoinPointStaticPart.method = true;
if (modify) {
modify(thisJoinPointStaticPart);
}
const createAdvisedFn = function (originalFn) {
return function advisedFn(...args) {
const thisJoinPoint = {
thiz: this,
args,
fullName: thisJoinPointStaticPart.name,
...thisJoinPointStaticPart };
if (thisJoinPoint.accessor) {
if (args.length === 0) {
thisJoinPoint.get = thisJoinPoint.fullName = `get ${thisJoinPoint.name}`;
} else {
thisJoinPoint.set = thisJoinPoint.fullName = `set ${thisJoinPoint.name}`;
}
}
const proceed = ({ thiz, args: newArgs } = {}) => originalFn.apply(thiz || this, newArgs || args);
if (around) {
thisJoinPoint.proceed = proceed;
return around(thisJoinPoint);
}
let returnValue;
let error;
try {
if (before) {
before(thisJoinPoint);
}
returnValue = proceed();
if (afterReturning) {
afterReturning({ returnValue, thisJoinPoint });
}
return returnValue;
} catch (e) {
error = e;
if (afterThrowing) {
afterThrowing({ error, thisJoinPoint });
}
throw error;
} finally {
if (afterFinally) {
afterFinally({ returnValue, error, thisJoinPoint });
}
}
};
};
if (value) {
advisedDescriptor.value = createAdvisedFn(value);
} else {
if (get) advisedDescriptor.get = createAdvisedFn(get);
if (set) advisedDescriptor.set = createAdvisedFn(set);
}
return advisedDescriptor;
};
};
const Around = (advice, modify) => Advice({ around: advice, modify });
const Before = (advice, modify) => Advice({ before: advice, modify });
const AfterReturning = (advice, modify) => Advice({ afterReturning: advice, modify });
const AfterThrowing = (advice, modify) => Advice({ afterThrowing: advice, modify });
const AfterFinally = (advice, modify) => Advice({ afterFinally: advice, modify });
module.exports = {
Around,
Before,
AfterReturning,
AfterThrowing,
AfterFinally };
//# sourceMappingURL=<snipped>
转译后的文件lib/test/async/before.spec.js
:
/* global it, describe */
'use strict';function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {var desc = {};Object.keys(descriptor).forEach(function (key) {desc[key] = descriptor[key];});desc.enumerable = !!desc.enumerable;desc.configurable = !!desc.configurable;if ('value' in desc || desc.initializer) {desc.writable = true;}desc = decorators.slice().reverse().reduce(function (desc, decorator) {return decorator(target, property, desc) || desc;}, desc);if (context && desc.initializer !== void 0) {desc.value = desc.initializer ? desc.initializer.call(context) : void 0;desc.initializer = undefined;}if (desc.initializer === void 0) {Object.defineProperty(target, property, desc);desc = null;}return desc;}
const chai = require('chai');
const expect = chai.expect;
chai.use(require('dirty-chai'));
const { Before } = require('../../../main/Advice');
const pause = require('./pause');
class Class {
async go(delayMillis, value) {
await pause(delayMillis);
return value;
}}
describe('unit tests of asynchronous before advice', function () {
describe('base Class', function () {
it('should work', async function () {
const c = new Class();
const v = await c.go(10, 1);
expect(v).to.equal(1);
});
it('subclass should work', async function () {
class Subclass extends Class {
async go(d, v) {
return super.go(d, v);
}}
const c = new Subclass();
const v = await c.go(10, 1);
expect(v).to.equal(1);
});
});
describe('parameterless before advice', function () {
it('should work', async function () {var _class;
let count = 0;
const delay = 10;
const val = 1;
const ParameterlessBeforeCount = Before(async thisJoinPoint => {
await pause(delay + 100);
count++;
});let
Class = (_class = class Class {
async
go(delayMillis, value) {
await pause(delayMillis);
return value;
}}, (_applyDecoratedDescriptor(_class.prototype, "go", [ParameterlessBeforeCount], Object.getOwnPropertyDescriptor(_class.prototype, "go"), _class.prototype)), _class);
const c = new Class();
const v = await c.go(delay, val);
expect(count).to.equal(1);
expect(v).to.equal(val);
});
});
});
//# sourceMappingURL=<snipped>
转译后的文件lib/test/async/pause.js
:
'use strict';
const pause = async (ms, value) => new Promise(resolve => setTimeout(() => resolve(value), ms));
module.exports = pause;
//# sourceMappingURL=<snipped>
我尝试克隆您的存储库,设置 retainLines: false
,语法错误消失了。
看起来像是 babel 中的错误,retainLines
可能无法很好地使用异步函数语法。
重现我的结果的完整步骤:
git clone git@github.com:SciSpike/aspectify.git
git checkout async
npm install
# modified package.json ln:74 to "retainLines": false
npm run u
如果您看到不同的行为,可能是我们的环境不同。尝试从一个新的 git 克隆开始。
此外,您可以尝试使用 babel REPL 来查看哪些配置可以生成正确的代码。我为您设置了一个基本示例:link
我创建了一个专门由装饰器驱动的 AOP 库,它支持 Before、AfterReturning、AfterThrowing、AfterFinally 和 Around 建议(la AspectJ). It's called @scispike/aspectify.
使用所有同步代码效果很好。全部 synchronous tests pass just fine.
当我试图用异步通知(即,用一个评估为异步函数的装饰器)装饰一个异步方法时出现问题。我在发布 mocha --opts mocha.opts 'src/test/unit/**/*.spec.js' | npx bunyan
时从 Babel 收到语法错误 -- (通过 npm run u
-- 参见 package.json and my mocha.opts
):
$ npm --version
6.4.1
$ node --version
v10.14.2
$ npm run u
npm WARN lifecycle The node binary used for scripts is /Users/matthewadams/.asdf/shims/node but npm is using /Users/matthewadams/.asdf/installs/nodejs/10.14.2/bin/node itself. Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed with.
> @scispike/aspectify@0.1.0-pre.1 u /Users/matthewadams/dev/scispike/aspectify
> mocha --opts mocha.opts 'src/test/unit/**/*.spec.js' | npx bunyan
/Users/matthewadams/dev/scispike/aspectify/src/test/unit/async/before.spec.js:50
go(delayMillis, value) {
^^
SyntaxError: Unexpected identifier
at new Script (vm.js:79:7)
at createScript (vm.js:251:10)
at Object.runInThisContext (vm.js:303:10)
at Module._compile (internal/modules/cjs/loader.js:657:28)
at Module._compile (/Users/matthewadams/dev/scispike/aspectify/node_modules/pirates/lib/index.js:99:24)
at Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Object.newLoader [as .js] (/Users/matthewadams/dev/scispike/aspectify/node_modules/pirates/lib/index.js:104:7)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Module.require (internal/modules/cjs/loader.js:637:17)
at require (internal/modules/cjs/helpers.js:22:18)
at /Users/matthewadams/dev/scispike/aspectify/node_modules/mocha/lib/mocha.js:250:27
at Array.forEach (<anonymous>)
at Mocha.loadFiles (/Users/matthewadams/dev/scispike/aspectify/node_modules/mocha/lib/mocha.js:247:14)
at Mocha.run (/Users/matthewadams/dev/scispike/aspectify/node_modules/mocha/lib/mocha.js:576:10)
at Object.<anonymous> (/Users/matthewadams/dev/scispike/aspectify/node_modules/mocha/bin/_mocha:637:18)
at Module._compile (internal/modules/cjs/loader.js:689:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
at Module.load (internal/modules/cjs/loader.js:599:32)
at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
at Function.Module._load (internal/modules/cjs/loader.js:530:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
at startup (internal/bootstrap/node.js:282:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:743:3)
^Cmake: *** [.] Interrupt: 2
npm ERR! code ELIFECYCLE
npm ERR! errno 130
npm ERR! @scispike/aspectify@0.1.0-pre.1 u: `mocha --opts mocha.opts 'src/test/unit/**/*.spec.js' | npx bunyan`
npm ERR! Exit status 130
npm ERR!
npm ERR! Failed at the @scispike/aspectify@0.1.0-pre.1 u script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! /Users/matthewadams/.npm/_logs/2019-04-01T17_17_04_757Z-debug.log
对于那些仍然和我在一起的人,包含 before.spec.js:50 的测试是:
describe('parameterless before advice', function () {
it('should work', async function () {
let count = 0
const delay = 10
const val = 1
const ParameterlessBeforeCount = Before(async thisJoinPoint => {
await pause(delay + 100)
count++
})
class Class {
@ParameterlessBeforeCount
async go (delayMillis, value) { // <-- THIS IS LINE 50
await pause(delayMillis)
return value
}
}
const c = new Class()
const v = await c.go(delay, val)
expect(count).to.equal(1)
expect(v).to.equal(val)
})
根据@hackape 的要求编辑 1:
如果你克隆 the git repo,cd 进去,发出 npm install
然后 npm run transpile
,所有代码都可以在 lib/main
和 lib/test
中找到。要 运行 单元测试到位,运行 npm run u
; 运行 转译的单元测试,运行 npm run unit
。无论如何,这是转译后的代码。
转译后的文件lib/main/Advice.js
:
'use strict';
/**
* Returns a decorator that applies advice of any type.
* @param modify [{function}] Function that takes a `thisJoinPointStaticPart` that can be used to modify the decorated member.
* @param before [{function}] Function that takes a `thisJointPoint` that runs before execution proceeds.
* @param afterReturning [{function}] Function that takes a `thisJointPoint` that runs after execution normally completes.
* @param afterThrowing [{function}] Function that takes a `thisJointPoint` that runs after execution completes with an error.
* @param afterFinally [{function}] Function that takes a `thisJointPoint` that runs after execution completes via `finally`.
* @param around [{function}] Function that takes a `thisJointPoint` that leaves it to the developer to control behavior; no other advice functions are called.
* @return {Function}
* @private
*/
const Advice = ({ modify, before, afterReturning, afterThrowing, afterFinally, around } = {}) => {
return (clazz, name, originalDescriptor) => {
const advisedDescriptor = { ...originalDescriptor };
let value;
let get;
let set;
if (originalDescriptor.value && typeof originalDescriptor.value === 'function') value = originalDescriptor.value;
if (originalDescriptor.get && typeof originalDescriptor.get === 'function') get = originalDescriptor.get;
if (originalDescriptor.set && typeof originalDescriptor.set === 'function') set = originalDescriptor.set;
const thisJoinPointStaticPart = {
clazz,
name,
descriptors: {
original: originalDescriptor,
advised: advisedDescriptor } };
if (get || set) thisJoinPointStaticPart.accessor = true;
if (value) thisJoinPointStaticPart.method = true;
if (modify) {
modify(thisJoinPointStaticPart);
}
const createAdvisedFn = function (originalFn) {
return function advisedFn(...args) {
const thisJoinPoint = {
thiz: this,
args,
fullName: thisJoinPointStaticPart.name,
...thisJoinPointStaticPart };
if (thisJoinPoint.accessor) {
if (args.length === 0) {
thisJoinPoint.get = thisJoinPoint.fullName = `get ${thisJoinPoint.name}`;
} else {
thisJoinPoint.set = thisJoinPoint.fullName = `set ${thisJoinPoint.name}`;
}
}
const proceed = ({ thiz, args: newArgs } = {}) => originalFn.apply(thiz || this, newArgs || args);
if (around) {
thisJoinPoint.proceed = proceed;
return around(thisJoinPoint);
}
let returnValue;
let error;
try {
if (before) {
before(thisJoinPoint);
}
returnValue = proceed();
if (afterReturning) {
afterReturning({ returnValue, thisJoinPoint });
}
return returnValue;
} catch (e) {
error = e;
if (afterThrowing) {
afterThrowing({ error, thisJoinPoint });
}
throw error;
} finally {
if (afterFinally) {
afterFinally({ returnValue, error, thisJoinPoint });
}
}
};
};
if (value) {
advisedDescriptor.value = createAdvisedFn(value);
} else {
if (get) advisedDescriptor.get = createAdvisedFn(get);
if (set) advisedDescriptor.set = createAdvisedFn(set);
}
return advisedDescriptor;
};
};
const Around = (advice, modify) => Advice({ around: advice, modify });
const Before = (advice, modify) => Advice({ before: advice, modify });
const AfterReturning = (advice, modify) => Advice({ afterReturning: advice, modify });
const AfterThrowing = (advice, modify) => Advice({ afterThrowing: advice, modify });
const AfterFinally = (advice, modify) => Advice({ afterFinally: advice, modify });
module.exports = {
Around,
Before,
AfterReturning,
AfterThrowing,
AfterFinally };
//# sourceMappingURL=<snipped>
转译后的文件lib/test/async/before.spec.js
:
/* global it, describe */
'use strict';function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {var desc = {};Object.keys(descriptor).forEach(function (key) {desc[key] = descriptor[key];});desc.enumerable = !!desc.enumerable;desc.configurable = !!desc.configurable;if ('value' in desc || desc.initializer) {desc.writable = true;}desc = decorators.slice().reverse().reduce(function (desc, decorator) {return decorator(target, property, desc) || desc;}, desc);if (context && desc.initializer !== void 0) {desc.value = desc.initializer ? desc.initializer.call(context) : void 0;desc.initializer = undefined;}if (desc.initializer === void 0) {Object.defineProperty(target, property, desc);desc = null;}return desc;}
const chai = require('chai');
const expect = chai.expect;
chai.use(require('dirty-chai'));
const { Before } = require('../../../main/Advice');
const pause = require('./pause');
class Class {
async go(delayMillis, value) {
await pause(delayMillis);
return value;
}}
describe('unit tests of asynchronous before advice', function () {
describe('base Class', function () {
it('should work', async function () {
const c = new Class();
const v = await c.go(10, 1);
expect(v).to.equal(1);
});
it('subclass should work', async function () {
class Subclass extends Class {
async go(d, v) {
return super.go(d, v);
}}
const c = new Subclass();
const v = await c.go(10, 1);
expect(v).to.equal(1);
});
});
describe('parameterless before advice', function () {
it('should work', async function () {var _class;
let count = 0;
const delay = 10;
const val = 1;
const ParameterlessBeforeCount = Before(async thisJoinPoint => {
await pause(delay + 100);
count++;
});let
Class = (_class = class Class {
async
go(delayMillis, value) {
await pause(delayMillis);
return value;
}}, (_applyDecoratedDescriptor(_class.prototype, "go", [ParameterlessBeforeCount], Object.getOwnPropertyDescriptor(_class.prototype, "go"), _class.prototype)), _class);
const c = new Class();
const v = await c.go(delay, val);
expect(count).to.equal(1);
expect(v).to.equal(val);
});
});
});
//# sourceMappingURL=<snipped>
转译后的文件lib/test/async/pause.js
:
'use strict';
const pause = async (ms, value) => new Promise(resolve => setTimeout(() => resolve(value), ms));
module.exports = pause;
//# sourceMappingURL=<snipped>
我尝试克隆您的存储库,设置 retainLines: false
,语法错误消失了。
看起来像是 babel 中的错误,retainLines
可能无法很好地使用异步函数语法。
重现我的结果的完整步骤:
git clone git@github.com:SciSpike/aspectify.git
git checkout async
npm install
# modified package.json ln:74 to "retainLines": false
npm run u
如果您看到不同的行为,可能是我们的环境不同。尝试从一个新的 git 克隆开始。
此外,您可以尝试使用 babel REPL 来查看哪些配置可以生成正确的代码。我为您设置了一个基本示例:link