Can ES6 imports be awaited? - javascript

I saw this answer about differences between es6 imports vs require.
But one of the answers there caught my eyes:
Thing is import() is actually async in nature.
Example :
const module = await import('./module.js');
mdn says nothing about it.
I've been using imports for some time now but never knew that it can be awaited ??
I've also created an experiment (dummy angular test):
1.ts
export const a=3;
app.component.ts
import { Component } from '#angular/core'; //no await here
#Component({
...
})
export class AppComponent {
constructor() {
this.foo();
}
async foo() {
const module = await import('./1'); //<--- if I remove the await, it doesn't work
alerqt(module.a)
}
}
So it seems that import returns a promise ?
Question
What am I missing? I didn't have to await in the first two lines of where I imported Component:
import { Component } from '#angular/core'; //no await
#Component({
...
})
And where does it say that import returns a promise ?
Also , one of the webpack programmers : (or system.js) said :
If import was just an asynchronous function, then it would no longer
be possible to statically analyze modules, as just like CommonJS
require, I could have import within arbitrary conditions etc.
But from my testing it does require await , which makes it asynchronisly evaluated.
So what is going on here?

Related

Conditional Import of 2 objets with the same name [duplicate]

I need to do something like:
if (condition) {
import something from 'something';
}
// ...
if (something) {
something.doStuff();
}
The above code does not compile; it throws SyntaxError: ... 'import' and 'export' may only appear at the top level.
I tried using System.import as shown here, but I don't know where System comes from. Is it an ES6 proposal that didn't end up being accepted? The link to "programmatic API" from that article dumps me to a deprecated docs page.
We do have dynamic imports proposal now with ECMA. This is in stage 3. This is also available as babel-preset.
Following is way to do conditional rendering as per your case.
if (condition) {
import('something')
.then((something) => {
console.log(something.something);
});
}
This basically returns a promise. Resolution of promise is expected to have the module. The proposal also have other features like multiple dynamic imports, default imports, js file import etc. You can find more information about dynamic imports here.
If you'd like, you could use require. This is a way to have a conditional require statement.
let something = null;
let other = null;
if (condition) {
something = require('something');
other = require('something').other;
}
if (something && other) {
something.doStuff();
other.doOtherStuff();
}
You can't import conditionally, but you can do the opposite: export something conditionally. It depends on your use case, so this work around might not be for you.
You can do:
api.js
import mockAPI from './mockAPI'
import realAPI from './realAPI'
const exportedAPI = shouldUseMock ? mockAPI : realAPI
export default exportedAPI
apiConsumer.js
import API from './api'
...
I use that to mock analytics libs like mixpanel, etc... because I can't have multiple builds or our frontend currently. Not the most elegant, but works. I just have a few 'if' here and there depending on the environment because in the case of mixpanel, it needs initialization.
2020 Update
You can now call the import keyword as a function (i.e. import()) to load a module at runtime. It returns a Promise that resolves to an object with the module exports.
Example:
const mymodule = await import('modulename');
const foo = mymodule.default; // Default export
const bar = mymodule.bar; // Named export
or:
import('modulename')
.then(mymodule => {
const foo = mymodule.default; // Default export
const bar = mymodule.bar; // Named export
});
See Dynamic Imports on MDN
Looks like the answer is that, as of now, you can't.
http://exploringjs.com/es6/ch_modules.html#sec_module-loader-api
I think the intent is to enable static analysis as much as possible, and conditionally imported modules break that. Also worth mentioning -- I'm using Babel, and I'm guessing that System is not supported by Babel because the module loader API didn't become an ES6 standard.
Import and Export Conditionally in JS
const value = (
await import(`${condtion ? `./file1.js` : `./file2.js`}`)
).default
export default value
Important difference if you use dynamic import Webpack mode eager:
if (normalCondition) {
// this will be included to bundle, whether you use it or not
import(...);
}
if (process.env.SOMETHING === 'true') {
// this will not be included to bundle, if SOMETHING is not 'true'
import(...);
}
require() is a way to import some module on the run time and it equally qualifies for static analysis like import if used with string literal paths. This is required by bundler to pick dependencies for the bundle.
const defaultOne = require('path/to/component').default;
const NamedOne = require('path/to/component').theName;
For dynamic module resolution with complete static analysis support, first index modules in an indexer(index.js) and import indexer in host module.
// index.js
export { default as ModuleOne } from 'path/to/module/one';
export { default as ModuleTwo } from 'path/to/module/two';
export { SomeNamedModule } from 'path/to/named/module';
// host.js
import * as indexer from 'index';
const moduleName = 'ModuleOne';
const Module = require(indexer[moduleName]);
obscuring it in an eval worked for me, hiding it from the static analyzer ...
if (typeof __CLI__ !== 'undefined') {
eval("require('fs');")
}
Conditional imports could also be achieved with a ternary and require()s:
const logger = DEBUG ? require('dev-logger') : require('logger');
This example was taken from the ES Lint global-require docs: https://eslint.org/docs/rules/global-require
I was able to achieve this using an immediately-invoked function and require statement.
const something = (() => (
condition ? require('something') : null
))();
if(something) {
something.doStuff();
}
Look at this example for clear understanding of how dynamic import works.
Dynamic Module Imports Example
To have Basic Understanding of importing and exporting Modules.
JavaScript modules Github
Javascript Modules MDN
No, you can't!
However, having bumped into that issue should make you rethink on how you organize your code.
Before ES6 modules, we had CommonJS modules which used the require() syntax. These modules were "dynamic", meaning that we could import new modules based on conditions in our code. - source: https://bitsofco.de/what-is-tree-shaking/
I guess one of the reasons they dropped that support on ES6 onward is the fact that compiling it would be very difficult or impossible.
One can go through the below link to learn more about dynamic imports
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports
I know this is not what the question is asking for, but here is my approach to use mocks when using vite. I'm sure we can do the same with webpack and others.
Suppose we have two libraries with same interface: link.js and link-mock.js, then:
In my vite.config.js
export default defineConfig(({ command, mode }) => {
const cfg = {/* ... */}
if (process.env.VITE_MOCK == 1) {
cfg.resolve.alias["./link"] = "./link-mock"; // magic is here!
}
return cfg;
}
code:
import { link } from "./link";
in console we call:
# to use the real link.js
npm run vite
# to use the mock link-mock.js
VITE_MOCK=1 npm run vite
or
package.json script
{
....
"scripts": {
"dev": "vite",
"dev-mock": "VITE_MOCK=1 vite"
}
}

javascript variable outside function (with async/await)?

I have following code which doesn't work due to syntax error (await outside an async function)
how do I define a variable with await and export it?
When I define a variable like this and import it from other files, does the variable gets created only once (when file is first read?) or gets created everytime when it's imported?
Code..
import _ from 'lodash'
import remoteConfig from '#react-native-firebase/remote-config'
class RemoteConfig {
constructor() {
if (__DEV__) {
//Fetch, no cache. activate
remoteConfig().fetchAndActivate(0)
} else {
//Fetch, cache for 5 minutes and activate
remoteConfig().fetchAndActivate()
}
}
static async build() {
await remoteConfig().setConfigSettings({
minimumFetchIntervalMillis: 300000,
})
return new RemoteConfig()
}
setDefaults(defaults) {
remoteConfig().setDefaults(defaults)
}
getValues(keys) {
return _.pick(remoteConfig().getAll(), keys)
}
getValue(key) {
return remoteConfig().getValue(key)
}
}
export let firebaseConfig = await RemoteConfig.build()
I'm using it with import {firebaseConfig} from path/to/thefile
await can be only used in async function. It's not possible to export a variable with await syntax.
Since the export with await is not possible, it's difficult to say if the RemoteConfig.build() will be called each time.
If we assume, you wrote firebaseConfig = RemoteConfig.build();. The function will be called one time when the module is evaluated.
Here is a workaround:
You can define a function to set the firebaseConfig and call it at the time of app start.
So:
export let firebaseConfig;
export async function setFirebaseConfig() {
firebaseConfig = await RemoteConfig.build();
}
This will allow you to not call the RemoteConfig.build() more than once. Also the firebaesConfig can be exported without await syntax.
To make this work, you have to wait for the "Top-level await" proposal (currently at Stage 3), that allows doing so (with a transpiler like Babel, you can use it now). It would allow exactly the same code that you have.
Until then, the only way to achieve a variable to exported like this to make the variable's value a promise, that all importers have to await on its own.

Exporting a dynamic import

I'd like to export a different package from a custom module based on an expression
const settings = {}
const init = (sentry) => {
sentry.init(settings)
return sentry
}
const pkg = async () => {
let mod
if (__CLIENT__) {
mod = await import('#sentry/browser').then(init)
} else {
mod = await import('#sentry/node').then(init)
}
return mod
}
const Sentry = pkg()
console.log({ Sentry })
export default Sentry
However, when i import this file later i receive a pending promise
import Sentry from 'config/sentry'
console.log(Sentry) // -> promise pending
Is it possible to default export the dynamically imported module at the top level?
update
I ended up going with require instead of dynamic imports, as supported by webpack globals setup on my system
const Sentry = __CLIENT__ ? require('#sentry/browser') : require('#sentry/node')
Async functions always return a promise. That's by definition. Since dynamic imports also return promises (and are async), you can't export them, since exports have to be at the top level of a script. The best you can do is export the promise and include a .then handler in whichever script imports it; since you're returning mod from your async function, that will be passed to the .then handler's parameter for you to use there.

.then() on node modules not working?

I'm working on a react native app right now and I wrote my own module to store some information separated from my components.
While I did that I noticed that in my module I created, I had a function like this:
testFetch = function(url){
return fetch(url);
}
exports.test = testFetch;
In my main module I did the following:
import ts from 'test-module'
ts.test("http://example.com").then((response) => {console.log(response)});
But .then() is not getting fired for any reason. The request went through and was successful.
Any help on this?
The problem is the way you mix CommonJS and ES6 modules. You seem to expect that ts in your example main module gives you the value of the whole export in your module dependency, but that's not how it works.
You can use export default to export your testFetch function, like so:
testFetch = function (url) {
return fetch(url);
}
export default testFetch;
Main module:
import testFetch from 'test-module';
testFetch("http://example.com").then((response) => {console.log(response)});

How can I conditionally import an ES6 module?

I need to do something like:
if (condition) {
import something from 'something';
}
// ...
if (something) {
something.doStuff();
}
The above code does not compile; it throws SyntaxError: ... 'import' and 'export' may only appear at the top level.
I tried using System.import as shown here, but I don't know where System comes from. Is it an ES6 proposal that didn't end up being accepted? The link to "programmatic API" from that article dumps me to a deprecated docs page.
We do have dynamic imports proposal now with ECMA. This is in stage 3. This is also available as babel-preset.
Following is way to do conditional rendering as per your case.
if (condition) {
import('something')
.then((something) => {
console.log(something.something);
});
}
This basically returns a promise. Resolution of promise is expected to have the module. The proposal also have other features like multiple dynamic imports, default imports, js file import etc. You can find more information about dynamic imports here.
If you'd like, you could use require. This is a way to have a conditional require statement.
let something = null;
let other = null;
if (condition) {
something = require('something');
other = require('something').other;
}
if (something && other) {
something.doStuff();
other.doOtherStuff();
}
You can't import conditionally, but you can do the opposite: export something conditionally. It depends on your use case, so this work around might not be for you.
You can do:
api.js
import mockAPI from './mockAPI'
import realAPI from './realAPI'
const exportedAPI = shouldUseMock ? mockAPI : realAPI
export default exportedAPI
apiConsumer.js
import API from './api'
...
I use that to mock analytics libs like mixpanel, etc... because I can't have multiple builds or our frontend currently. Not the most elegant, but works. I just have a few 'if' here and there depending on the environment because in the case of mixpanel, it needs initialization.
2020 Update
You can now call the import keyword as a function (i.e. import()) to load a module at runtime. It returns a Promise that resolves to an object with the module exports.
Example:
const mymodule = await import('modulename');
const foo = mymodule.default; // Default export
const bar = mymodule.bar; // Named export
or:
import('modulename')
.then(mymodule => {
const foo = mymodule.default; // Default export
const bar = mymodule.bar; // Named export
});
See Dynamic Imports on MDN
Looks like the answer is that, as of now, you can't.
http://exploringjs.com/es6/ch_modules.html#sec_module-loader-api
I think the intent is to enable static analysis as much as possible, and conditionally imported modules break that. Also worth mentioning -- I'm using Babel, and I'm guessing that System is not supported by Babel because the module loader API didn't become an ES6 standard.
Import and Export Conditionally in JS
const value = (
await import(`${condtion ? `./file1.js` : `./file2.js`}`)
).default
export default value
Important difference if you use dynamic import Webpack mode eager:
if (normalCondition) {
// this will be included to bundle, whether you use it or not
import(...);
}
if (process.env.SOMETHING === 'true') {
// this will not be included to bundle, if SOMETHING is not 'true'
import(...);
}
require() is a way to import some module on the run time and it equally qualifies for static analysis like import if used with string literal paths. This is required by bundler to pick dependencies for the bundle.
const defaultOne = require('path/to/component').default;
const NamedOne = require('path/to/component').theName;
For dynamic module resolution with complete static analysis support, first index modules in an indexer(index.js) and import indexer in host module.
// index.js
export { default as ModuleOne } from 'path/to/module/one';
export { default as ModuleTwo } from 'path/to/module/two';
export { SomeNamedModule } from 'path/to/named/module';
// host.js
import * as indexer from 'index';
const moduleName = 'ModuleOne';
const Module = require(indexer[moduleName]);
obscuring it in an eval worked for me, hiding it from the static analyzer ...
if (typeof __CLI__ !== 'undefined') {
eval("require('fs');")
}
Conditional imports could also be achieved with a ternary and require()s:
const logger = DEBUG ? require('dev-logger') : require('logger');
This example was taken from the ES Lint global-require docs: https://eslint.org/docs/rules/global-require
I was able to achieve this using an immediately-invoked function and require statement.
const something = (() => (
condition ? require('something') : null
))();
if(something) {
something.doStuff();
}
Look at this example for clear understanding of how dynamic import works.
Dynamic Module Imports Example
To have Basic Understanding of importing and exporting Modules.
JavaScript modules Github
Javascript Modules MDN
No, you can't!
However, having bumped into that issue should make you rethink on how you organize your code.
Before ES6 modules, we had CommonJS modules which used the require() syntax. These modules were "dynamic", meaning that we could import new modules based on conditions in our code. - source: https://bitsofco.de/what-is-tree-shaking/
I guess one of the reasons they dropped that support on ES6 onward is the fact that compiling it would be very difficult or impossible.
One can go through the below link to learn more about dynamic imports
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports
I know this is not what the question is asking for, but here is my approach to use mocks when using vite. I'm sure we can do the same with webpack and others.
Suppose we have two libraries with same interface: link.js and link-mock.js, then:
In my vite.config.js
export default defineConfig(({ command, mode }) => {
const cfg = {/* ... */}
if (process.env.VITE_MOCK == 1) {
cfg.resolve.alias["./link"] = "./link-mock"; // magic is here!
}
return cfg;
}
code:
import { link } from "./link";
in console we call:
# to use the real link.js
npm run vite
# to use the mock link-mock.js
VITE_MOCK=1 npm run vite
or
package.json script
{
....
"scripts": {
"dev": "vite",
"dev-mock": "VITE_MOCK=1 vite"
}
}

Categories