Home > Software design >  Can I really return a reactive variable and have it update its value asynchronosly?
Can I really return a reactive variable and have it update its value asynchronosly?

Time:10-20

When reading the code of a frontend written in Vue3 I stumbled upon a construction I have not seen so far and I have some problems understanding how it works. The basic idea is assigning to a reactive value the result of an asynchronous call (the JSON result of a fetch for instance).

The way I have been doing it is shown in the code below (Vue Playground). The setTimeout simulates the asynchronous call, a Promise is returned, and then acted upon via a then() that sets the reactive variable msg.

<script setup>
import { ref } from 'vue'

const msg = ref('one')

const asyncCall = () => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("two");
  }, 2000);
})

asyncCall().then(r => msg.value = r)

</script>

<template>
  <h1>{{ msg }}</h1>
</template>

The code I stumbled upon (Vue Playground) takes a different approach: it creates a local reactive variable in a function, makes the asynchronous call and when it resolves it sets the value of that local ref variable. It is then returned (this is the visual sequence, the execution is different, see comments further down)

<script setup>
import { ref } from 'vue'

const msg = ref('x')

const asyncCall = () => {
  const result = ref('one')
  setTimeout(() => {
    result.value = 'two'
  }, 2000)
  return result
}

msg.value = asyncCall()

</script>

<template>
  <h1>{{ msg }}</h1>
</template>

It works as well, but I do not understand:

  • ... why the local variable result is updated once we leave asyncCall (and return result with its default value of one because the callback in setTimeout() has not happened yet). It should be destroyed once the function is over.
  • ... why I can return result (a pointer) and assign it to msg.value (a string) (and it works)
  • ... why the displayed value ("one" and "two") have quotes. converted to its own question.

My question: is the second approach correct? recommended? It surely simplifies code because all the asynchronous part happens in the function, but I have never seen that approach.

CodePudding user response:

What you call an async function is not, technically, an async function. It's a function returning a ref(). Immediately, without any asynchrony.

But that function also has a side effect (the setTimeout). At some point in the future, it updates the returned ref. Because you're assigning the result to another ref, when you modify the returned one's value you actually modify the value of the one it was assigned to.

To your point, it's perfectly legal. You can assign ref()s to a reactive()'s prop or to another ref() and if/when you update the returned ref's value, the one you passed it to will also update.


To make my point clearer, in the example you linked, msg.value starts off as x, it's immediately assigned the value of ref('one') and two seconds later the inner ref()'s value is changed to 'two'.
After this assignment, if you check msg.value, it is a ref(), it's no longer a string. But that's not a problem for <template> because it automatically unwraps any nested refs, until it gets to the actual .value.
However, be weary of using msg.value inside the controller (after the assignment), as you will have to unref it to get to the string value.

Another potential problem one might encounter with this technique is watch-ing msg will no longer detect changes after the assignment, because its value remains the same: result. To get past this problem, one would need to use { deep: true } in the watcher's options.

  • Related