Code Monkey home page Code Monkey logo

di's People

Contributors

akatquas avatar bk1012 avatar bytemain avatar congyuandong avatar erha19 avatar hacke2 avatar renovate[bot] avatar snnsnn avatar zhangpanweb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

di's Issues

【QA】请教一下,这个类什时候能释放掉?

因为我们是vue的项目,而且是嵌套在qiankun中的某个子项目,我们需要在切换到其它的子项目时需要释放掉这些类,想问下这些类什么情况下会释放掉?有没有什么手动的api可以释放这些类?主要是害怕内存泄漏相关的....

singleton class injected by token and decorated by aspect will be instanced multiple times

Aspect hook 针对通过 token 注入的依赖,会进行多次实例化

单测如下:

describe('aspect', () => {
 it('aspect针对token注入的内容,不会多次实例化', () => {
    const spy = jest.fn();

    @Injectable()
    class B {
      async do() {}
    }
    @Aspect()
    @Injectable()
    class A {
      constructor() {
        spy();
      }

      @Around(B, 'do', { await: true })
      async aroundDo() {}
    }
    const token = 'token';

    const injector = new Injector();
    injector.addProviders({
      token,
      useClass: A,
    });
    injector.addProviders(B);
    injector.get(token);
    injector.get(B);
    expect(spy).toHaveBeenCalledTimes(1);

    const b = injector.get(B);
    b.do();
    expect(spy).toHaveBeenCalledTimes(1);
  });
}

原因是:

instance = this.get(creator.useClass);
这里固定用的是 useClass,如果通过 token 注入,通过 useClass 获取不到原实例

在vite创建出来的项目中使用Autowired异常

在vite 5 创建的环境中使用 2.1.0版本,使用Autowired的代码截图如下

1

但是从容器中获取SplitPanelManager实例的时候,实例属性injector为undefined,而有效值则在SplitPanelManager类的原型上。打印对象截图如下

2

删除实例属性Injector才能访问到注入的对象,因自身技术差找不出问题出在哪里,该怎样操作才能解决问题。

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Awaiting Schedule

These updates are awaiting their schedule. Click on a checkbox to get an update now.

  • chore(deps): update dependency husky to v8.0.2
  • chore(deps): update dependency jest to v29.3.0

Detected dependencies

github-actions
.github/workflows/ci.yml
  • actions/checkout v3
  • actions/setup-node v3
  • codecov/codecov-action v3
.github/workflows/codeql-analysis.yml
  • actions/checkout v3
  • github/codeql-action v2
  • github/codeql-action v2
  • github/codeql-action v2
.github/workflows/release.yml
  • actions/checkout v3
  • actions/setup-node v3
  • ncipollo/release-action v1
npm
package.json
  • reflect-metadata ^0.1.13
  • @commitlint/cli 17.2.0
  • @commitlint/config-conventional 17.2.0
  • @types/jest 29.2.2
  • @types/node 18.11.9
  • @typescript-eslint/eslint-plugin 5.42.1
  • @typescript-eslint/parser 5.42.1
  • commitlint 17.2.0
  • eslint 8.27.0
  • eslint-config-prettier 8.5.0
  • husky 8.0.1
  • jest 29.2.2
  • lint-staged 13.0.3
  • prettier 2.7.1
  • standard-version 9.5.0
  • ts-jest 29.0.3
  • typescript 4.8.4
  • node >=8.0.0

  • Check this box to trigger a request for Renovate to run again on this repository

使用异步hook时,调用时序有问题

诉求

想要在一个异步函数调用前后包裹其他的异步逻辑,类似koa的middleware

复现场景

预期输出日志的顺序是:
TestAspect2 async 10
TestClass add result 3
TestAspect 3
TestAspect2 3
TestClass invoke result 3

但是尝试了下面两种写法都不行,写法1是直接promise无法结束,逻辑执行不下去了,写法 2 逻辑能执行完,但是时序不对

import { Injectable, Aspect, Around, IAroundJoinPoint, Injector } from '../src';

describe('aspect', () => {
  /**
   * 下面的 case 目前输出:
   * TestAspect2 async 10
   * 然后执行超时
   */
  it('异步的hook异常, promise无法结束', async () => {
    function delay(value: number, time: number): Promise<number> {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(value);
        }, time);
      });
    }

    @Injectable()
    class TestClass {
      async add(a: number, b: number): Promise<number> {
        const data = await delay(a + b, 1000);
        console.log('TestClass add result', data);
        return data;
      }
    }

    @Aspect()
    @Injectable()
    class TestAspect {
      @Around<TestClass, [number, number], number>(TestClass, 'add', { await: true })
      async interceptAdd(joinPoint: IAroundJoinPoint<TestClass, [number, number], number>) {
        expect(joinPoint.getMethodName()).toBe('add');
        expect(joinPoint.getOriginalArgs()).toBeInstanceOf(Array);
        expect(joinPoint.getThis()).toBeInstanceOf(TestClass);
        await joinPoint.proceed();
        const result = await joinPoint.getResult();
        console.log('TestAspect', result);
      }
    }

    @Aspect()
    @Injectable()
    class TestAspect2 {
      @Around<TestClass, [number, number], number>(TestClass, 'add', { await: true })
      async interceptAdd(joinPoint: IAroundJoinPoint<TestClass, [number, number], number>) {
        const other = await delay(10, 1000);
        console.log('TestAspect2 async', other);
        expect(joinPoint.getMethodName()).toBe('add');
        expect(joinPoint.getOriginalArgs()).toBeInstanceOf(Array);
        expect(joinPoint.getThis()).toBeInstanceOf(TestClass);
        await joinPoint.proceed();
        const result = await joinPoint.getResult();
        console.log('TestAspect2', result);
      }
    }

    const injector = new Injector();
    injector.addProviders(TestClass);
    injector.addProviders(TestAspect);
    injector.addProviders(TestAspect2);

    const testClass = injector.get(TestClass);

    const result = await testClass.add(1, 2);
    console.log('TestClass invoke result', result);

    // expect(result).toBe(3);
  });

  /**
   * 下面的 case 目前输出:
   * TestAspect2 async 10
   * TestAspect2 undefined
   * TestClass invoke result undefined
   * 到这里单测就停了,其实后续会再执行
   * TestAspect undefined
   * TestClass add result 3
   */
  it('异步的hook异常, 等待的promise错误', async () => {
    function delay(value: number, time: number): Promise<number> {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(value);
        }, time);
      });
    }

    @Injectable()
    class TestClass {
      async add(a: number, b: number): Promise<number> {
        const data = await delay(a + b, 1000);
        console.log('TestClass add result', data);
        return data;
      }
    }

    @Aspect()
    @Injectable()
    class TestAspect {
      @Around<TestClass, [number, number], number>(TestClass, 'add', { await: true })
      async interceptAdd(joinPoint: IAroundJoinPoint<TestClass, [number, number], number>) {
        expect(joinPoint.getMethodName()).toBe('add');
        expect(joinPoint.getOriginalArgs()).toBeInstanceOf(Array);
        expect(joinPoint.getThis()).toBeInstanceOf(TestClass);
        joinPoint.proceed();
        const result = await joinPoint.getResult();
        console.log('TestAspect', result);
      }
    }

    @Aspect()
    @Injectable()
    class TestAspect2 {
      @Around<TestClass, [number, number], number>(TestClass, 'add', { await: true })
      async interceptAdd(joinPoint: IAroundJoinPoint<TestClass, [number, number], number>) {
        const other = await delay(10, 1000);
        console.log('TestAspect2 async', other);
        expect(joinPoint.getMethodName()).toBe('add');
        expect(joinPoint.getOriginalArgs()).toBeInstanceOf(Array);
        expect(joinPoint.getThis()).toBeInstanceOf(TestClass);
        joinPoint.proceed();
        const result = await joinPoint.getResult();
        console.log('TestAspect2', result);
      }
    }

    const injector = new Injector();
    injector.addProviders(TestClass);
    injector.addProviders(TestAspect);
    injector.addProviders(TestAspect2);

    const testClass = injector.get(TestClass);

    const result = await testClass.add(1, 2);
    console.log('TestClass invoke result', result);

    // expect(result).toBe(3);
  });
});

原因

大概看了下实现的代码,这段逻辑有点奇怪,目前函数的调用顺序是:
TestAspect2.interceptAdd -> TestAspect.interceptAdd -> TestClass.add

此时promise的完成顺序应该是反过来的:
TestClass.add -> TestAspect.interceptAdd -> TestAspect2.interceptAdd

但是下面这段逻辑中,promise的等待顺序没有反过来,必须要等第一个函数的promise完成之后,再执行第二个。

if (hook.awaitPromise) {
// 如果要求await hook,如果之前有promise,直接用,不然创建Promise给下一个使用
promise = promise || Promise.resolve();
promise = promise.then(() => {
return hook.hook(joinPoint);
});

基于上面的逻辑,最终导致了上面两个例子的异常:

第一种情况下,如果我加了await joinPoint.proceed();,此时上面代码中的promise.then最终变成了TestAspect2.interceptAdd这个函数的内部逻辑等待TestAspect2.interceptAdd promise完成,导致永远不会完成

第二种情况下,如果joinPoint.proceed();前面不加await,此时确实能按照 TestAspect2.interceptAdd -> TestAspect.interceptAdd -> TestClass.add 的顺序把逻辑执行完,但还是有下面两个问题:

  1. hook函数执行时序不对,因为在执行TestAspect2.interceptAdd内的await joinPoint.getResult();时,原始函数尚未被调用过,此时joinPoint.getResult()返回的ret是undefined,如果hook内需要等待原始函数执行完毕,然后再执行其他操作,就无法实现了
  2. 在单测中调用await testClass.add(1, 2)返回的promise其实是TestAspect2.interceptAdd的执行结果promise,因为上面的原因 TestAspect2.interceptAdd会提前执行完毕,所以await testClass.add(1, 2)也会提前执行完毕,并且返回了一个错误的结果undefined(此时原始函数并没有被执行..)

Bug: Contructor injection does not work

Hi, I was going through the example, I could not reproduce injections via constructors:

import { Inject, Injectable, Injector } from '@opensumi/di';

interface Drivable {
  drive(): void;
}

@Injectable()
class Student {
  constructor(@Inject('Drivable') public vehicle: Drivable) {}

  go() {
    this.vehicle.drive();
  }
}

@Injectable()
class Car implements Drivable {
  drive() {
    console.log('Driving Car');
  }
}

const injector = new Injector();
injector.addProviders(Student);
injector.addProviders({ token: 'Drivable', useClass: Car });

const student = injector.get(Student);
student.go();

This outputs:

Running.. [index.ts:13:12](webpack://kiki/application/src/index.ts)
Uncaught TypeError: this.vehicle is undefined
    go index.ts:14

Explicitly assigning the property in the constructor also did not work:

constructor(@Inject('Drivable') public vehicle: Drivable) {
  this.vehicle = vehicle;
}

Then I tried the example from the README file, which also did not work:

import { Inject, Injectable, Injector } from '@opensumi/di';

@Injectable()
class A {
  constructor() {
    console.log('Create A');
  }
}

@Injectable()
class B {
  constructor(public a: A){}
}

const injector = new Injector();
injector.addProviders(A, B);

const b = injector.get(B);
console.log(b.a instanceof A);  // false
console.log(b.a);  // undefined

Using a token did not change the situation, b.a still returns undefined.

使用disposeOne后,实例没有被销毁

诉求

在B里面通过@Autowired注入A,然后对A的token执行了disposeOne,此时在B内再次使用A时,应该重新创建实例

复现场景

下面的例子在B里面通过@Autowired注入A,然后对A的token执行了disposeOne,,但是b.a返回的对象还是之前a的实例

import { Injectable, Injector, Autowired } from '../../src';

@Injectable()
class A {
  num = 1;

  add() {
    this.num += 1;
  }
}

@Injectable()
class B {
  @Autowired()
  a!: A;
}

describe('dispose', () => {
  let injector: Injector;

  beforeEach(() => {
    injector = new Injector();
  });

  it('成功进行批量对象销毁', async () => {
    const a = injector.get(A);
    const b = injector.get(B);
    expect(injector.hasInstance(a)).toBeTruthy();
    expect(injector.hasInstance(b)).toBeTruthy();

    b.a.add();
    expect(b.a.num).toBe(2);

    injector.disposeOne(B);
    expect(injector.hasInstance(b)).toBeFalsy();

    // 这里的 A 的实例并没有被销毁,并且因为 Autowired 内有缓存的原因也不会再创建了
    expect(b.a.num).toBe(1); // 目前还是2
    expect(injector.hasInstance(b)).toBeTruthy(); // 目前这里是 false
  });
});

原因

di/src/decorator.ts

Lines 97 to 110 in e4e6bf5

get(this: any) {
if (!this[INSTANCE_KEY]) {
const injector = Helper.getInjectorOfInstance(this);
if (!injector) {
throw Error.noInjectorError(this);
}
this[INSTANCE_KEY] = injector.get(realToken, opts);
}
return this[INSTANCE_KEY];
},
};

@Autowired的实现上取值时通过Symbol做了缓存,这个缓存是独立在di之外的,在dispose时并不会清理这个缓存,导致再次获取值的时使用的还是缓存的内容。

这个问题导致两个问题:

  1. 这样如果要重新生成 A 的实例,就必须要把 B 也清理重新生成了才行,会比较奇怪,如果哪个地方忘了清理,就会出现虽然写法上预期是单例的,但是实际使用上会有多个实例
  2. 这个缓存的实例会被注入的地方持续引用,内存无法释放

Have comments in English

Hi, this looks like a great library. I was wondering if you could provide comments and docs in English.

使用disposeOne后,useFactory 结果未重置

#109 ,执行 disposeOne 之后, useFactory 的结果未重置,参见以下 单测

it('disposeOne should dispose instance of useFactory', () => {
    const injector = new Injector();
    let aValue = 1;
    const token = Symbol.for('A');

    injector.addProviders(
      ...[
        {
          token,
          useFactory: () => aValue,
        },
      ],
    );

    @Injectable()
    class B {
      @Autowired(token)
      a!: number;
    }

    const instance = injector.get(B);
    expect(injector.hasInstance(instance)).toBeTruthy();
    expect(instance).toBeInstanceOf(B);
    expect(instance.a).toBe(1);

    injector.disposeOne(token);
    aValue = 2;
    console.log('==== ', instance.a);
    expect(instance.a).toBe(2);
  });

应该正常运行,但是最后获取的 instance.a 仍然为 1

DI Autowired无法工作,是否对 webpack 打包脚本有什么特别的要求

我们开发了一个 基于Opensumi 的 SDK,在自己的demo页面工作良好,但是提供给外部引用(monorepo源码引用)后,发现大量 DI 无法工作的场景。
image
不知道是不是 对webpack 配置有特殊要求,已经开启了对修饰器的编译支持,打断点确定 autowired 函数已经运行。

下面是最小复现的 demo。

// const.ts

export const TestToken = Symbol('token');
export const TestClassToken = Symbol('TestClassToken');

// TestClass.ts

import { Injectable, Autowired } from '@opensumi/di';

import { TestToken } from './const';

@Injectable()
export class TestClass {
  @Autowired(TestToken)
  public value: string;
}

// index.ts

const injector = new Injector();
injector.addProviders({
  token: TestToken,
  useValue: 'test',
});
injector.addProviders({
  token: TestClassToken,
  useClass: TestClass,
});

console.log('TestClassToken', injector, injector.get(TestClassToken), injector.get(TestClassToken).value); // 这里 value为undefined
```

useFactory 的结果不支持 hasInstance 检测

如下面这个例子,通过 useFactory 创建出来的 value,调用 injector.hasInstance 结果为 false。

const token = 'Token';

@Injectable()
class A {}

const provider = {
  token,
  useFactory: () => new A(),
};

const injector = new Injector([provider]);
const a = injector.get(token);
injector.hasInstance(a) === false; // true

提供的 asSingleton 也同样不支持 hasInstance 检测:

const provider = {
  token,
  useFactory: asSingleton(() => new A()),
};

[Question] 使用 Token 和 使用抽象类进行依赖注入有什么区别?

在 DI 部分,有 “类型依赖抽象而不是依赖实现的用法” 的注入方法,如下:

const LOGGER_TOKEN = Symbol('LOGGER_TOKEN');

interface Logger {
  log(msg: string): void;
}

@Injectable()
class App {
  @Autowired(LOGGER_TOKEN)
  logger: Logger;
}

@Injectable()
class LoggerImpl implements Logger {
  log(msg: string) {
    console.log(msg);
  }
}

const injector = new Injector();
injector.addProviders(App);
injector.addProviders({
  token: LOGGER_TOKEN,
  useClass: LoggerImpl,
});

const app = injector.get(App);
console.log(app.logger instanceof LoggerImpl); // print 'true'

也有 “使用抽象函数作为 Token 进行依赖注入” 的注入方法,如下:

abstract class Logger {
  abstract log(msg: string): void;
}

@Injectable()
class LoggerImpl implements Logger {
  log(msg: string) {
    console.log(msg);
  }
}

@Injectable()
class App {
  @Autowired()
  logger: Logger;
}

const injector = new Injector();
injector.addProviders(App);
injector.addProviders({
  token: Logger,
  useClass: LoggerImpl,
});

const app = injector.get(App);
console.log(app.logger instanceof LoggerImpl); // print 'true'

请问这两种方法使用上有什么区别,分别在什么情况下会用到,以及适合在什么场景下使用?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.