I have my Configuration class:
interface ConfigObject {
apiUrl: string;
identityPoolId: string;
identityPoolRegion: string;
region: string;
userPoolId: string;
userPoolWebClientId: string;
}
class Configuration {
private static _instance: ConfigObject;
public static get Instance() {
return (
this._instance ||
this.getConfig<ConfigObject>().then((config) => {
this._instance = config;
return config;
})
);
}
private static async getConfig<TConfig>(): Promise<TConfig> {
const response = await fetch('env.json', {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(response.statusText);
}
const data = await response.json();
return data;
}
}
export default Configuration.Instance;
and I want to access it's values in my service:
export default class APIService {
static baseURL: string = `${Config.apiUrl}/mocked`;
Yet at the time of accessing Config.apiUrl is undefined
How can I make sure that the getConfig fetch gets executed and the actual object is returned instead?
You can't make an asynchronous process synchronous. But you can make your module wait to load until you've read that JSON file, by using top-level await, which is now broadly supported in browsers and by bundlers.
async function getConfigData(): Promise<ConfigObject> {
const response = await fetch('env.json', {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(response.statusText);
}
const data: ConfigObject = await response.json();
return data;
}
const data = await getConfigData(); // *** Module load waits here
class Configuration {
private static _instance: ConfigObject;
public static get Instance() {
if (!this._instance) {
this._instance = data;
}
return this._instance;
}
}
export default Configuration.Instance;
That said, there doesn't seem to be any purpose to the Configuration class, just export the data directly:
async function getConfigData(): Promise<ConfigObject> {
const response = await fetch('env.json', {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(response.statusText);
}
const data = await response.json();
return data;
}
const data: ConfigObject = await getConfigData(); // *** Module load waits here
export default data;
Side note: Since objects are mutable by default, any module that imports the configuration data can modify it (for instance, if there's a data.example property, by doing data.example = "some value"). Maybe you want it to be mutable, in which case don't do anything, but if you don't you might use Object.freeze to make everything about the object read-only:
// ...
const data: ConfigObject = await getConfigData();
Object.freeze(data); // *** Freezes the object
export default data;