inversify / InversifyJS

A powerful and lightweight inversion of control container for JavaScript & Node.js apps powered by TypeScript.
http://inversify.io/
MIT License
11.02k stars 712 forks source link

getAllAsync fails when used with .toService and async .toDynamicValue #1564

Open humodz opened 3 months ago

humodz commented 3 months ago

I've provided a full example in this gist: https://gist.github.com/humodz/364f0046eaae1aa724767da934719f64

But, in a nutshell, getAllAsync can't resolve dynamic dependencies if .toService was used to bound the requested services

container.bind(Database).toDynamicValue(async () => { /* ... */ });
container.bind(Service1).toSelf(); // Service1 depends on Database
container.bind('services').toService(Service1);

const services = await container.getAllAsync('services');
// throws: You are attempting to construct Service1 in a synchronous way but it has asynchronous dependencies.

The same problem occurs when using @multiInject

Expected Behavior

getAllAsync should be able to resolve all async dependencies, even transitive ones, just like getAsync

Current Behavior

getAllAsync is only able to resolve the requested services asynchronously, but not their dependencies

Steps to Reproduce (for bugs)

https://gist.github.com/humodz/364f0046eaae1aa724767da934719f64

Additional Issues

In the gist, if I uncomment container.bind(Database).toSelf(); and comment the .toDynamicValue call, then two instances of Database and two instances of Service1 are created, even though the default scope is Request. Is this intended? From taking a look at https://github.com/inversify/InversifyJS/blob/master/test/features/transitive_bindings.test.ts, it feels that the intended behavior would be for a single instance of each to have been created.

Context

In the end, I'm trying to find a way to get all Controllers in the application. My idea was to try:

container.bind('controllers').toService(Controller1);
container.bind('controllers').toService(Controller2);
// ... etc

const controllers = await container.getAllASync('controllers');

But then I ran into the aforementioned issue

Your Environment

$ node --version
v18.18.0
$ npm why inversify
inversify@6.0.2

Stack trace

Error: You are attempting to construct 'class{constructor(database){this.database=database;console.log("new Service1")}}' in a synchronous way
 but it has asynchronous dependencies.
    at Container._getButThrowIfAsync (/home/hugo/projects/scratch/hello-fastify/node_modules/inversify/lib/container/container.js:601:19)
    at Container.get (/home/hugo/projects/scratch/hello-fastify/node_modules/inversify/lib/container/container.js:374:21)
    at Binding.dynamicValue (/home/hugo/projects/scratch/hello-fastify/node_modules/inversify/lib/syntax/binding_to_syntax.js:102:75)
    at /home/hugo/projects/scratch/hello-fastify/node_modules/inversify/lib/resolution/resolver.js:94:119
    at tryAndThrowErrorIfStackOverflow (/home/hugo/projects/scratch/hello-fastify/node_modules/inversify/lib/utils/exceptions.js:31:16)
    at _resolveFactoryFromBinding (/home/hugo/projects/scratch/hello-fastify/node_modules/inversify/lib/resolution/resolver.js:94:61)
    at _getResolvedFromBinding (/home/hugo/projects/scratch/hello-fastify/node_modules/inversify/lib/resolution/resolver.js:112:22)
    at /home/hugo/projects/scratch/hello-fastify/node_modules/inversify/lib/resolution/resolver.js:127:22
    at _resolveInScope (/home/hugo/projects/scratch/hello-fastify/node_modules/inversify/lib/resolution/resolver.js:121:14)
    at _resolveBinding (/home/hugo/projects/scratch/hello-fastify/node_modules/inversify/lib/resolution/resolver.js:126:12)