How to dynamically bind a dynamically imported typ

2019-07-29 03:18发布

问题:

I am trying to have templates automagically wired to an inversifyjs container but whatever I try it's not working. Please help?

private templates = [
    {file: './component.html.tpl', obj: 'HtmlTemplate'},
    {file: './component.tpl.ts', obj: 'ComponentTemplate'}
];
private container = new Container();
bind(){
    // original and working code 
    // this.container.bind<ITemplate>('HtmlTemplate').to(HtmlTemplate);
    // this.container.bind<ITemplate>('ComponentTemplate').to(ComponentTemplate);
    this.templates.forEach(template => {
        import(template.file).then(mod => {
            console.log(mod.default, template);
            // is this correct (seems to work) => 
            this.container.bind<ITemplate>(template.obj).to(mod.default);
            console.log('bound =>', mod.default);
        });
    });
}

and files ./component.html.tpl

@injectable() export default class HtmlTemplate implements ITemplate { ... }

and ./component.ts.tpl

@injectable() export default class ComponentTemplate implements ITemplate { ... }

Which logs completely as expected to the console:

[Function: HtmlTemplate] { file: './component.html.tpl', obj: 'HtmlTemplate' }
[Function: ComponentTemplate] { file: './component.tpl.ts', obj: 'ComponentTemplate' }

But I really expected the code in the foreach statement:

this.container.bind<ITemplate>(template.obj).to(mod.default);

to be equivalent to this:

this.container.bind<HtmlTemplate>('HtmlTemplate').to(HtmlTemplate);
this.container.bind<ComponentTemplate>('ComponentTemplate').to(ComponentTemplate);

but when I try to resolve it in an other loop:

this.templates.forEach(template => {
    const tpl = this.container.get<ITemplate>(template.obj);
...

it throws an error:

Error: No matching bindings found for serviceIdentifier HtmlTemplate

Anyone know how to solve this?

回答1:

The code has problems with control flow. There are promises that aren't chained, which is antipattern. This results in inefficient error handling and race conditions.

Each and every promise should be chained. The use of forEach is discouraged in ES6 for several reasons, one of them is that it requires additional actions to work with promises and doesn't work well with generators and async functions. The code can take most of async functions and be refactored to make control flow clean and efficient:

async bind(){
    for (const template of this.templates)
       const mod = await import(template.file);
       this.container.bind<ITemplate>(template.obj).to(mod.default);
    }
}

The code that uses bind should chain it and avoid promise construction antipatterns:

async bind() {
    // binding for when the widget is needed;
    for (const component of this.components)
        component.widget = this.container.get<Widget>(component.name);
        if(component.widget) {
            await component.widget.configure();
            await component.widget.bind();
        } else {
            console.error(`widget ${component.name} not resolved`);
        }
    });

    return this;
}

A more efficient way is to discard asynchronous initialization routine, because the only thing that requires it is dynamic import(). import() promises can be replaced with synchronous require statements, import() falls back to require in Node.js any way.