简体   繁体   English

如何使用Babel 7的插件提议装饰器和异步函数来装饰异步方法?

[英]How do I decorate an async method using Babel 7's plugin-proposal-decorators with an async function?

I've created an exclusively decorator-driven AOP library that supports Before, AfterReturning, AfterThrowing, AfterFinally, and Around advice (a la AspectJ ). 我创建了一个专门的装饰器驱动的AOP库,它支持Before,AfterReturning,AfterThrowing,AfterFinally和Around建议(一个AspectJ )。 It's called @scispike/aspectify . 它被称为@ scispike / aspectify

It works great using all synchronous code. 它使用所有同步代码都很好用。 All synchronous tests pass just fine . 所有同步测试都 通过

Problem occurs when I attempt to decorate an asynchronous method with asynchronous advice (that is, with a decorator that evaluates to an asynchronous function). 当我尝试使用异步通知装饰异步方法时(即,使用评估为异步函数的装饰器),会出现问题。 I get a syntax error from Babel when issuing mocha --opts mocha.opts 'src/test/unit/**/*.spec.js' | npx bunyan 在发布mocha --opts mocha.opts 'src/test/unit/**/*.spec.js' | npx bunyan时我从Babel得到语法错误 - mocha --opts mocha.opts 'src/test/unit/**/*.spec.js' | npx bunyan mocha --opts mocha.opts 'src/test/unit/**/*.spec.js' | npx bunyan -- (via npm run u -- see package.json and my mocha.opts ): mocha --opts mocha.opts 'src/test/unit/**/*.spec.js' | npx bunyan - (通过npm run u - 请参阅package.json和我的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

For those still with me, the test containing before.spec.js:50 is: 对于那些还在我身边的人来说,包含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)
  })

Edit 1, as requested by @hackape: If you clone the git repo , cd into it, issue npm install then npm run transpile , all code can be found in lib/main and lib/test . 按照@hackape的要求编辑1:如果克隆git repo ,cd进入它,发出npm install然后npm run transpile ,所有代码都可以在lib/mainlib/test To run unit tests in place, run npm run u ; 要运行单元测试,运行npm run u ; to run transpiled unit tests, run npm run unit . 运行转换单元测试,运行npm run unit In any case, here's the transpiled code. 无论如何,这是转换后的代码。

Transpiled file lib/main/Advice.js : 透明文件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>

Transpiled file lib/test/async/before.spec.js : 透明文件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>

Transpiled file lib/test/async/pause.js : 透明文件lib/test/async/pause.js

'use strict';

const pause = async (ms, value) => new Promise(resolve => setTimeout(() => resolve(value), ms));

module.exports = pause;
//# sourceMappingURL=<snipped>

I try clone your repo, set retainLines: false , and the syntax error goes away. 我尝试克隆你的repo,设置retainLines: false ,语法错误就消失了。

Looks like a bug in babel, retainLines probably doesn't play well with async function syntax. 看起来像babel中的bug, retainLines可能与异步函数语法不能很好地兼容。

Full steps to reproduce my result: 重现我的结果的完整步骤:

git clone git@github.com:SciSpike/aspectify.git 
git checkout async
npm install

# modified package.json ln:74 to "retainLines": false

npm run u

If you're seeing different behavior, it could be difference in our env. 如果你看到不同的行为,那可能与我们的环境不同。 Try start from a fresh git clone. 尝试从一个新的git克隆开始。

Also, you can try babel REPL to see what configuration produce the correct code. 此外,您可以尝试使用babel REPL来查看哪些配置生成了正确的代码。 I setup a basic example for you: link 我为你设置了一个基本的例子: 链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM