41

I've tried searching the internet for the execution order of imported modules. For instance, let's say I have the following code:

import "one"
import "two"
console.log("three");

Where one.js and two.js are defined as follows:

// one.js
console.log("one");

// two.js
console.log("two");

Is the console output guaranteed to be:

one
two
three

Or is it undefined?

McMath
  • 5,426
  • 2
  • 21
  • 29
Max
  • 13,084
  • 12
  • 69
  • 109
  • import is sync, so the output order is guaranteed. the console showing stuff is technically async, but that doesn't matter because it's buffered. – dandavis Feb 22 '16 at 11:05
  • 1
    Regardless of the answer, the rule of thumb is: Whenever you require a certain evaluation order, **explicitly declare your dependencies with an `import`**. – Bergi Feb 23 '16 at 20:56

1 Answers1

32

JavaScript modules are evaluated asynchronously. However, all imports are evaluated prior to the body of module doing the importing. This makes JavaScript modules different from CommonJS modules in Node or <script> tags without the async attribute. JavaScript modules are closer to the AMD spec when it comes to how they are loaded. For more detail, see section 16.6.1 of Exploring ES6 by Axel Rauschmayer.

Thus, in the example provided by the questioner, the order of execution cannot be guaranteed. There are two possible outcomes. We might see this in the console:

one
two
three

Or we might see this:

two
one
three

In other words, the two imported modules could execute their console.log() calls in any order; they are asynchronous with respect to one another. But they will definitely be executed prior to the body of the module that imports them, so "three" is guaranteed to be logged last.

The asynchronicity of modules can be observed when using top-level await statements (now implemented in Chrome). For example, suppose we modify the questioner's example slightly:

// main.js
import './one.js';
import './two.js';
console.log('three');

// one.js
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('one');

// two.js
console.log('two');

When we run main.js, we see the following in the console (with timestamps added for illustration):

[0s] two
[1s] one
[1s] three
McMath
  • 5,426
  • 2
  • 21
  • 29
  • 3
    Any reference on "Imported ES6 modules are executed asynchronously."? – Benjamin Gruenbaum Feb 23 '16 at 13:47
  • 2
    As far as I know, ES2015 modules are _not_ imported asynchronously, rather - how they load is up to the module loader entirely. – Benjamin Gruenbaum Feb 23 '16 at 13:48
  • 1
    @BenjaminGruenbaum I can only quote my own answer: "For more detail, see [section 16.6.1](http://exploringjs.com/es6/ch_modules.html#sec_modules-in-browsers) of Exploring ES6 by Axel Rauschmayer." – McMath Feb 23 '16 at 13:49
  • 2
    @BenjaminGruenbaum Yes, that's why I add the caveat that "no modern browser implements ES6 modules. I don't know if transpilers such as Babel follow the original specification in this respect." I suspect that most such transpilers do import synchronously, as you suggest. But my answer is about the original specification. – McMath Feb 23 '16 at 13:53
  • 2
    I'm actually a reviewer of that book. The point I'm making is that it's not up to the ES module spec to decide it - it's up to the module _loaded_ spec to decide how to load the modules. IIRC the spec only requires that all the modules are loaded when code executes. – Benjamin Gruenbaum Feb 23 '16 at 14:13
  • @BenjaminGruenbaum OK, it sounds like you know more about this than I do. Feel free to provide your own answer. I'm happy to take this one down once I'm shown to be mistaken. – McMath Feb 23 '16 at 14:18
  • 1
    I don't want you to take it down, I'm not even 100% sure about it myself. Your answer is _almost_ right except the part that dictates how it works in browsers should indicate it happens this way because of the browser module loader spec and not the ES spec. Node for example is not bound by it. – Benjamin Gruenbaum Feb 23 '16 at 14:19
  • @BenjaminGruenbaum I'm pretty sure the intention is to allow asynchronous and concurrent resolution/loading/initialisation of modules (also see [this wording](http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-initialization)). I have to admit though that the spec is written in a pretty synchronous style (especially the [module evaluation](https://esdiscuss.org/topic/negative-indexes)) which is all part of a single TopLevelModuleEvaluationJob. It's even pretty sequential (with a well-defined order), given that the *[[RequestedModules]]* list is iterated. So… – Bergi Feb 23 '16 at 20:53
  • We would have to assume that [HostResolveImportedModule](http://www.ecma-international.org/ecma-262/6.0/#sec-hostresolveimportedmodule) is asynchronous (like: wait an implementation-defined time, similar to how XHR is specced) during which other job queues can run, and that it either recursively schedules module evaluation jobs or just calls the module evaluation method of the loaded modules as soon as there are no more unresolved dependencies. – Bergi Feb 23 '16 at 20:53
  • @Bergi Thanks for your contribution. The spec still seems ambiguous to me on this question. I'm not sure I quite understand why HostResolveImportedModule must be asynchronous; I get the impression that it would be implementation-dependent. It may just be that I am in over my head on this. In any case, I invite you to make or suggest any edits, and I will gladly yield to a better answer if you have one. – McMath Feb 23 '16 at 21:13
  • If modules wouldn't be executed in import order (within a single module, not across multiple html script tags) then polyfill libraries like core-js would not work reliably. I think this answer is a bit misleading, even though I don't have a good reference in the spec to prove it. – letmaik Mar 19 '21 at 17:15
  • @letmaik All imports are executed before the rest of the code in a file, so a polyfill library (or any other library) will have fully executed by time you use it. The only scenario that would be a problem would be if you were to import `core-js`, followed by `some-other-module`, which depends on `core-js`. But in that case, `some-other-module` should *itself* import `core-js`. That said, this is an old answer. Now that browsers have in fact implemented modules, it's possible that imports are executed consecutively in practice. I should probably check and update the answer. – McMath Mar 22 '21 at 09:17