1

My code uses plugins feature in Vuejs to define global shared variable.

Vue.use(shared)

shared is defined as:-

export const shared = {
  config: getAppConfig()
}
shared.install = function() {
  Object.defineProperty(Vue.prototype, '$shared', {
    get() {
      return shared
    }
  })
}

function getAppConfig() {
  var api = getAPIURL()
  return axios.get("https://url/get_config")
  .then(response => {
    return response.data
  }
}

My issue is, in my component, if I use this variable this.$shared.config, I get undefined.

Looking at the console window and debug statement, my component code is executed before the plugin got time to this.$shared.config.

I'm new to javascript+Vuejs but when I researched about this error, it is related to axios being asynchronous so I decided to return promise and use await.

function getAppConfig() {
  var api = getAPIURL()
  return axios.get("https://url/get_config")
}

But, when in my shared.install function, I try do:-

shared.install = function() {
  let config = await shared.config

I get error: Syntax Error: await is a reserved word.

Since I'm new, it looks like I'm doing a fundamental error in how I should make this code synchronous. What's the right way to fix this?

shadyabhi
  • 16,675
  • 26
  • 80
  • 131
  • If the app config is so important, maybe you should delay instantiating your VueJS app until you’ve received a response from the endpoint? – Terry Mar 15 '20 at 00:15
  • `function getAppConfig` doesn't return anything, therefore the result is `undefined` - problem two, `await` can only be used in a function that is `async` ... – Jaromanda X Mar 15 '20 at 00:29
  • 1
    problem three *how I should make this code synchronous* - you can't. asynchronous results can not be *made* synchronous, unless you change how the results are retrieved to be synchronous - and synchronous XMLHttpRequest is deprecated and results in poor user experience – Jaromanda X Mar 15 '20 at 00:49
  • depending on your usage - i.e. how you're using this data in the components, https://www.npmjs.com/package/vue-async-computed may help – Jaromanda X Mar 15 '20 at 00:56
  • @JaromandaX I have a use-case where my app config can only be fetched via an HTTP request. Is installing a new plugin the only way to do it? – shadyabhi Mar 15 '20 at 01:17
  • your description of your use case provides no extra information at all - because clearly you are fetching the config using AJAX in the code you've shown - but no, you don't have to use that plugin, just recreate how that plugin works using your own code - i.e. re-invent the wheel – Jaromanda X Mar 15 '20 at 02:07
  • @JaromandaX Thank you for your response. Is this something that's recommended? http://sprunge.us/wWz1LF – shadyabhi Mar 15 '20 at 02:30
  • no idea - does it work? – Jaromanda X Mar 15 '20 at 04:14
  • You can't make this code synchronous. It's **a**synchronous. Yes, this is a fundamental problem, see https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call – Estus Flask Mar 15 '20 at 07:23

1 Answers1

2

Vue doesn't specifically support plugins that are initialized asynchronously.

If a component will be instantiated right after Vue.use(shared), $shared won't be available. A promise that could be chained should exist, e.g.:

install() {
  Vue.sharedPromise = getAppConfig();
  Vue.sharedPromise.then(config => {
    Vue.prototype.$shared = config
  });
}

and

Vue.use(shared);
Vue.sharedPromise.then(() => {
  // $shared is available, mount app
});

Since there is no real necessity to push asynchronous operation to a plugin, it could be kept outside:

install(Vue, { config }) {
  Vue.prototype.$shared = config;
}

and

getAppConfig().then(config => {
  Vue.use(shared, { config });
  // $shared is available, mount app
});
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Thank you for the answer. I'm going to implement this way. Thanks for suggesting the recommended approach and explaining why it doesn't work this way. Could you shed some light on if this method is highly discouraged? http://sprunge.us/wWz1LF – shadyabhi Mar 17 '20 at 00:27
  • It really is. $shared won't be available when a component is mounted and you don't know when it's available because it's not reactive. The only way for it to work is to use huge setTimeout and hope that it's available after a delay. – Estus Flask Mar 17 '20 at 06:46
  • If you do asynchronous init for some reason, at least make sure you expose a promise that can be chained in places that depend on initialization. If you have initPromise (imported or exposed on Vue.prototype), you need to do `async mounted() { await initPromise; ... }` in every place where it's relevant. See `auth0ClientPromise` here for example https://stackoverflow.com/a/60713249/3731501 , it's a case similar to yours but the OP chose to finish init after start because it's acceptable for auth token (but it likely isn't for app-wide config like $shared). – Estus Flask Mar 17 '20 at 06:48
  • Thanks for the help – shadyabhi Mar 18 '20 at 15:05