tc39 / proposal-dynamic-import

import() proposal for JavaScript

Home Page:https://tc39.github.io/proposal-dynamic-import/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

export a promise as module to help import() resolving the dep chain in child import()s

xxoo opened this issue · comments

commented

the module statement in 2.js is like the cmd module.exports = something or amd return something to export a top level value.

in this case when import() received a promise as module, it will keep waiting till the promise chain is resolved or any of them is rejected

1.js

import('./2.js').then(m => console.log(m)); //will be: 2.js with 3.js and 4.js

2.js

module Promise.all([import('./3.js'), import('./4.js')])
  .then(([m3, m4]) => '2.js with ' + m3.m3 + ' and ' + m4.m4));

3.js

export let m3 = '3.js';

4.js

export let m4 = '4.js';

Edit: I may have confused the imported value: module binding / module record, see other replies)

I suppose this is the expected behavior due to the semantics of promises: the value passed to the .then handler is never a promise.
This effectively means that you cannot import promises from other modules (they will always be resolved).

I'd say that it is better to keep the current promise semantics.
A less confusing workaround to changing the semantics is to just use a function instead of exporting a bare promise:

2.js

export default async function() {
  [m3, m4] = await Promise.all([import("./3.js"), import("./4.js")]);
  return `2.js with ${m3.m3} and ${m4.m4}`;
}

You can use it as:

1.js

import("./2.js")
  .then(m2 =>{
    const p = m2.default();
    console.log(`pending promise`);
    return p;
  })
  .then(result => console.log(result)); // 2.js with 3.js and 4.js

Basically, if a module exports a Promise, it means that is not usable until this promise is resolved. If you should be able to use before, then let the consumer create the promise when he actually needs it.

commented

@demurgos thanks for pointing out. but i mean import() should wait only if the whole module is a promise.
that's not really necessary but can make your code more clear.

I think what you are looking for is top-level await.

commented

@matthewp sure, that's another good thing to have. but i guess async functions may lead performance issue or we don't need the keyword otherwise. making all top level scope async might be risky at this time.
in this case perhaps we don't really have to use async functions. always write one more then(v => v.default) after import() and always use export default should be enough. like
1.js

import('./2.js').then(v => v.default).then(m => console.log(m));

2.js

export default Promise.all([
  import('./3.js').then(v => v.default),
  import('./4.js').then(v => v.default)
]).then(([m3, m4]) => `2.js with ${m3} and ${m4}`);

3.js

export default '3.js';

4.js

export default '4.js';
commented

@xxoo Actually, there's a pretty good chance that async/await can be optimized so that it's faster than using Promises directly.

The reason for the keyword is to indicate which function calls are asynchronous and which are not, because that affects the behavior of the functions. It's done for semantic reasons, not performance reasons.

import() gives you a Promise for a ModuleRecord, not the default export. Unless you have a named export “then”, that’s a function, exported promises won’t be implicitly awaited; they’ll just be properties on the ModuleRecord.

commented

@Pauan that's really good to know. but i didn't compare async functions to promises, i did compare them to normal functions. if we want to make a top level await call, we need the top level scope to be async. i just wonder, if there is no issue, why that's not done by default.

This thread does not appear to be a bug report with the specification, so let me close it. If people still want to continue discussing things in the closed thread, that's fine.

commented

@ljharb good god u are the man! problem solved!
the ModuleRecord does't need to be a promise, it just need to be thenable.

1.js

import("./2.js").then(m => console.log(m)); //2.js with 3.js and 4.js

2.js

export function then (resolve, reject) {
  Promise.all([import('./3.js'), import('./4.js')])
    .then(([m3, m4]) => resolve(`2.js with ${m3.m3} and ${m4.m4}`));
}

3.js

export let m3 = '3.js';

4.js

export let m4 = '4.js';
commented

@domenic it was just a suggestion or feature request. but not any more. thanks you and any one who watched it. may your code be strong clear and fast.