├── .editorconfig ├── .gitattributes ├── .github └── workflows │ └── publish.yml ├── .gitignore ├── .nvmrc ├── LICENSE ├── README.md ├── img └── module-graph-cycle-async.svg ├── package.json └── spec.html /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{README.md,package.json,spec.html,.travis.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Automatically normalize line endings for all text-based files 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build-deploy: 10 | runs-on: ubuntu-18.04 11 | steps: 12 | - uses: actions/checkout@v1 13 | 14 | - name: Setup Node 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: '12.x' 18 | 19 | - run: npm install 20 | - run: npm run build 21 | 22 | - name: Deploy 23 | uses: peaceiris/actions-gh-pages@v2 24 | env: 25 | ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} 26 | PUBLISH_BRANCH: gh-pages 27 | PUBLISH_DIR: ./dist 28 | with: 29 | username: "ghpages auto deploy" 30 | useremail: "myles.borins@gmail.com" 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | 3 | # Installed npm modules 4 | package-lock.json 5 | node_modules 6 | 7 | # Folder view configuration files 8 | .DS_Store 9 | Desktop.ini 10 | 11 | # Thumbnail cache files 12 | ._* 13 | Thumbs.db 14 | 15 | # Files that might appear on external disks 16 | .Spotlight-V100 17 | .Trashes 18 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ECMAScript proposal: Top-level `await` 2 | 3 | Champions: Myles Borins, Yulia Startsev. 4 | 5 | Authors: Myles Borins, Yulia Startsev, Daniel Ehrenberg, Guy Bedford, Ms2ger, and others. 6 | 7 | Status: Stage 4 8 | 9 | ## Synopsis 10 | 11 | Top-level `await` enables modules to act as big async functions: With top-level `await`, ECMAScript Modules (ESM) can `await` resources, causing other modules who `import` them to wait before they start evaluating their body. 12 | 13 | ## Motivation 14 | 15 | ### Limitations on IIAFEs 16 | 17 | With `await` only available within `async` functions, a module can include an `await` in the code that executes at startup by factoring that code into an `async` function: 18 | 19 | ```mjs 20 | // awaiting.mjs 21 | import { process } from "./some-module.mjs"; 22 | let output; 23 | async function main() { 24 | const dynamic = await import(computedModuleSpecifier); 25 | const data = await fetch(url); 26 | output = process(dynamic.default, data); 27 | } 28 | main(); 29 | export { output }; 30 | ``` 31 | 32 | This pattern can also be immediately invoked. You could call this an Immediately Invoked Async Function Expression (IIAFE), as a play on [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) idiom. 33 | 34 | ```mjs 35 | // awaiting.mjs 36 | import { process } from "./some-module.mjs"; 37 | let output; 38 | (async () => { 39 | const dynamic = await import(computedModuleSpecifier); 40 | const data = await fetch(url); 41 | output = process(dynamic.default, data); 42 | })(); 43 | export { output }; 44 | ``` 45 | 46 | This pattern is appropriate for situations where loading a module is intended to schedule work that will happen some time later. However, the exports from this module may be accessed before this async function completes: If another module imports this one, it may see `output` as `undefined`, or it may see it after it's initialized to the return value of `process`, depending on when the access occurs! For example: 47 | 48 | ```mjs 49 | // usage.mjs 50 | import { output } from "./awaiting.mjs"; 51 | export function outputPlusValue(value) { return output + value; } 52 | 53 | console.log(outputPlusValue(100)); 54 | setTimeout(() => console.log(outputPlusValue(100), 1000); 55 | ``` 56 | 57 | ### Workaround: Export a Promise to represent initialization 58 | 59 | In the absence of this feature, it's possible to export a Promise from a module, and wait on that to know when its exports are ready. For example, the above module could be written as: 60 | 61 | ```mjs 62 | // awaiting.mjs 63 | import { process } from "./some-module.mjs"; 64 | let output; 65 | export default (async () => { 66 | const dynamic = await import(computedModuleSpecifier); 67 | const data = await fetch(url); 68 | output = process(dynamic.default, data); 69 | })(); 70 | export { output }; 71 | ``` 72 | 73 | Then, the module could be used as: 74 | 75 | ```mjs 76 | // usage.mjs 77 | import promise, { output } from "./awaiting.mjs"; 78 | export function outputPlusValue(value) { return output + value } 79 | 80 | promise.then(() => { 81 | console.log(outputPlusValue(100)); 82 | setTimeout(() => console.log(outputPlusValue(100), 1000); 83 | }); 84 | ``` 85 | 86 | However, this leaves us with a number of problems still: 87 | - Everyone has to learn about a particular protocol to find the right Promise to wait on the module being loaded 88 | - If you forget to apply the protocol, things might "just work" some of the time (due to the race being won in a certain way) 89 | - In a deep module hierarchy, the Promise needs to be explicitly threaded through each step of the chain. 90 | 91 | For example, here, we waited on the promise from `"./awaiting.mjs"` properly, but we forgot to re-export it, so modules that use our module may still run into the original race condition. 92 | 93 | ### Avoiding the race through significant additional dynamism 94 | 95 | To avoid the hazard of forgetting to wait for the exported Promise before accessing exports, a module could instead export a Promise which resolves to an object which contains exports 96 | 97 | ```mjs 98 | // awaiting.mjs 99 | import { process } from "./some-module.mjs"; 100 | export default (async () => { 101 | const dynamic = await import(computedModuleSpecifier); 102 | const data = await fetch(url); 103 | const output = process(dynamic.default, data); 104 | return { output }; 105 | })(); 106 | ``` 107 | 108 | ```mjs 109 | // usage.mjs 110 | import promise from "./awaiting.mjs"; 111 | 112 | export default promise.then(({output}) => { 113 | function outputPlusValue(value) { return output + value } 114 | 115 | console.log(outputPlusValue(100)); 116 | setTimeout(() => console.log(outputPlusValue(100), 1000); 117 | 118 | return { outputPlusValue }; 119 | }); 120 | ``` 121 | 122 | It's unclear whether this pattern has caught on, but it's sometimes [recommended in StackOverflow](https://stackoverflow.com/questions/42958334/how-can-i-export-promise-result/42958644#42958644) to people who face these sorts of issues. 123 | 124 | However, this pattern has the undesirable effect of requiring a broad reorganization of the related source into more dynamic patterns, and placing much of the module body inside the `.then()` callback in order to use the dynamically available imports. This represents a significant regression in terms of static analyzability, testability, ergonomics and more, compared to ES2015 modules. And when you run into a deep dependency which needs to `await`, you need to reorganize all dependent modules to use this pattern. 125 | 126 | ### Solution: Top-level `await` 127 | 128 | Top-level `await` lets us rely on the module system itself to handle all of these promises, and make sure that things are well-coordinated. The above example could be simply written and used as follows: 129 | 130 | ```mjs 131 | // awaiting.mjs 132 | import { process } from "./some-module.mjs"; 133 | const dynamic = import(computedModuleSpecifier); 134 | const data = fetch(url); 135 | export const output = process((await dynamic).default, await data); 136 | ``` 137 | 138 | ```mjs 139 | // usage.mjs 140 | import { output } from "./awaiting.mjs"; 141 | export function outputPlusValue(value) { return output + value } 142 | 143 | console.log(outputPlusValue(100)); 144 | setTimeout(() => console.log(outputPlusValue(100), 1000); 145 | ``` 146 | 147 | None of the statements in `usage.mjs` will execute until the `await`s in `awaiting.mjs` have had their Promises resolved, so the race condition is avoided by design. This is an extension of how, if `awaiting.mjs` didn't use top-level `await`, none of the statements in `usage.mjs` will execute until `awaiting.mjs` is loaded and all of its statements have executed. 148 | 149 | ## Use cases 150 | 151 | When would it make sense to have a module which waits on an asynchronous operation to load? This section gives some examples. 152 | 153 | ### Dynamic dependency pathing 154 | 155 | ```mjs 156 | const strings = await import(`/i18n/${navigator.language}`); 157 | ``` 158 | 159 | This allows for Modules to use runtime values in order to determine 160 | dependencies. This is useful for things like development/production splits, 161 | internationalization, environment splits, etc. 162 | 163 | ### Resource initialization 164 | 165 | ```mjs 166 | const connection = await dbConnector(); 167 | ``` 168 | 169 | This allows Modules to represent resources and also to produce errors in 170 | cases where the Module will never be able to be used. 171 | 172 | ### Dependency fallbacks 173 | 174 | ```mjs 175 | let jQuery; 176 | try { 177 | jQuery = await import('https://cdn-a.com/jQuery'); 178 | } catch { 179 | jQuery = await import('https://cdn-b.com/jQuery'); 180 | } 181 | ``` 182 | 183 | ### WebAssembly Modules 184 | 185 | WebAssembly Modules are "compiled" and "instantiated" in a logically asynchronous way, based on their imports: Some WebAssembly implementations do nontrivial work at either phase, which is important to be able to shunt off into another thread. To integrate with the JavaScript module system, they will need to do the equivalent of a top-level await. See the [WebAssembly ESM integration proposal](https://github.com/webassembly/esm-integration) for more details. 186 | 187 | ## Semantics as desugaring 188 | 189 | Currently, a module waits for all of its dependencies to execute all of their statements before the import is considered finished, and the module's code can run. This proposal maintains this property when introducing `await`, : dependencies still execute through to the end, even if you need to wait for that execution to finish asynchronously. One way to think of this is as if each module exported a Promise, and after all the `import` statements, but before the rest of the module, the Promises are all `await`ed: 190 | 191 | ```mjs 192 | import { a } from './a.mjs'; 193 | import { b } from './b.mjs'; 194 | import { c } from './c.mjs'; 195 | 196 | console.log(a, b, c); 197 | ``` 198 | 199 | would be roughly equivalent to 200 | 201 | ```mjs 202 | import { promise as aPromise, a } from './a.mjs'; 203 | import { promise as bPromise, b } from './b.mjs'; 204 | import { promise as cPromise, c } from './c.mjs'; 205 | 206 | export const promise = Promise.all([aPromise, bPromise, cPromise]).then(() => { 207 | 208 | console.log(a, b, c); 209 | 210 | }); 211 | ``` 212 | 213 | Modules `a.mjs`, `b.mjs`, and `c.mjs` would all execute in order up until the first await in each of them; we then wait on all of them to resume and finish evaluating before continuing. 214 | 215 | ### FAQ 216 | 217 | #### Isn't top-level `await` a footgun? 218 | 219 | If you have seen [the gist](https://gist.github.com/Rich-Harris/0b6f317657f5167663b493c722647221) you likely have heard this critique before. 220 | 221 | Some responses to some of the top concerns here: 222 | 223 | ##### Will top-level `await` cause developers to make their code block longer than it should? 224 | 225 | It's true that top-level `await` gives developers a new tool to make their code wait. Our hope is that proper developer education can ensure that the semantics of top-level `await` are well-understood, so that people know to use it just when they intend that importers should block on it. 226 | 227 | We've seen this work well in the past. For example, it's easy to write code with async/await that serializes two tasks that could be done in parallel, but a deliberate developer education effort has popularized the use of `Promise.all` to avoid this hazard. 228 | 229 | ##### Will top-level `await` encourage developers to use `import()` unnecessarily, which is less optimizable? 230 | 231 | Many JavaScript developers are learning about `import()` specifically as a tool for code splitting. People are becoming aware of the relationship between bundling and multiple requests, and learning how to combine them for good application performance. Top-level `await` doesn't really change the calculus--using `import()` from a top-level `await` will have similar performance effects to using it from a function. As long as we can tie top-level `await`'s educational materials into the existing knowledge of that performance tradeoff, we hope to be able to avoid counterproductive increases in the use of `import()`. 232 | 233 | #### What exactly is blocked by a top-level `await`? 234 | 235 | When one module imports another one, the importing module will only start executing its module body once the dependency's body has finished executing. If the dependency reaches a top-level await, that will have to complete before the importing module's body starts executing. 236 | 237 | #### Why doesn't top-level `await` block the import of an adjacent module? 238 | 239 | If one module wants to declare itself dependent on another module, for the purposes of waiting for that other module to complete its top-level `await` statements before the module body executes, it can declare that other module as an import. 240 | 241 | In a case such as the following, the printed order will be `"X1"`, `"Y"`, `"X2"`, because importing one module "before" another does not create an implicit dependency. 242 | 243 | ```mjs 244 | // x.mjs 245 | console.log("X1"); 246 | await new Promise(r => setTimeout(r, 1000)); 247 | console.log("X2"); 248 | ``` 249 | 250 | ```mjs 251 | // y.mjs 252 | console.log("Y"); 253 | ``` 254 | 255 | ```mjs 256 | // z.mjs 257 | import "./x.mjs"; 258 | import "./y.mjs"; 259 | ``` 260 | 261 | Dependencies are required to be explicitly noted in order to boost the potential for parallelism: Most setup work that will be blocking due to a top-level await (for example, all of the case studies above) can be done in parallel with other setup work from unrelated modules. When some of this work may be highly parallelizable (e.g., network fetches), it's important to get as many of these queued up close to the start of execution as possible. 262 | 263 | #### What is guaranteed about code execution order? 264 | 265 | Modules maintain the same ordering as in ES2015 for when they start executing. If a module reaches an `await`, it will yield control and let other modules initialize themselves in the same well-specified order. 266 | 267 | To be specific: Regardless of whether top-level `await` is used, modules always initially start running in the same post-order traversal established in ES2015: execution of module bodies starts with the deepest imports, in the order that the import statements for them are reached. After a top-level `await` is reached, control is passed to start the next module in this traversal order, or to other asynchronously scheduled code. 268 | 269 | #### Do these guarantees meet the needs of polyfills? 270 | 271 | Currently (in a world without top-level `await`), polyfills are synchronous. So, the idiom of importing a polyfill (which modifies the global object) and then importing a module which should be affected by the polyfill will still work if top-level `await` is added. However, if a polyfill includes a top-level `await`, it will need to be imported by modules that depend on it in order to reliably take effect. 272 | 273 | #### Does the `Promise.all` happen even if none of the imported modules have a top-level `await`? 274 | 275 | If the module's execution is deterministically synchronous (that is, if it and its dependencies each contain no top-level `await`), there will be no entry in the `Promise.all` for that module. In this case, it will run synchronously. 276 | 277 | These semantics preserve the current behavior of ES Modules, where, when top-level `await` is not used, the Evaluate phase is entirely synchronous. The semantics are a bit in contrast with uses of Promises elsewhere. For a concrete example and further discussion, see [issue #43](https://github.com/tc39/proposal-top-level-await/issues/43) and [#47](https://github.com/tc39/proposal-top-level-await/issues/47). 278 | 279 | #### How exactly are dependencies waited on? Does it really use `Promise.all`? 280 | 281 | The semantics of modules without top-level `await` is synchronous: the whole tree executes in postorder, with a module running just after its dependencies have run. The same semantics apply to modules with top-level `await`: Once the module which contains top-level `await` has executed, it will trigger the synchronous execution of dependent modules whose dependencies have all executed. If a module does contain top-level `await`, even if the `await` is not dynamically reached, the whole module will be treated as "asynchronous", as if it were a big async function. Therefore, anything that runs when it's done is in a Promise reaction. However, from there, if there are multiple modules which are dependent on it, and these do not contain top-level await, then they will run synchronously, *without* any Promise work between them. 282 | 283 | #### Does top-level `await` increase the risk of deadlocks? 284 | 285 | Top-level `await` creates a new mechanism for deadlocks, but the champions of this proposal consider the risk to be worth it, because: 286 | - There are many existing ways that modules can create deadlocks or otherwise halt progress, and developer tools can help in debugging them 287 | - All deterministic deadlock prevention strategies considered would be overly broad and block appropriate, realistic, useful patterns 288 | 289 | ##### Existing Ways to block progress 290 | 291 | ###### Infinite Loops 292 | 293 | ```mjs 294 | for (const n of primes()) { 295 | console.log(`${n} is prime}`); 296 | } 297 | ``` 298 | 299 | Infinite series or lack of base condition means static control structures 300 | are vulnerable to infinite looping. 301 | 302 | ###### Infinite Recursion 303 | 304 | ```mjs 305 | const fibb = n => (n ? fibb(n - 1) : 1); 306 | fibb(Infinity); 307 | ``` 308 | 309 | Proper tail calls allow for recursion to never overflow the stack. This makes 310 | it vulnerable to infinite recursion. 311 | 312 | ###### Atomics.wait 313 | 314 | ```mjs 315 | Atomics.wait(shared_array_buffer, 0, 0); 316 | ``` 317 | 318 | Atomics allow blocking forward progress by waiting on an index that never changes. 319 | 320 | ###### `export function then` 321 | 322 | ```mjs 323 | // a 324 | export function then(f, r) {} 325 | ``` 326 | 327 | ```mjs 328 | async function start() { 329 | const a = await import('a'); 330 | console.log(a); 331 | } 332 | ``` 333 | 334 | Exporting a `then` function allows blocking `import()`. 335 | 336 | ###### Conclusion: Ensuring continued progress is a larger problem 337 | 338 | ##### Rejected deadlock prevention mechanisms 339 | 340 | A potential problem space to solve for in designing top level await is to aid in detecting and preventing forms of deadlock that can occur. For example awaiting on a cyclical dynamic import could introduce deadlock into the module graph execution. 341 | 342 | The following sections about deadlock prevention will be based on this code example: 343 | 344 | ```jsx 345 | // file.html 346 | 347 | 348 | // a.mjs 349 | await import("./b.mjs"); 350 | 351 | // b.mjs 352 | await import("./a.mjs"); 353 | ``` 354 | 355 | ###### Alternative: Return a partially filled module record 356 | 357 | In `b.mjs`, resolve the Promise immediately, even though `a.mjs` has not yet completed, to avoid a deadlock. 358 | 359 | ###### Alternative: Throw an exception on use of in-progress modules 360 | 361 | In `b.mjs`, reject the Promise when importing `a.mjs` because that module hasn't completed yet, to prevent a deadlock. 362 | 363 | ###### Case study: Race to `import()` a module 364 | 365 | Both of these strategies fall over when considering that multiple pieces of code may want to dynamically import the same module. Such multiple imports would not ordinarily be any sort of race or deadlock to worry about. However, neither of the above mechanisms would handle the situation well: One would reject the Promise, and the other would fail to wait for the imported module to be initialized. 366 | 367 | ###### Conclusion: No feasible strategy for deadlock avoidance 368 | 369 | #### Will top-level await work in transpilers? 370 | 371 | To the greatest extent possible. The widely deployed CommonJS (CJS) module system does not directly support top-level await, so any transpilation strategy targeting it will need adjustments. However, within this context, we've made several adjustments to the semantics of top-level await based on the feedback and experience of the authors of several JavaScript module systems, including transpiler authors. This proposal aims to be implementable in such contexts. 372 | 373 | #### Without this proposal, module graph execution is synchronous. Does this proposal maintain developer expectations that such loading be synchronous? 374 | 375 | To the greatest extent possible. When a module includes a top-level `await` (even if that `await` is not dynamically reached), this is not synchronous, and at the very least takes one trip through the Promise job queue. However, module subgraphs which do not use top-level await continue to run synchronously in exactly the same way as without this proposal. And if several modules which do not use top-level `await` depend on a module which does use it, then those modules will all run when the async module is ready, without yielding to any other work (neither the Promise job queue/microtask queue, nor the host's event loop, etc.). See [#74](https://github.com/tc39/proposal-top-level-await/pull/74) for details on the logic used. 376 | 377 | #### Should module loading include microtask checkpoints between modules, or yielding to the event loop after modules load? 378 | 379 | Maybe! These module loading questions are part of an exciting research area in loading performance, as well as an interesting discussion on the invariants surrounding microtask checkpoints. This proposal doesn't take an opinion on these questions, leaving the asynchronous behavior for separate proposals. Host environments may wrap modules in a way which does these things, and the top-level await specification machinery can be used to coordinate things. A future proposal either in TC39 or in a host environment could add additional microtask checkpoints. For related discussion, see [whatwg/html#4400](https://github.com/whatwg/html/issues/4400). 380 | 381 | #### Would top-level await work in web pages? 382 | 383 | Yes. The details of the integration into the HTML specification is proposed at [whatwg/html#4352](https://github.com/whatwg/html/pull/4352). 384 | 385 | ## History 386 | 387 | [The `async` / `await` proposal](https://github.com/tc39/ecmascript-asyncawait) was originally brought to committee in [January of 2014](https://github.com/tc39/tc39-notes/blob/master/meetings/2014-01/jan-30.md#asyncawait). In [April of 2014](https://github.com/tc39/tc39-notes/blob/master/meetings/2014-04/apr-10.md#preview-of-asnycawait) it was discussed that the keyword `await` should be reserved in the module goal for the purpose of top-level `await`. In [July of 2015](https://github.com/tc39/tc39-notes/blob/master/meetings/2015-07/july-30.md#64-advance-async-functions-to-stage-2) [the `async` / `await` proposal](https://github.com/tc39/ecmascript-asyncawait) advanced to Stage 2. During this meeting it was decided to punt on top-level `await` to not block the current proposal as top-level `await` would need to be "designed in concert with the loader". 388 | 389 | Since the decision to delay standardizing top-level `await` it has come up in a handful of committee discussions, primarily to ensure that it would remain possible in the language. 390 | 391 | In May 2018, this proposal reached Stage 2 in TC39's process, with many design decisions (in particular, whether to block "sibling" execution) left open to be discussed during Stage 2. 392 | 393 | ## Specification 394 | 395 | * [Ecmarkup source](https://github.com/tc39/proposal-top-level-await/blob/HEAD/spec.html) 396 | * [HTML version](https://tc39.github.io/proposal-top-level-await/) 397 | 398 | ## Implementations 399 | 400 | * [V8 v8.9](https://v8.dev/blog/v8-release-89) 401 | * SpiderMonkey via [`javascript.options.experimental.top_level_await`](https://bugzilla.mozilla.org/show_bug.cgi?id=1519100) flag 402 | * [JavaScriptCore](https://bugs.webkit.org/show_bug.cgi?id=202484) 403 | * [webpack 5.0.0](https://webpack.js.org/blog/2020-10-10-webpack-5-release/) 404 | 405 | ## References 406 | 407 | * https://github.com/bmeck/top-level-await-talking/ 408 | * https://gist.github.com/Rich-Harris/0b6f317657f5167663b493c722647221 409 | 410 | [defer]: https://jakearchibald.com/2017/es-modules-in-browsers/#defer-by-default 411 | -------------------------------------------------------------------------------- /img/module-graph-cycle-async.svg: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | A.js 30 | 31 | 32 | B.js 33 | 34 | 35 | C.js 36 | 37 | 38 | D.js 39 | 40 | 41 | E.js 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 69 | 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "test": ":", 5 | "prebuild": "mkdirp dist", 6 | "build": "ecmarkup --verbose spec.html dist/index.html --css dist/ecmarkup.css --js dist/ecmarkup.js", 7 | "watch": "npm run build -- --watch" 8 | }, 9 | "devDependencies": { 10 | "ecmarkup": "^3.12.0", 11 | "mkdirp": "^0.5.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
   4 | title: 'Top-Level Await'
   5 | status: proposal
   6 | stage: 3
   7 | location: https://tc39.github.io/proposal-top-level-await/
   8 | copyright: false
   9 | contributors: Myles Borins, Yulia Startsev, Daniel Ehrenberg, Guy Bedford, Ms2ger, and others.
  10 | 
11 | 12 | 13 | 14 | 15 |

Introduction

16 | 17 |

Top-Level Await allows the `await` keyword to be used at the top level of the module goal. See the explainer for the motivation, context, and high-level semantics.

18 |
19 | 20 | 21 |

AsyncFunctionStart ( _promiseCapability_, _asyncFunctionBody_ )

22 |

The abstract operation AsyncFunctionStart takes arguments _promiseCapability_ and _asyncFunctionBody_. It performs the following steps when called:

23 | 24 | 1. Let _runningContext_ be the running execution context. 25 | 1. Let _asyncContext_ be a copy of _runningContext_. 26 | 1. NOTE: Copying the execution state is required for the step belowAsyncBlockStart to resume its execution. It is ill-defined to resume a currently executing context. 27 | 1. Perform ! AsyncBlockStart(_promiseCapability_, _asyncFunctionBody_, _asyncContext_). 28 | 1. Set the code evaluation state of _asyncContext_ such that when evaluation is resumed for that execution context the following steps will be performed: 29 | 1. Let _result_ be the result of evaluating _asyncFunctionBody_. 30 | 1. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done. 31 | 1. Remove _asyncContext_ from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. 32 | 1. If _result_.[[Type]] is ~normal~, then 33 | 1. Perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, « *undefined* »). 34 | 1. Else if _result_.[[Type]] is ~return~, then 35 | 1. Perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, « _result_.[[Value]] »). 36 | 1. Else, 37 | 1. Assert: _result_.[[Type]] is ~throw~. 38 | 1. Perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « _result_.[[Value]] »). 39 | 1. Return. 40 | 1. Push _asyncContext_ onto the execution context stack; _asyncContext_ is now the running execution context. 41 | 1. Resume the suspended evaluation of _asyncContext_. Let _result_ be the value returned by the resumed computation. 42 | 1. Assert: When we return here, _asyncContext_ has already been removed from the execution context stack and _runningContext_ is the currently running execution context. 43 | 1. Assert: _result_ is a normal completion with a value of *undefined*. The possible sources of completion values are Await or, if the async function doesn't await anything, the step 4.g above. 44 | 1. Return. 45 | 46 |
47 | 48 | 49 |

AsyncBlockStart ( _promiseCapability_, _asyncBody_, _asyncContext_ )

50 | 51 | 1. Assert: _promiseCapability_ is a PromiseCapability Record. 52 | 1. Let _runningContext_ be the running execution context. 53 | 1. Set the code evaluation state of _asyncContext_ such that when evaluation is resumed for that execution context the following steps will be performed: 54 | 1. Let _result_ be the result of evaluating _asyncBody_. 55 | 1. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done. 56 | 1. Remove _asyncContext_ from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context. 57 | 1. If _result_.[[Type]] is ~normal~, then 58 | 1. Perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, « *undefined* »). 59 | 1. Else if _result_.[[Type]] is ~return~, then 60 | 1. Perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, « _result_.[[Value]] »). 61 | 1. Else, 62 | 1. Assert: _result_.[[Type]] is ~throw~. 63 | 1. Perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « _result_.[[Value]] »). 64 | 1. Return. 65 | 1. Push _asyncContext_ onto the execution context stack; _asyncContext_ is now the running execution context. 66 | 1. Resume the suspended evaluation of _asyncContext_. Let _result_ be the value returned by the resumed computation. 67 | 1. Assert: When we return here, _asyncContext_ has already been removed from the execution context stack and _runningContext_ is the currently running execution context. 68 | 1. Assert: _result_ is a normal completion with a value of *undefined*. The possible sources of completion values are Await or, if the async function doesn't await anything, the step 2.g above. 69 | 70 |
71 | 72 | 73 |

HostImportModuleDynamically ( _referencingScriptOrModule_, _specifier_, _promiseCapability_ )

74 |

HostImportModuleDynamically is a host-defined abstract operation that performs any necessary setup work in order to make available the module corresponding to the |ModuleSpecifier| String, _specifier_, occurring within the context of the script or module represented by the Script Record or Module Record _referencingScriptOrModule_. (_referencingScriptOrModule_ may also be *null*, if there is no active script or module when the `import()` expression occurs.) It then performs FinishDynamicImport to finish the dynamic import process.

75 |

The implementation of HostImportModuleDynamically must conform to the following requirements:

76 | 77 | 112 | 113 |

The actual process performed is host-defined, but typically consists of performing whatever I/O operations are necessary to allow HostResolveImportedModule to synchronously retrieve the appropriate Module Record, and then calling its Evaluate concrete method. This might require performing similar normalization as HostResolveImportedModule does.

114 |
115 | 116 | 117 |

FinishDynamicImport ( _referencingScriptOrModule_, _specifier_, _promiseCapability_, _completion__innerPromise_ )

118 |

The abstract operation FinishDynamicImport takes arguments _referencingScriptOrModule_, _specifier_, _promiseCapability_ (a PromiseCapability Record), and _innerPromise_. FinishDynamicImport completes the process of a dynamic import originally started by an `import()` call, resolving or rejecting the promise returned by that call as appropriate according to _innerPromise_. It is performed by host environments as part of HostImportModuleDynamically. It performs the following steps when called:

119 | 120 | 1. If _completion_ is an abrupt completion, then perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « _completion_.[[Value]] »). 121 | 1. Else, 122 | 1. Assert: _completion_ is a normal completion and _completion_.[[Value]] is *undefined*. 123 | 1. Let _moduleRecord_ be ! HostResolveImportedModule(_referencingScriptOrModule_, _specifier_). 124 | 1. Assert: Evaluate has already been invoked on _moduleRecord_ and successfully completed. 125 | 1. Let _namespace_ be GetModuleNamespace(_moduleRecord_). 126 | 1. If _namespace_ is an abrupt completion, perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « _namespace_.[[Value]] »). 127 | 1. Else, perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, « _namespace_.[[Value]] »). 128 | 1. Let _stepsFulfilled_ be the steps of a CallDynamicImportResolved function as specified below. 129 | 1. Let _onFulfilled_ be CreateBuiltinFunction(_stepsFulfilled_, « »). 130 | 1. Let _stepsRejected_ be the steps of a CallDynamicImportRejected function as specified below. 131 | 1. Let _onRejected_ be CreateBuiltinFunction(_stepsRejected_, « »). 132 | 1. Perform ! PerformPromiseThen(_innerPromise_.[[Promise]], _onFulfilled_, _onRejected_). 133 | 134 | 135 |

A CallDynamicImportResolved function is an anonymous built-in function with no internal slots and has one argument _result_. When a CallDynamicImportResolved function is called it performs the following steps:

136 | 137 | 1. Assert: _result_ is *undefined*. 138 | 1. Let _moduleRecord_ be ! HostResolveImportedModule(_referencingScriptOrModule_, _specifier_). 139 | 1. Assert: Evaluate has already been invoked on _moduleRecord_ and successfully completed. 140 | 1. Let _namespace_ be GetModuleNamespace(_moduleRecord_). 141 | 1. If _namespace_ is an abrupt completion, perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « _namespace_.[[Value]] »). 142 | 1. Else, perform ! Call(_promiseCapability_.[[Resolve]], *undefined*, « _namespace_.[[Value]] »). 143 | 144 | 145 |

A CallDynamicImportRejected function is an anonymous built-in function with no internal slots and has one argument _error_. When a CallDynamicImportRejected function is called it performs the following steps:

146 | 147 | 1. Perform ! Call(_promiseCapability_.[[Reject]], *undefined*, « _error_ »). 148 | 149 |
150 | 151 | 152 |

Abstract Module Records

153 | 154 | 155 | 156 | 157 | 160 | 163 | 166 | 167 | 168 | 169 | 170 | 173 | 176 | 179 | 180 | 181 | 184 | 187 | 190 | 191 | 192 | 195 | 198 | 201 | 202 | 203 | 206 | 209 | 212 | 213 | 214 |
158 | Field Name 159 | 161 | Value Type 162 | 164 | Meaning 165 |
171 | [[Realm]] 172 | 174 | Realm Record 175 | 177 | The Realm within which this module was created. 178 |
182 | [[Environment]] 183 | 185 | module Environment Record | ~empty~ 186 | 188 | The Environment Record containing the top level bindings for this module. This field is set when the module is linked. 189 |
193 | [[Namespace]] 194 | 196 | Object | ~empty~ 197 | 199 | The Module Namespace Object () if one has been created for this module. 200 |
204 | [[HostDefined]] 205 | 207 | Any, default value is *undefined*. 208 | 210 | Field reserved for use by host environments that need to associate additional information with a module. 211 |
215 |
216 | 217 | 218 | 219 | 220 | 221 | 224 | 227 | 228 | 229 | 232 | 235 | 236 | 237 | 240 | 244 | 245 | 246 | 249 | 252 | 253 | 254 | 257 | 261 | 262 | 263 |
222 | Method 223 | 225 | Purpose 226 |
230 | GetExportedNames(_exportStarSet_) 231 | 233 | Return a list of all names that are either directly or indirectly exported from this module. 234 |
238 | ResolveExport(_exportName_, _resolveSet_) 239 | 241 |

Return the binding of a name exported by this module. Bindings are represented by a ResolvedBinding Record, of the form { [[Module]]: Module Record, [[BindingName]]: String }. If the export is a Module Namespace Object without a direct binding in any module, [[BindingName]] will be set to *"\*namespace\*"*. Return *null* if the name cannot be resolved, or *"ambiguous"* if multiple bindings were found.

242 |

Each time this operation is called with a specific _exportName_, _resolveSet_ pair as arguments it must return the same result if it completes normally.

243 |
247 | Link() 248 | 250 |

Prepare the module for evaluation by transitively resolving all module dependencies and creating a module Environment Record.

251 |
255 | Evaluate() 256 | 258 |

Returns a promise for the evaluation of this module and its dependencies, resolving on successful evaluation or if it has already been evaluated successfully, and rejecting for an evaluation error or If this module has already been evaluated successfully, return *undefined*; if it has already been evaluated unsuccessfully, throw the exception that was produced. Otherwise, Transitively evaluate all module dependencies of this module and then evaluate this module. If the promise is rejected, hosts are expected to handle the promise rejection and rethrow the evaluation error.

259 |

Link must have completed successfully prior to invoking this method.

260 |
264 |
265 |
266 | 267 | 268 |

Cyclic Module Records

269 |

A Cyclic Module Record is used to represent information about a module that can participate in dependency cycles with other modules that are subclasses of the Cyclic Module Record type. Module Records that are not subclasses of the Cyclic Module Record type must not participate in dependency cycles with Source Text Module Records.

270 |

In addition to the fields defined in Cyclic Module Records have the additional fields listed in

271 | 272 | 273 | 274 | 275 | 278 | 281 | 284 | 285 | 286 | 289 | 292 | 296 | 297 | 298 | 301 | 304 | 307 | 308 | 309 | 312 | 315 | 319 | 320 | 321 | 324 | 327 | 331 | 332 | 333 | 336 | 339 | 342 | 343 | 344 | 347 | 350 | 353 | 354 | 355 | 358 | 361 | 364 | 365 | 366 | 369 | 372 | 376 | 377 | 378 | 381 | 384 | 391 | 392 | 393 | 396 | 399 | 405 | 406 | 407 | 410 | 413 | 419 | 420 | 421 |
276 | Field Name 277 | 279 | Value Type 280 | 282 | Meaning 283 |
287 | [[Status]] 288 | 290 | ~unlinked~ | ~linking~ | ~linked~ | ~evaluating~ | ~evaluating-async~ | ~evaluated~ 291 | 293 | Initially ~unlinked~. Transitions to ~linking~, ~linked~, ~evaluating~, possibly ~evaluating-async~, ~evaluated~ (in that order) as the module progresses throughout its lifecycle. 294 | ~evaluating-async~ indicates this module is queued to execute on completion of its async dependencies or it is an async module that has been executed and is pending top-level completion. 295 |
299 | [[EvaluationError]] 300 | 302 | An abrupt completion | *undefined* 303 | 305 | A completion of type ~throw~ representing the exception that occurred during evaluation. *undefined* if no exception occurred or if [[Status]] is not ~evaluated~. 306 |
310 | [[DFSIndex]] 311 | 313 | Integer | ~empty~ 314 | 316 | Auxiliary field used during Link and Evaluate only. 317 | If [[Status]] is ~linking~ or ~evaluating~, this nonnegative number records the point at which the module was first visited during the depth-first traversal of the dependency graph. 318 |
322 | [[DFSAncestorIndex]] 323 | 325 | Integer | ~empty~ 326 | 328 | Auxiliary field used during Link and Evaluate only. 329 | If [[Status]] is ~linking~ or ~evaluating~, this is either the module's own [[DFSIndex]] or that of an "earlier" module in the same strongly connected component. 330 |
334 | [[RequestedModules]] 335 | 337 | List of String 338 | 340 | A List of all the |ModuleSpecifier| strings used by the module represented by this record to request the importation of a module. The List is source code occurrence ordered. 341 |
345 | [[CycleRoot]] 346 | 348 | Cyclic Module Record | ~empty~ 349 | 351 | The first visited module of the cycle, the root DFS ancestor of the strongly connected component. For a module not in a cycle this would be the module itself. 352 |
356 | [[Async]] 357 | 359 | Boolean 360 | 362 | Whether this module is individually asynchronous (for example, if it's a Source Text Module Record containing a top-level await). Having an asynchronous dependency does not make the module asynchronous. This field must not change after the module is parsed. 363 |
367 | [[AsyncEvaluation]] 368 | 370 | Boolean 371 | 373 | Whether this module is either itself async or has an asynchronous dependency. 374 | Note: The order in which this field is set is used to order queued executions, see . 375 |
379 | [[TopLevelCapability]] 380 | 382 | Promise Capability | ~empty~ 383 | 385 | 386 | If Evaluate() was called on this module, this field contains the Promise capability for that entire evaluation. 387 | It is used to settle the Promise object that is returned from the Evaluate() abstract method. 388 | This field will be ~empty~ for any dependencies of that module, unless a top-level Evaluate() has been initiated for any of those dependencies. 389 | 390 |
394 | [[AsyncParentModules]] 395 | 397 | List of Cyclic Module Records 398 | 400 | 401 | If this module or a dependency has [[Async]] *true*, and execution is in progress, this tracks the parent importers of this module for the top-level execution job. 402 | These parent modules will not start executing before this module has successfully completed execution. 403 | 404 |
408 | [[PendingAsyncDependencies]] 409 | 411 | Integer | ~empty~ 412 | 414 | 415 | This tracks the number of async dependency modules remaining to execute for this module if it has any asynchronous dependencies. 416 | A module with async dependencies will be executed when this field reaches 0 and there are no execution errors. 417 | 418 |
422 |
423 |

In addition to the methods defined in Cyclic Module Records have the additional methods listed in

424 | 425 | 426 | 427 | 428 | 431 | 434 | 435 | 436 | 439 | 442 | 443 | 444 | 447 | 451 | 452 | 453 |
429 | Method 430 | 432 | Purpose 433 |
437 | InitializeEnvironment() 438 | 440 | Initialize the Environment Record of the module, including resolving all imported bindings, and create the module's execution context. 441 |
445 | ExecuteModule( [ _promiseCapability_ ] ) 446 | 448 | Evaluate the module's code within its execution context. 449 | If this module has *true* in [[Async]], then a Promise Capability is passed as an argument, and the method is expected to resolve or reject the given capability. In this case, the method must not throw an exception, but instead reject the Promise Capability if necessary. 450 |
454 |
455 | 456 | 457 |

Link ( ) Concrete Method

458 |

The Link concrete method of a Cyclic Module Record implements the corresponding Module Record abstract method.

459 |

On success, Link transitions this module's [[Status]] from ~unlinked~ to ~linked~. On failure, an exception is thrown and this module's [[Status]] remains ~unlinked~.

460 |

This abstract method performs the following steps (most of the work is done by the auxiliary function InnerModuleLinking):

461 | 462 | 463 | 1. Let _module_ be this Cyclic Module Record. 464 | 1. Assert: _module_.[[Status]] is not ~linking~ or ~evaluating~. 465 | 1. Let _stack_ be a new empty List. 466 | 1. Let _result_ be InnerModuleLinking(_module_, _stack_, 0). 467 | 1. If _result_ is an abrupt completion, then 468 | 1. For each Cyclic Module Record _m_ in _stack_, do 469 | 1. Assert: _m_.[[Status]] is ~linking~. 470 | 1. Set _m_.[[Status]] to ~unlinked~. 471 | 1. Assert: _module_.[[Status]] is ~unlinked~. 472 | 1. Return _result_. 473 | 1. Assert: _module_.[[Status]] is ~linked~, ~evaluating-async~ or ~evaluated~. 474 | 1. Assert: _stack_ is empty. 475 | 1. Return *undefined*. 476 | 477 | 478 | 479 |

InnerModuleLinking ( _module_, _stack_, _index_ )

480 |

The abstract operation InnerModuleLinking takes arguments _module_ (a Cyclic Module Record), _stack_, and _index_. It is used by Link to perform the actual linking process for _module_, as well as recursively on all other modules in the dependency graph. The _stack_ and _index_ parameters, as well as a module's [[DFSIndex]] and [[DFSAncestorIndex]] fields, keep track of the depth-first search (DFS) traversal. In particular, [[DFSAncestorIndex]] is used to discover strongly connected components (SCCs), such that all modules in an SCC transition to ~linked~ together. It performs the following steps when called:

481 | 482 | 483 | 1. If _module_ is not a Cyclic Module Record, then 484 | 1. Perform ? _module_.Link(). 485 | 1. Return _index_. 486 | 1. If _module_.[[Status]] is ~linking~, ~linked~, ~evaluating-async~ or ~evaluated~, then 487 | 1. Return _index_. 488 | 1. Assert: _module_.[[Status]] is ~unlinked~. 489 | 1. Set _module_.[[Status]] to ~linking~. 490 | 1. Set _module_.[[DFSIndex]] to _index_. 491 | 1. Set _module_.[[DFSAncestorIndex]] to _index_. 492 | 1. Set _index_ to _index_ + 1. 493 | 1. Append _module_ to _stack_. 494 | 1. For each String _required_ that is an element of _module_.[[RequestedModules]], do 495 | 1. Let _requiredModule_ be ? HostResolveImportedModule(_module_, _required_). 496 | 1. Set _index_ to ? InnerModuleLinking(_requiredModule_, _stack_, _index_). 497 | 1. If _requiredModule_ is a Cyclic Module Record, then 498 | 1. Assert: _requiredModule_.[[Status]] is either ~linking~, ~linked~, ~evaluating-async~ or ~evaluated~. 499 | 1. Assert: _requiredModule_.[[Status]] is ~linking~ if and only if _requiredModule_ is in _stack_. 500 | 1. If _requiredModule_.[[Status]] is ~linking~, then 501 | 1. Set _module_.[[DFSAncestorIndex]] to min(_module_.[[DFSAncestorIndex]], _requiredModule_.[[DFSAncestorIndex]]). 502 | 1. Perform ? _module_.InitializeEnvironment(). 503 | 1. Assert: _module_ occurs exactly once in _stack_. 504 | 1. Assert: _module_.[[DFSAncestorIndex]] is less than or equal to _module_.[[DFSIndex]]. 505 | 1. If _module_.[[DFSAncestorIndex]] equals _module_.[[DFSIndex]], then 506 | 1. Let _done_ be *false*. 507 | 1. Repeat, while _done_ is *false*, 508 | 1. Let _requiredModule_ be the last element in _stack_. 509 | 1. Remove the last element of _stack_. 510 | 1. Assert: _requiredModule_ is a Cyclic Module Record. 511 | 1. Set _requiredModule_.[[Status]] to ~linked~. 512 | 1. If _requiredModule_ and _module_ are the same Module Record, set _done_ to *true*. 513 | 1. Return _index_. 514 | 515 |
516 |
517 | 518 | 519 |

Evaluate ( ) Concrete Method

520 |

The Evaluate concrete method of a Cyclic Module Record implements the corresponding Module Record abstract method.

521 |

Evaluate transitions this module's [[Status]] from ~linked~ to ~evaluating-async~ or~evaluated~.

522 |

If execution results in an exception, that exception is recorded in the [[EvaluationError]] field and rethrown by future invocations of Evaluate.

523 |

The Promise returned by Evaluate is allocated by the first invocation of Evaluate, and its capability is stored in the [[TopLevelCapability]] field. If execution results in an exception, the Promise is rejected. Future invocations of Evaluate return the same Promise.

524 |

This abstract method performs the following steps (most of the work is done by the auxiliary function InnerModuleEvaluation):

525 | 526 | 527 | 1. Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent. 528 | 1. Let _module_ be this Cyclic Module Record. 529 | 1. Assert: _module_.[[Status]] is ~linked~, ~evaluating-async~ or ~evaluated~. 530 | 1. If _module_.[[Status]] is ~evaluating-async~ or ~evaluated~, set _module_ to _module_.[[CycleRoot]]. 531 | 1. If _module_.[[TopLevelCapability]] is not ~empty~, then 532 | 1. Return _module_.[[TopLevelCapability]].[[Promise]]. 533 | 1. Let _stack_ be a new empty List. 534 | 1. Let _capability_ be ! NewPromiseCapability(%Promise%). 535 | 1. Set _module_.[[TopLevelCapability]] to _capability_. 536 | 1. Let _result_ be InnerModuleEvaluation(_module_, _stack_, 0). 537 | 1. If _result_ is an abrupt completion, then 538 | 1. For each Cyclic Module Record _m_ in _stack_, do 539 | 1. Assert: _m_.[[Status]] is ~evaluating~. 540 | 1. Set _m_.[[Status]] to ~evaluated~. 541 | 1. Set _m_.[[EvaluationError]] to _result_. 542 | 1. Assert: _module_.[[Status]] is ~evaluated~ and _module_.[[EvaluationError]] is _result_. 543 | 1. Return _result_. 544 | 1. Perform ! Call(_capability_.[[Reject]], *undefined*, « _result_.[[Value]] »). 545 | 1. Otherwise, 546 | 1. Assert: _module_.[[Status]] is ~evaluating-async~ or~evaluated~ and _module_.[[EvaluationError]] is ~empty~. 547 | 1. If _module_.[[AsyncEvaluation]] is *false*, then 548 | 1. Assert: _module_.[[Status]] is ~evaluated~. 549 | 1. Perform ! Call(_capability_.[[Resolve]], *undefined*, « *undefined* »). 550 | 1. Assert: _stack_ is empty. 551 | 1. Return *undefined*_capability_.[[Promise]]. 552 | 553 | 554 | 555 |

InnerModuleEvaluation ( _module_, _stack_, _index_ )

556 |

The abstract operation InnerModuleEvaluation takes arguments _module_ (a Source Text Module Record), _stack_, and _index_. It is used by Evaluate to perform the actual evaluation process for _module_, as well as recursively on all other modules in the dependency graph. The _stack_ and _index_ parameters, as well as _module_'s [[DFSIndex]] and [[DFSAncestorIndex]] fields, are used the same way as in InnerModuleLinking. It performs the following steps when called:

557 | 558 | 559 | 1. If _module_ is not a Cyclic Module Record, then 560 | 1. Let _promise_ be ! _module_.Evaluate(). 561 | 1. Assert: _promise_.[[PromiseState]] is not ~pending~. 562 | 1. If _promise_.[[PromiseState]] is ~rejected~, then 563 | 1. Return ThrowCompletion(_promise_.[[PromiseStateResult]]). 564 | 1. Return _index_. 565 | 1. If _module_.[[Status]] is ~evaluated~ or ~evaluating-async~, then 566 | 1. If _module_.[[EvaluationError]] is ~empty~, return _index_. 567 | 1. Otherwise, return _module_.[[EvaluationError]]. 568 | 1. If _module_.[[Status]] is ~evaluating~, return _index_. 569 | 1. Assert: _module_.[[Status]] is ~linked~. 570 | 1. Set _module_.[[Status]] to ~evaluating~. 571 | 1. Set _module_.[[DFSIndex]] to _index_. 572 | 1. Set _module_.[[DFSAncestorIndex]] to _index_. 573 | 1. Set _module_.[[PendingAsyncDependencies]] to 0. 574 | 1. Set _index_ to _index_ + 1. 575 | 1. Append _module_ to _stack_. 576 | 1. For each String _required_ that is an element of _module_.[[RequestedModules]], do 577 | 1. Let _requiredModule_ be ! HostResolveImportedModule(_module_, _required_). 578 | 1. NOTE: Link must be completed successfully prior to invoking this method, so every requested module is guaranteed to resolve successfully. 579 | 1. Set _index_ to ? InnerModuleEvaluation(_requiredModule_, _stack_, _index_). 580 | 1. If _requiredModule_ is a Cyclic Module Record, then 581 | 1. Assert: _requiredModule_.[[Status]] is either ~evaluating~, ~evaluating-async~ or ~evaluated~. 582 | 1. Assert: _requiredModule_.[[Status]] is ~evaluating~ if and only if _requiredModule_ is in _stack_. 583 | 1. If _requiredModule_.[[Status]] is ~evaluating~, then 584 | 1. Set _module_.[[DFSAncestorIndex]] to min(_module_.[[DFSAncestorIndex]], _requiredModule_.[[DFSAncestorIndex]]). 585 | 1. Otherwise, 586 | 1. Set _requiredModule_ to _requiredModule_.[[CycleRoot]]. 587 | 1. Assert: _requiredModule_.[[Status]] is ~evaluating-async~ or ~evaluated~. 588 | 1. If _requiredModule_.[[EvaluationError]] is not ~empty~, return _requiredModule_.[[EvaluationError]]. 589 | 1. If _requiredModule_.[[AsyncEvaluation]] is *true*, then 590 | 1. Set _module_.[[PendingAsyncDependencies]] to _module_.[[PendingAsyncDependencies]] + 1. 591 | 1. Append _module_ to _requiredModule_.[[AsyncParentModules]]. 592 | 1. Perform ? _module_.ExecuteModule(). 593 | 1. If _module_.[[PendingAsyncDependencies]] > 0 or _module_.[[Async]] is *true*, then 594 | 1. Assert: _module_.[[AsyncEvaluation]] is *false*. 595 | 1. Set _module_.[[AsyncEvaluation]] to *true*. 596 | 1. NOTE: The order in which [[AsyncEvaluation]] transitions to *true* is significant. (See ) 597 | 1. If _module_.[[PendingAsyncDependencies]] is 0, perform ! ExecuteAsyncModule(_module_). 598 | 1. Otherwise, perform ? _module_.ExecuteModule(). 599 | 1. Assert: _module_ occurs exactly once in _stack_. 600 | 1. Assert: _module_.[[DFSAncestorIndex]] is less than or equal to _module_.[[DFSIndex]]. 601 | 1. If _module_.[[DFSAncestorIndex]] equals _module_.[[DFSIndex]], then 602 | 1. Let _done_ be *false*. 603 | 1. Repeat, while _done_ is *false*, 604 | 1. Let _requiredModule_ be the last element in _stack_. 605 | 1. Remove the last element of _stack_. 606 | 1. Assert: _requiredModule_ is a Cyclic Module Record. 607 | 1. If _requiredModule_.[[AsyncEvaluation]] is *false*, set _requiredModule_.[[Status]] to ~evaluated~. 608 | 1. Otherwise, set _requiredModule_.[[Status]] to ~evaluating-async~. 609 | 1. If _requiredModule_ and _module_ are the same Module Record, set _done_ to *true*. 610 | 1. Set _requiredModule_.[[CycleRoot]] to _module_. 611 | 1. Return _index_. 612 | 613 | 614 |

A module is ~evaluating~ while it is being traversed by InnerModuleEvaluation. A module is ~evaluated~ on execution completion or ~evaluating-async~ during execution if it is an asynchronous module.

615 |
616 | 617 |

Any modules depending on a module of an async cycle when that cycle is not ~evaluating~ will instead depend on the execution of the root of the cycle via [[CycleRoot]]. This ensures that the cycle state can be treated as a single strongly connected component through its root module state.

618 |
619 |
620 | 621 | 622 |

ExecuteAsyncModule ( _module_ )

623 | 624 | 1. Assert: _module_.[[Status]] is ~evaluating~ or ~evaluating-async~. 625 | 1. Assert: _module_.[[Async]] is *true*. 626 | 1. Let _capability_ be ! NewPromiseCapability(%Promise%). 627 | 1. Let _stepsFulfilled_ be the steps of a CallAsyncModuleFulfilled function as specified below. 628 | 1. Let _onFulfilled_ be CreateBuiltinFunction(_stepsFulfilled_, « [[Module]] »). 629 | 1. Set _onFulfilled_.[[Module]] to _module_. 630 | 1. Let _stepsRejected_ be the steps of a CallAsyncModuleRejected function as specified below. 631 | 1. Let _onRejected_ be CreateBuiltinFunction(_stepsRejected_, « [[Module]] »). 632 | 1. Set _onRejected_.[[Module]] to _module_. 633 | 1. Perform ! PerformPromiseThen(_capability_.[[Promise]], _onFulfilled_, _onRejected_). 634 | 1. Perform ! _module_.ExecuteModule(_capability_). 635 | 1. Return. 636 | 637 | 638 |

A CallAsyncModuleFulfilled function is an anonymous built-in function with a [[Module]] internal slot. When a CallAsyncModuleFulfilled function is called that expects no arguments it performs the following steps:

639 | 640 | 1. Let _f_ be the active function object. 641 | 1. Let _module_ be _f_.[[Module]]. 642 | 1. Perform ! AsyncModuleExecutionFulfilled(_module_). 643 | 1. Return. 644 | 645 | 646 |

A CallAsyncModuleRejected function is an anonymous built-in function with a [[Module]] internal slot. When a CallAsyncModuleRejected function is called with argument _error_ it performs the following steps:

647 | 648 | 1. Let _f_ be the active function object. 649 | 1. Let _module_ be _f_.[[Module]]. 650 | 1. Perform ! AsyncModuleExecutionRejected(_module_, _error_). 651 | 1. Return. 652 | 653 |
654 | 655 | 656 |

GatherAsyncParentCompletions ( _module_, _execList_ )

657 | 658 | 1. For each Module _m_ of _module_.[[AsyncParentModules]], do 659 | 1. If _execList_ does not contain _m_ and _m_.[[CycleRoot]].[[EvaluationError]] is ~empty~, then 660 | 1. Assert: _m_.[[Status]] is ~evaluating-async~. 661 | 1. Assert: _m_.[[EvaluationError]] is ~empty~. 662 | 1. Assert: _m_.[[AsyncEvaluation]] is *true*. 663 | 1. Assert: _m_.[[PendingAsyncDependencies]] > 0. 664 | 1. Set _m_.[[PendingAsyncDependencies]] to _m_.[[PendingAsyncDependencies]] - 1. 665 | 1. If _m_.[[PendingAsyncDependencies]] is equal to 0, then 666 | 1. Append _m_ to _execList_. 667 | 1. If _m_.[[Async]] is *false*, perform ! GatherAsyncParentCompletions(_m_, _execList_). 668 | 1. Return *undefined*. 669 | 670 | 671 |

When an async execution for a root _module_ is fulfilled, this function determines the list of modules which are able to synchronously execute together on this completion, populating them in _execList_.

672 |
673 |
674 | 675 | 676 |

AsyncModuleExecutionFulfilled ( _module_ )

677 | 678 | 1. If _module_.[[Status]] is ~evaluated~, 679 | 1. Assert: _module_.[[EvaluationError]] is not ~empty~. 680 | 1. Return *undefined*. 681 | 1. Assert: _module_.[[Status]] is ~evaluating-async~. 682 | 1. Assert: _module_.[[AsyncEvaluation]] is *true*. 683 | 1. Assert: _module_.[[EvaluationError]] is ~empty~. 684 | 1. Set _module_.[[Status]] to ~evaluated~. 685 | 1. If _module_.[[TopLevelCapability]] is not ~empty~, then 686 | 1. Assert: _module_.[[CycleRoot]] is equal to _module_. 687 | 1. Perform ! Call(_module_.[[TopLevelCapability]].[[Resolve]], *undefined*, «*undefined*»). 688 | 1. Let _execList_ be a new empty List. 689 | 1. Perform ! GatherAsyncParentCompletions(_module_, _execList_). 690 | 1. Let _sortedExecList_ be a List of elements that are the elements of _execList_, in the order in which they had their [[AsyncEvaluation]] fields set to *true* in InnerModuleEvaluation. 691 | 1. Assert: All elements of _sortedExecList_ have their [[AsyncEvaluation]] field set to *true*, [[PendingAsyncDependencies]] field set to 0 and [[EvaluationError]] field set to *undefined*. 692 | 1. For each Module _m_ of _sortedExecList_, do 693 | 1. If _m_.[[Status]] is ~evaluated~, then 694 | 1. Assert: _m_.[[EvaluationError]] is not ~empty~. 695 | 1. Otherwise, if _m_.[[Async]] is *true*, then 696 | 1. Perform ! ExecuteAsyncModule(_m_). 697 | 1. Otherwise, 698 | 1. Let _result_ be _m_.ExecuteModule(). 699 | 1. If _result_ is an abrupt completion, 700 | 1. Perform ! AsyncModuleExecutionRejected(_m_, _result_.[[Value]]). 701 | 1. Otherwise, 702 | 1. Set _m_.[[Status]] to ~evaluated~. 703 | 1. If _m_.[[TopLevelCapability]] is not ~empty~, then 704 | 1. Assert: _m_.[[CycleRoot]] is equal to _m_. 705 | 1. Perform ! Call(_m_.[[TopLevelCapability]].[[Resolve]], *undefined*, «*undefined*»). 706 | 1. Return *undefined*. 707 | 708 |
709 | 710 | 711 |

AsyncModuleExecutionRejected ( _module_, _error_ )

712 | 713 | 1. If _module_.[[Status]] is ~evaluated~, 714 | 1. Assert: _module_.[[EvaluationError]] is not ~empty~. 715 | 1. Return *undefined*. 716 | 1. Assert: _module_.[[Status]] is ~evaluating-async~. 717 | 1. Assert: _module_.[[AsyncEvaluation]] is *true*. 718 | 1. Assert: _module_.[[EvaluationError]] is ~empty~. 719 | 1. Set _module_.[[EvaluationError]] to ThrowCompletion(_error_). 720 | 1. Set _module_.[[Status]] to ~evaluated~. 721 | 1. For each Module _m_ of _module_.[[AsyncParentModules]], do 722 | 1. Perform ! AsyncModuleExecutionRejected(_m_, _error_). 723 | 1. If _module_.[[TopLevelCapability]] is not ~empty~, then 724 | 1. Assert: _module_.[[CycleRoot]] is equal to _module_. 725 | 1. Perform ! Call(_module_.[[TopLevelCapability]].[[Reject]], *undefined*, «_error_»). 726 | 1. Return *undefined*. 727 | 728 |
729 |
730 |
731 | 732 | 733 |

Example Cyclic Module Record Graphs

734 | 735 |

This non-normative section gives a series of examples of the linking and evaluation of a few common module graphs, with a specific focus on how errors can occur.

736 | 737 |

First consider the following simple module graph:

738 | 739 | 740 | A module graph in which module A depends on module B, and module B depends on module C 741 | 742 | 743 |

Let's first assume that there are no error conditions. When a host first calls _A_.Link(), this will complete successfully by assumption, and recursively link modules _B_ and _C_ as well, such that _A_.[[Status]] = _B_.[[Status]] = _C_.[[Status]] = ~linked~. This preparatory step can be performed at any time. Later, when the host is ready to incur any possible side effects of the modules, it can call _A_.Evaluate(), which will complete successfully, returning a Promise resolving to undefined (again by assumption), recursively having evaluated first _C_ and then _B_. Each module's [[Status]] at this point will be ~evaluated~.

744 |

Consider then cases involving linking errors. If InnerModuleLinking of _C_ succeeds but, thereafter, fails for _B_, for example because it imports something that _C_ does not provide, then the original _A_.Link() will fail, and both _A_ and _B_'s [[Status]] remain ~unlinked~. _C_'s [[Status]] has become ~linked~, though.

745 | 746 |

Finally, consider a case involving evaluation errors. If InnerModuleEvaluation of _C_ succeeds but, thereafter, fails for _B_, for example because _B_ contains code that throws an exception, then the original _A_.Evaluate() will fail, returning a rejected Promise. The resulting exception will be recorded in both _A_ and _B_'s [[EvaluationError]] fields, and their [[Status]] will become ~evaluated~. _C_ will also become ~evaluated~ but, in contrast to _A_ and _B_, will remain without an [[EvaluationError]], as it successfully completed evaluation. Storing the exception ensures that any time a host tries to reuse _A_ or _B_ by calling their Evaluate() method, it will encounter the same exception. (Hosts are not required to reuse Cyclic Module Records; similarly, hosts are not required to expose the exception objects thrown by these methods. However, the specification enables such uses.)

747 | 748 |

The difference here between linking and evaluation errors is due to how evaluation must be only performed once, as it can cause side effects; it is thus important to remember whether evaluation has already been performed, even if unsuccessfully. (In the error case, it makes sense to also remember the exception because otherwise subsequent Evaluate() calls would have to synthesize a new one.) Linking, on the other hand, is side-effect-free, and thus even if it fails, it can be retried at a later time with no issues.

749 | 750 |

Now consider a different type of error condition:

751 | 752 | 753 | A module graph in which module A depends on a missing (unresolvable) module, represented by ??? 754 | 755 | 756 |

In this scenario, module _A_ declares a dependency on some other module, but no Module Record exists for that module, i.e. HostResolveImportedModule throws an exception when asked for it. This could occur for a variety of reasons, such as the corresponding resource not existing, or the resource existing but ParseModule throwing an exception when trying to parse the resulting source text. Hosts can choose to expose the cause of failure via the exception they throw from HostResolveImportedModule. In any case, this exception causes a linking failure, which as before results in _A_'s [[Status]] remaining ~unlinked~.

757 | 758 |

LastlyNow, consider a module graph with a cycle:

759 | 760 | 761 | A module graph in which module A depends on module B and C, but module B also depends on module A 762 | 763 | 764 |

Here we assume that the entry point is module _A_, so that the host proceeds by calling _A_.Link(), which performs InnerModuleLinking on _A_. This in turn calls InnerModuleLinking on _B_. Because of the cycle, this again triggers InnerModuleLinking on _A_, but at this point it is a no-op since _A_.[[Status]] is already ~linking~. _B_.[[Status]] itself remains ~linking~ when control gets back to _A_ and InnerModuleLinking is triggered on _C_. After this returns with _C_.[[Status]] being ~linked~ , both _A_ and _B_ transition from ~linking~ to ~linked~ together; this is by design, since they form a strongly connected component.

765 | 766 |

An analogous story occurs for the evaluation phase of a cyclic module graph, in the success case.

767 | 768 |

Now consider a case where _A_ has an linking error; for example, it tries to import a binding from _C_ that does not exist. In that case, the above steps still occur, including the early return from the second call to InnerModuleLinking on _A_. However, once we unwind back to the original InnerModuleLinking on _A_, it fails during InitializeEnvironment, namely right after _C_.ResolveExport(). The thrown *SyntaxError* exception propagates up to _A_.Link, which resets all modules that are currently on its _stack_ (these are always exactly the modules that are still ~linking~). Hence both _A_ and _B_ become ~unlinked~. Note that _C_ is left as ~linked~.

769 | 770 |

Finally, consider a case where _A_ has an evaluation error; for example, its source code throws an exception. In that case, the evaluation-time analog of the above steps still occurs, including the early return from the second call to InnerModuleEvaluation on _A_. However, once we unwind back to the original InnerModuleEvaluation on _A_, it fails by assumption. The exception thrown propagates up to _A_.Evaluate(), which records the error in all modules that are currently on its _stack_ (i.e., the modules that are still ~evaluating~) as well as via [[AsyncParentModules]], which form a chain for modules which contain or depend on top-level `await` through the whole dependency graph through the AsyncModuleExecutionRejected algorithm. Hence both _A_ and _B_ become ~evaluated~ and the exception is recorded in both _A_ and _B_'s [[EvaluationError]] fields, while _C_ is left as ~evaluated~ with no [[EvaluationError]].

771 | 772 |

Lastly, consider a module graph with a cycle, where all modules complete asynchronously:

773 | 774 | A module graph in which module A depends on module B and C, module B depends on module D, module C depends on module D and E, and module D depends on module A 775 | 776 |

Linking happens as before, and all modules end up with [[Status]] set to ~linked~.

777 | 778 |

779 | Calling _A_.Evaluate() triggers InnerModuleEvaluation on _A_, _B_, and _D_, which all transition to ~evaluating~. 780 | Then InnerModuleEvaluation is called on _A_ again, which is a no-op because it is already ~evaluating~. 781 | At this point, _D_.[[PendingAsyncDependencies]] is 0, so ExecuteAsyncModule(_D_) is called and triggers the execution promise for _D_. 782 | We unwind back to the InnerModuleEvaluation on _B_, setting _B_.[[PendingAsyncDependencies]] to 1 and _B_.[[AsyncEvaluation]] to *true*. 783 | We unwind back to the original InnerModuleEvaluation on _A_, setting _A_.[[PendingAsyncDependencies]] to 1. 784 | In the next iteration of the loop over _A_'s dependencies, we call InnerModuleEvaluation on _C_ and thus on _D_ (again a no-op) and _E_. 785 | As _E_ has no dependencies, ExecuteAsyncModule(_E_) is called, which triggers its execution promise. 786 | Because _E_ is not part of a cycle, it is immediately removed from the stack and transitions to ~evaluated~. 787 | We unwind once more to the original InnerModuleEvaluation on _A_, setting _C_.[[AsyncEvaluation]] to *true*. 788 | Now we finish the loop over _A_'s dependencies, set _A_.[[AsyncEvaluation]] to *true*, and remove the entire strongly connected component from the stack, transitioning all of the modules to ~evaluating-async~ at once. 789 | At this point, the fields of the modules are as given in . 790 |

791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 |
Module[[DFSIndex]][[DFSAncestorIndex]][[Status]][[AsyncEvaluation]][[AsyncParentModules]][[PendingAsyncDependencies]]
_A_00~evaluating-async~*true*« »2 (_B_ and _C_)
_B_10~evaluating-async~*true*« _A_ »1 (_D_)
_C_20~evaluating-async~*true*« _A_ »2 (_D_ and _E_)
_D_30~evaluating-async~*true*« _B_, _C_ »0
_E_44~evaluating-async~*true*« _C_ »0
853 |
854 | 855 |

856 | Let us assume that _E_ finishes executing first. 857 | When that happens, AsyncModuleExecutionFulfilled is called, _E_.[[Status]] is set to ~evaluated~ and _C_.[[PendingAsyncDependencies]] is decremented to become 1. 858 | The fields of the updated modules are as given in . 859 |

860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 |
Module[[DFSIndex]][[DFSAncestorIndex]][[Status]][[AsyncEvaluation]][[AsyncParentModules]][[PendingAsyncDependencies]]
_C_20~evaluating-async~*true*« _A_ »1 (_D_)
_E_44~evaluated~*true*« _C_ »0
895 |
896 | 897 |

898 | _D_ is next to finish (as it was the only module that was still executing). 899 | When that happens, AsyncModuleExecutionFulfilled is called again and _D_.[[Status]] is set to ~evaluated~. 900 | Then _B_.[[PendingAsyncDependencies]] is decremented to become 0, ExecuteAsyncModule is called on _B_, and its execution is triggered. 901 | _C_.[[PendingAsyncDependencies]] is also decremented to become 0, and _C_'s execution is triggered. 902 | The fields of the updated modules are as given in . 903 |

904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 |
Module[[DFSIndex]][[DFSAncestorIndex]][[Status]][[AsyncEvaluation]][[AsyncParentModules]][[PendingAsyncDependencies]]
_B_10~evaluating-async~*true*« _A_ »0
_C_20~evaluating-async~*true*« _A_ »0
_D_30~evaluated~*true*« _B_, _C_ »0
948 |
949 | 950 |

951 | Let us assume that _C_ finishes executing next. 952 | When that happens, AsyncModuleExecutionFulfilled is called again, _C_.[[Status]] is set to ~evaluated~ and _A_.[[PendingAsyncDependencies]] is decremented to become 1. 953 | The fields of the updated modules are as given in . 954 |

955 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 |
Module[[DFSIndex]][[DFSAncestorIndex]][[Status]][[AsyncEvaluation]][[AsyncParentModules]][[PendingAsyncDependencies]]
_A_00~evaluating-async~*true*« »1 (_B_)
_C_20~evaluated~*true*« _A_ »0
990 |
991 | 992 |

993 | Then, _B_ finishes executing. 994 | When that happens, AsyncModuleExecutionFulfilled is called again and _B_.[[Status]] is set to ~evaluated~. 995 | _A_.[[PendingAsyncDependencies]] is decremented to become 0, so ExecuteAsyncModule is called and _A_'s execution is triggered. 996 | The fields of the updated modules are as given in . 997 |

998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | 1028 | 1029 | 1030 | 1031 | 1032 |
Module[[DFSIndex]][[DFSAncestorIndex]][[Status]][[AsyncEvaluation]][[AsyncParentModules]][[PendingAsyncDependencies]]
_A_00~evaluating-async~*true*« »0
_B_10~evaluated~*true*« _A_ »0
1033 |
1034 | 1035 |

1036 | Finally, _A_ finishes executing. 1037 | When that happens, AsyncModuleExecutionFulfilled is called again and _A_.[[Status]] is set to ~evaluated~. 1038 | At this point, the Promise in _A_.[[TopLevelCapability]] (which was returned from _A_.Evaluate()) is resolved, and this concludes the handling of this module graph. 1039 | The fields of the updated module are as given in . 1040 |

1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 |
Module[[DFSIndex]][[DFSAncestorIndex]][[Status]][[AsyncEvaluation]][[AsyncParentModules]][[PendingAsyncDependencies]]
_A_00~evaluated~*true*« »0
1067 |
1068 | 1069 |

1070 | Alternatively, consider a failure case where _C_ fails execution and returns an error before _B_ has finished executing. When that happens, AsyncModuleExecutionRejected is called and _C_.[[Status]] is set to ~evaluated~. We then set the evaluation error of the module to the error returned by executing _C_. We then propagate this error to all of the AsyncParentModules by performing AsyncModuleExeutionRejected on each of them. The fields of the updated modules are as given in . 1071 |

1072 | 1073 | 1074 | 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 |
Module[[DFSIndex]][[DFSAncestorIndex]][[Status]][[AsyncEvaluation]][[AsyncParentModules]][[PendingAsyncDependencies]][[EvaluationError]]
_A_00~evaluated~*true*« »1 (_B_)~empty~
_C_21~evaluated~*true*« _A_ »_C_'s evaluation error
1109 |
1110 | 1111 |

1112 | _A_ will be rejected with the same error as _C_ since _C_ will call AsyncModuleExecutionRejected on _A_ with _C_'s error. _A_.[[Status]] is set to ~evaluated~. At this point the Promise in _A_.[[TopLevelCapability]] (which was returned from _A_.Evaluate()) is rejected. The fields of the updated module are as given in . 1113 |

1114 | 1115 | 1116 | 1117 | 1118 | 1119 | 1120 | 1121 | 1122 | 1123 | 1124 | 1125 | 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 |
Module[[DFSIndex]][[DFSAncestorIndex]][[Status]][[AsyncEvaluation]][[AsyncParentModules]][[PendingAsyncDependencies]][[EvaluationError]]
_A_00~evaluated~*true*« »0_C_'s Evaluation Error
1142 |
1143 | 1144 |

1145 | Then, _B_ finishes executing without an error. When that happens, AsyncModuleExecutionFulfilled is called again and _B_.[[Status]] is set to ~evaluated~. GatherAsyncParentCompletions is called on _B_. However, _A_.[[CycleRoot]] is _A_ which has an evaluation error, so it will not be added to the returned _sortedExecList_ and AsyncModuleExecutionFulfilled will return without further processing. Any future importer of _B_ will resolve the rejection of _B_.[[CycleRoot]].[[EvaluationError]] from the evaluation error from _C_ that was set on the cycle root _A_. The fields of the updated modules are as given in . 1146 |

1147 | 1148 | 1149 | 1150 | 1151 | 1152 | 1153 | 1154 | 1155 | 1156 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | 1177 | 1178 | 1179 | 1180 | 1181 | 1182 | 1183 | 1184 |
Module[[DFSIndex]][[DFSAncestorIndex]][[Status]][[AsyncEvaluation]][[AsyncParentModules]][[PendingAsyncDependencies]][[EvaluationError]]
_A_00~evaluated~*true*« »0_C_'s Evaluation Error
_B_10~evaluated~*true*« _A_ »0~empty~
1185 |
1186 | 1187 |
1188 | 1189 | 1190 |

Source Text Module Records

1191 | 1192 |

ParseModule ( _sourceText_, _realm_, _hostDefined_ )

1193 |

The abstract operation ParseModule takes arguments _sourceText_ (ECMAScript source text), _realm_, and _hostDefined_. It creates a Source Text Module Record based upon the result of parsing _sourceText_ as a |Module|. It performs the following steps when called:

1194 | 1195 | 1. Assert: _sourceText_ is an ECMAScript source text (see clause ). 1196 | 1. Parse _sourceText_ using |Module| as the goal symbol and analyse the parse result for any Early Error conditions. If the parse was successful and no early errors were found, let _body_ be the resulting parse tree. Otherwise, let _body_ be a List of one or more *SyntaxError* objects representing the parsing errors and/or early errors. Parsing and early error detection may be interweaved in an implementation-dependent manner. If more than one parsing error or early error is present, the number and ordering of error objects in the list is implementation-dependent, but at least one must be present. 1197 | 1. If _body_ is a List of errors, return _body_. 1198 | 1. Let _requestedModules_ be the ModuleRequests of _body_. 1199 | 1. Let _importEntries_ be ImportEntries of _body_. 1200 | 1. Let _importedBoundNames_ be ImportedLocalNames(_importEntries_). 1201 | 1. Let _indirectExportEntries_ be a new empty List. 1202 | 1. Let _localExportEntries_ be a new empty List. 1203 | 1. Let _starExportEntries_ be a new empty List. 1204 | 1. Let _exportEntries_ be ExportEntries of _body_. 1205 | 1. For each ExportEntry Record _ee_ in _exportEntries_, do 1206 | 1. If _ee_.[[ModuleRequest]] is *null*, then 1207 | 1. If _ee_.[[LocalName]] is not an element of _importedBoundNames_, then 1208 | 1. Append _ee_ to _localExportEntries_. 1209 | 1. Else, 1210 | 1. Let _ie_ be the element of _importEntries_ whose [[LocalName]] is the same as _ee_.[[LocalName]]. 1211 | 1. If _ie_.[[ImportName]] is *"\*"*, then 1212 | 1. NOTE: This is a re-export of an imported module namespace object. 1213 | 1. Append _ee_ to _localExportEntries_. 1214 | 1. Else, 1215 | 1. NOTE: This is a re-export of a single name. 1216 | 1. Append the ExportEntry Record { [[ModuleRequest]]: _ie_.[[ModuleRequest]], [[ImportName]]: _ie_.[[ImportName]], [[LocalName]]: *null*, [[ExportName]]: _ee_.[[ExportName]] } to _indirectExportEntries_. 1217 | 1. Else if _ee_.[[ImportName]] is *"\*"* and _ee_.[[ExportName]] is *null*, then 1218 | 1. Append _ee_ to _starExportEntries_. 1219 | 1. Else, 1220 | 1. Append _ee_ to _indirectExportEntries_. 1221 | 1. Let _async_ be _body_ Contains `await`. 1222 | 1. Return Source Text Module Record { [[Realm]]: _realm_, [[Environment]]: ~empty~, [[Namespace]]: ~empty~, [[CycleRoot]]: ~empty~, [[Async]]: _async_, [[AsyncEvaluation]]: *false*, [[TopLevelCapability]]: ~empty~, [[AsyncParentModules]]: « », [[PendingAsyncDependencies]]: ~empty~, [[Status]]: ~unlinked~, [[EvaluationError]]: ~empty~, [[HostDefined]]: _hostDefined_, [[ECMAScriptCode]]: _body_, [[Context]]: ~empty~, [[ImportMeta]]: ~empty~, [[RequestedModules]]: _requestedModules_, [[ImportEntries]]: _importEntries_, [[LocalExportEntries]]: _localExportEntries_, [[IndirectExportEntries]]: _indirectExportEntries_, [[StarExportEntries]]: _starExportEntries_, [[DFSIndex]]: ~empty~, [[DFSAncestorIndex]]: ~empty~ }. 1223 | 1224 | 1225 |

An implementation may parse module source text and analyse it for Early Error conditions prior to the evaluation of ParseModule for that module source text. However, the reporting of any errors must be deferred until the point where this specification actually performs ParseModule upon that source text.

1226 |
1227 |
1228 | 1229 | 1230 |

ExecuteModule ( [ _capability_ ] ) Concrete Method

1231 |

The ExecuteModule concrete method of a Source Text Module Record implements the corresponding Cyclic Module Record abstract method.

1232 |

This abstract method performs the following steps:

1233 | 1234 | 1235 | 1236 | 1. Let _module_ be this Source Text Module Record. 1237 | 1. Let _moduleContext_ be a new ECMAScript code execution context. 1238 | 1. Set the Function of _moduleContext_ to *null*. 1239 | 1. Set the Realm of _moduleContext_ to _module_.[[Realm]]. 1240 | 1. Set the ScriptOrModule of _moduleContext_ to _module_. 1241 | 1. Assert: _module_ has been linked and declarations in its module environment have been linked. 1242 | 1. Set the VariableEnvironment of _moduleContext_ to _module_.[[Environment]]. 1243 | 1. Set the LexicalEnvironment of _moduleContext_ to _module_.[[Environment]]. 1244 | 1. Suspend the currently running execution context. 1245 | 1. Let _moduleContext_ be _module_.[[Context]]. 1246 | 1. Push _moduleContext_ onto the execution context stack; moduleContext is now the running execution context. 1247 | 1. Let _result_ be the result of evaluating _module_.[[ECMAScriptCode]]. 1248 | 1. Suspend moduleContext and remove it from the execution context stack. 1249 | 1. Resume the context that is now on the top of the execution context stack as the running execution context. 1250 | 1. Return Completion(_result_). 1251 | 1. If _module_.[[Async]] is *false*, then 1252 | 1. Assert: _capability_ was not provided. 1253 | 1. Push _moduleCxt_ on to the execution context stack; _moduleCxt_ is now the running execution context. 1254 | 1. Let _result_ be the result of evaluating _module_.[[ECMAScriptCode]]. 1255 | 1. Suspend _moduleCxt_ and remove it from the execution context stack. 1256 | 1. Resume the context that is now on the top of the execution context stack as the running execution context. 1257 | 1. Return Completion(_result_). 1258 | 1. Otherwise, 1259 | 1. Assert: _capability_ is a PromiseCapability Record. 1260 | 1. Perform ! AsyncBlockStart(_capability_, _module_.[[ECMAScriptCode]], _moduleCxt_). 1261 | 1. Return. 1262 | 1263 |
1264 |
1265 | 1266 | 1267 |

Modules

1268 |

Syntax

1269 | 1270 | Module : 1271 | ModuleBody? 1272 | 1273 | ModuleBody : 1274 | ModuleItemList 1275 | 1276 | ModuleItemList : 1277 | ModuleItem 1278 | ModuleItemList ModuleItem 1279 | 1280 | ModuleItem : 1281 | ImportDeclaration 1282 | ExportDeclaration 1283 | StatementListItem[~Yield, ~Await, ~Return] 1284 | StatementListItem[~Yield, +Await, ~Return] 1285 | 1286 |
1287 | 1288 | 1289 |

Async Function Definitions

1290 |

Syntax

1291 | 1292 |

Async Function Definitions

1293 |

Syntax

1294 | 1295 | AsyncFunctionDeclaration[Yield, Await, Default] : 1296 | `async` [no LineTerminator here] `function` BindingIdentifier[?Yield, ?Await] `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}` 1297 | [+Default] `async` [no LineTerminator here] `function` `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}` 1298 | 1299 | AsyncFunctionExpression : 1300 | `async` [no LineTerminator here] `function` `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}` 1301 | `async` [no LineTerminator here] `function` BindingIdentifier[~Yield, +Await] `(` FormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}` 1302 | 1303 | AsyncMethod[Yield, Await] : 1304 | `async` [no LineTerminator here] PropertyName[?Yield, ?Await] `(` UniqueFormalParameters[~Yield, +Await] `)` `{` AsyncFunctionBody `}` 1305 | 1306 | AsyncFunctionBody : 1307 | FunctionBody[~Yield, +Await] 1308 | 1309 | AwaitExpression[Yield] : 1310 | `await` UnaryExpression[?Yield, +Await] 1311 | 1312 | 1313 | 1314 |

`await` is parsed as an |AwaitExpression| when the [Await] parameter is present. The [Await] parameter is present in the top level of the following contexts, although these contexts may also contain nonterminals which disable the parameter, such as |FunctionBody|:

1315 |
    1316 |
  • In an |AsyncFunctionBody|.
  • 1317 |
  • In the |FormalParameters| of an |AsyncFunctionDeclaration|, |AsyncFunctionExpression|, |AsyncGeneratorDeclaration|, or |AsyncGeneratorExpression|. |AwaitExpression| in this position is a Syntax error via static semantics.
  • 1318 |
  • When |Module| is the syntactic goal symbol.
  • 1319 |
1320 |

When |Module| is the syntactic goal symbol and the [Await] parameter is absent, `await` is parsed as a keyword and will be a Syntax error. When |Script| is the syntactic goal symbol, `await` may be parsed as an identifier when the [Await] parameter is absent. This includes the following contexts:

1321 |
    1322 |
  • Anywhere outside of an |AsyncFunctionBody| or |FormalParameters| of an |AsyncFunctionDeclaration|, |AsyncFunctionExpression|, |AsyncGeneratorDeclaration|, or |AsyncGeneratorExpression|.
  • 1323 |
  • In the |BindingIdentifier| of a |FunctionExpression|, |GeneratorExpression|, or |AsyncGeneratorExpression|.
  • 1324 |
1325 |
1326 |
1327 | 1328 | 1329 |

Imports

1330 |

Syntax

1331 | 1332 | ImportedBinding : 1333 | BindingIdentifier[~Yield, ~Await] 1334 | BindingIdentifier[~Yield, +Await] 1335 | 1336 |
1337 | 1338 | 1339 |

Exports

1340 |

Syntax

1341 | 1342 | ExportDeclaration : 1343 | `export` `*` FromClause `;` 1344 | `export` ExportClause FromClause `;` 1345 | `export` ExportClause `;` 1346 | `export` VariableStatement[~Yield, ~Await] 1347 | `export` VariableStatement[~Yield, +Await] 1348 | `export` Declaration[~Yield, ~Await] 1349 | `export` Declaration[~Yield, +Await] 1350 | `export` `default` HoistableDeclaration[~Yield, ~Await, +Default] 1351 | `export` `default` HoistableDeclaration[~Yield, +Await, +Default] 1352 | `export` `default` ClassDeclaration[~Yield, ~Await, +Default] 1353 | `export` `default` ClassDeclaration[~Yield, +Await, +Default] 1354 | `export` `default` [lookahead <! {`function`, `async` [no |LineTerminator| here] `function`, `class`}] AssignmentExpression[+In, ~Yield, ~Await] `;` 1355 | `export` `default` [lookahead <! {`function`, `async` [no |LineTerminator| here] `function`, `class`}] AssignmentExpression[+In, ~Yield, +Await] `;` 1356 | 1357 |
1358 | --------------------------------------------------------------------------------