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.
Related
I'm operating a bot on Wikipedia using npm mwbot, and planning to migrate to npm mwn. This is because you need a "token" to edit pages on Wikipedia, and this can expire after a while so you need to prepare your own countermeasures against this if you use mwbot, but it seems like mwn handles this issue on its own.
When you use mwn, you first need to initialize a bot instance as documented on the turotial:
const bot = await mwn.init(myUserInfo);
Then your token is stored in the bot instance and you can for example edit a page using:
const result = await bot.edit('Page title', () => {text: 'text'});
So, basically you want to share the initialized bot instance across modules. I believe it'd be easiest to declare a global variable like so:
// bot.js (main script)
const {mwn} = require('mwn');
const {my} = require('./modules/my');
(async() => {
global.mw = await mwn.init(my.userinfo);
const {t} = require('./modules/test');
t();
})();
// modules/test.js
/* global mw */
exports.t = async () => {
const content = await mw.read('Main page');
console.log(content);
return content;
};
I'm currently using JavaScript, but will hopefully migrate to TypeScript (although I'm new to it) because I feel like it'd be useful in developing some of my modules. But I'm stuck with how I should use the initialized bot instance across modules in TypeScript.
-+-- dist (<= where ts files are compiled into)
+-- src
+-- types
+-- global.d.ts
+-- bot.ts
+-- my.ts
// bot.ts
import {mwn} from 'mwn';
import {my} from './my';
(async() => {
global.mw = await mwn.init(my.userinfo);
})();
// global.d.ts
import {mwn} from 'mwn';
declare global {
// eslint-disable-next-line no-var
var mw: mwn;
}
This doesn't work and returns "Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature. (at mw in global.mw)".
This is probably a naive question but any help would be appreciated.
Edit:
Thanks #CertainPerformance, that's a simple and easy approach. Actually, I once tried the same kind of an approach:
export const init = async () => {
if (typeof mw === 'undefined') {
return Promise.resolve(mw);
} else {
return mwn.init(my.userinfo).then((res) => {
mw = res;
return mw;
});
}
}
But I was like "init().then() in every module?"... don't know why I didn't come up with just exporting the initialized mwn instance.
Anyway, is it like the entry point file should be a .js file? I've been trying with a .ts file and this is one thing that's been giving me a headache. I'm using ts-node or nodemon to auto-compile .ts files, but without "type: module", "Cannot use import statement outside a module" error occurs and with that included, "TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"" occurs. How do you tell a given file should be a .js or .ts file?
Edit2:
Just making a note: The error I mentioned above was caused by not having "module": "CommonJS" in my tsconfig.json, as I commented to CertainPerformance's answer below.
One of the main benefits of modules is the ability to drop dependencies on global variables. Rather than going back on that and assigning a global anyway, a better approach that happens to solve your problem as well would be to have a module that exports two functions:
One that initializes the asynchronous mwn
One that returns mwn when called
// mw.ts
import {mwn} from 'mwn';
import {my} from './my';
let mw: mwn;
export const init = async () => {
mw = await mwn.init(my.userinfo);
};
export const getMw = () => mw;
Then it can be consumed by other modules quite naturally, barely requiring any typing at all:
// modules/index.ts
// Entry point
import { init } from './mw';
import { test } from './test';
init()
.then(() => {
test();
})
// .catch(handleErrors);
// modules/test.ts
import { getMw } from './bot';
export const test = () => {
const mw = getMw();
// anything else that depends on mw
};
The above could be a reasonable approach if mw is used in many places across your app and you don't think that passing it around everywhere would be maintainable.
If you could pass it around everywhere, that would have even less of a code smell, though.
// initMw.ts
import {mwn} from 'mwn';
import {my} from './my';
export const initMw = () => mwn.init(my.userinfo);
// modules/index.ts
// Entry point
import { initMw } from './initMw';
import { test } from './test';
initMw()
.then((mw) => {
test(mw);
})
// .catch(handleErrors);
// modules/test.ts
import { mwn } from 'mwn';
export const test = (mw: mwn) => {
// anything that depends on mw
};
Initialize it once, then pass it down (synchronously) everywhere it's needed - that's the approach I'd prefer.
You could put the mwn type in a global d.ts file to avoid having to add import { mwn } from 'mwn'; to every module if you wanted. (Yes, it's somewhat of a global variable, but it's a type rather than a value, so it's arguably less of a problem.)
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.
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?
I am currently working on technical debt identified by SonarQube for a Node.js application. My application allows on-the-fly switching between a live and mock datasource. To achieve this I destroy the previous "require" from cache and re-require it. When running SonarQube it does not like "require" statements. It does suggest "import" statements. However that may not be suitable in this case.
Simplified version of existing code:
var config = require('../config');
var polService = require(config.polService);
var root = require('../root');
function doingStuff(liveOrMock) {
setEnvironment(liveOrMock);
delete require.cache[require.resolve(root.path + ‘/config’)];
config = require('../config');
polService = require(config.polService);
}
The setEnvironment function sets process.env.NODE_ENV = liveOrMock, which is used in config.js. We export the config module using module.exports = localOptions[process.env.NODE_ENV]; This code picks a single key-pair from a JSON. The value that comes back is used to choose which module is being used for a restService.
Being able to change what module is being used is for polService is the purpose of the code.
Change your config module to export a function, and then call this function whenever you need to change environment.
In order to make polService a dynamic module, you can use dynamic import(). import() is not supported natively, but you can use this Babel plugin (it works with webpack) to transpile it.
config.js:
export default () => {
// ...
return localOptions[process.env.NODE_ENV];
}
Main module:
import getConfig from '../config';
let config = getConfig();
function doingStuff(liveOrMock) {
setEnvironment(liveOrMock);
config = getConfig();
return import(config.polService).then(result => {
polService = result;
});
}
Keep in mind that now the doingStuff function is asynchronous (i.e. returns a promise), so you can't just call it and access polService immediately. You have to wait for it by either using the then() method, or using await in an async function.
If you have a limited number of polService modules, it might be a better option to import all of them beforehand, and in the doingStuff function just switch which one the polService variable refers to.
import getConfig from '../config';
import polService1 from '../polService1';
import polService2 from '../polService2';
import polService3 from '../polService3';
const polServices = { polService1, polService2, polService3 };
let config = getConfig();
let polService = polService1;
function doingStuff(liveOrMock) {
setEnvironment(liveOrMock);
config = getConfig();
polService = polServices[config.polService];
}
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)});