opensumi / di Goto Github PK
View Code? Open in Web Editor NEWA Dependency Injection Library for JavaScript. Support AOP.
License: MIT License
A Dependency Injection Library for JavaScript. Support AOP.
License: MIT License
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);
});
}
原因是:
Line 357 in f586cd5
useClass
,如果通过 token 注入,通过 useClass
获取不到原实例This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
These updates are awaiting their schedule. Click on a checkbox to get an update now.
.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
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
想要在一个异步函数调用前后包裹其他的异步逻辑,类似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完成之后,再执行第二个。
Lines 405 to 410 in ede8dc8
基于上面的逻辑,最终导致了上面两个例子的异常:
第一种情况下,如果我加了await joinPoint.proceed();
,此时上面代码中的promise.then
最终变成了TestAspect2.interceptAdd
这个函数的内部逻辑等待TestAspect2.interceptAdd
promise完成,导致永远不会完成
第二种情况下,如果joinPoint.proceed();
前面不加await,此时确实能按照 TestAspect2.interceptAdd -> TestAspect.interceptAdd -> TestClass.add 的顺序把逻辑执行完,但还是有下面两个问题:
TestAspect2.interceptAdd
内的await joinPoint.getResult();
时,原始函数尚未被调用过,此时joinPoint.getResult()
返回的ret
是undefined,如果hook内需要等待原始函数执行完毕,然后再执行其他操作,就无法实现了await testClass.add(1, 2)
返回的promise其实是TestAspect2.interceptAdd
的执行结果promise,因为上面的原因 TestAspect2.interceptAdd
会提前执行完毕,所以await testClass.add(1, 2)
也会提前执行完毕,并且返回了一个错误的结果undefined(此时原始函数并没有被执行..)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
.
在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
});
});
Lines 97 to 110 in e4e6bf5
在@Autowired的实现上取值时通过Symbol做了缓存,这个缓存是独立在di之外的,在dispose时并不会清理这个缓存,导致再次获取值的时使用的还是缓存的内容。
这个问题导致两个问题:
How do you contributors like tsyringe
?
Would you help me to find out differences between tsyringe
and opensumi/di
.
Thank you.
RT,如果在 child injector 中绑定了 contribution provider,这个 provider 调用 getContributions 时获取不到实例,除非把 parent 的 providers 在 child 再 add 一次才行
Hi, this looks like a great library. I was wondering if you could provide comments and docs in English.
同 #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
我们开发了一个 基于Opensumi 的 SDK,在自己的demo页面工作良好,但是提供给外部引用(monorepo源码引用)后,发现大量 DI 无法工作的场景。
不知道是不是 对webpack 配置有特殊要求,已经开启了对修饰器的编译支持,打断点确定 autowired 函数已经运行。
// 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 创建出来的 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()),
};
在 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'
请问这两种方法使用上有什么区别,分别在什么情况下会用到,以及适合在什么场景下使用?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.