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 leaveasyncCall
(and returnresult
with its default value ofone
because the callback insetTimeout()
has not happened yet). It should be destroyed once the function is over. - ... why I can return
result
(a pointer) and assign it tomsg.value
(a string) (and it works) ... why the displayed value (converted to its own question."one"
and"two"
) have quotes.
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.