Reputation: 30054
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:
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.result
(a pointer) and assign it to msg.value
(a string) (and it works)"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.
Upvotes: 1
Views: 809
Reputation: 90277
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.
Upvotes: 3