tc39 / proposal-dynamic-import

import() proposal for JavaScript
https://tc39.github.io/proposal-dynamic-import/
MIT License
1.86k stars 47 forks source link

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

Closed xxoo closed 6 years ago

xxoo commented 6 years ago

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';
demurgos commented 6 years ago

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.

xxoo commented 6 years ago

@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.

matthewp commented 6 years ago

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

xxoo commented 6 years ago

@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';
Pauan commented 6 years ago

@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.

ljharb commented 6 years ago

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.

xxoo commented 6 years ago

@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.

domenic commented 6 years ago

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.

xxoo commented 6 years ago

@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';
xxoo commented 6 years ago

@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.