vue-async-computed
With this plugin, you can have computed properties in Vue that are computed asynchronously.
Without using this plugin, you can't do this:
new Vue({
data: {
userId: 1
},
computed: {
username () {
return Vue.http.get('/get-username-by-id/' + this.userId)
.then(response => response.data.username)
}
}
}
Or rather, you could, but it wouldn't do what you'd want it to do. But using this plugin, it works just like you'd expect:
new Vue({
data: {
userId: 1
},
asyncComputed: {
username () {
return Vue.http.get('/get-username-by-id/' + this.userId)
.then(response => response.data.username)
}
}
}
This is especially useful with ES7 async functions:
new Vue({
asyncComputed: {
async someCalculation () {
const x = await someAsycFunction()
const y = await anotherAsyncFunction()
return x + y
}
}
})
Install
npm install --save vue-async-computed
Alternately, you can link it directly from a CDN:
<script src="https://unpkg.com/vue-async-computed"></script>
<script src="https://unpkg.com/vue-async-computed@3.8.1"></script>
When used with a module system such as webpack
or browserify
, you need to explicitly install vue-async-computed
via Vue.use()
:
import Vue from 'vue'
import AsyncComputed from 'vue-async-computed'
Vue.use(AsyncComputed)
You don't need to do this when using global script tags. So long as you include vue-async-computed
in a script tag after Vue itself, it will be installed automatically.
Usage example
import AsyncComputed from 'vue-async-computed'
Vue.use(AsyncComputed)
const vm = new Vue({
data: {
x: 2,
y: 3
},
asyncComputed: {
sum () {
const total = this.x + this.y
return new Promise(resolve =>
setTimeout(() => resolve(total), 1000)
)
}
}
})
Like with regular synchronous computed properties, you can pass an object
with a get
method instead of a function, but unlike regular computed
properties, async computed properties are always getter-only. If the
object provided has a set
method it will be ignored.
Async computed properties can also have a custom default value, which will
be used until the data is loaded for the first time:
new Vue({
data: {
postId: 1
},
asyncComputed: {
blogPostContent: {
get () {
return Vue.http.get('/post/' + this.postId)
.then(response => response.data.postContent)
},
default: 'Loading...'
}
}
}
You can instead define the default value as a function, in order to depend on
props or on data:
new Vue({
data: {
postId: 1
},
asyncComputed: {
blogPostContent: {
get () {
return Vue.http.get('/post/' + this.postId)
.then(response => response.data.postContent)
},
default () {
return 'Loading post ' + this.postId
}
}
}
}
You can also set a custom global default value in the options passed to Vue.use
:
Vue.use(AsyncComputed, {
default: 'Global default value'
})
Recalculation
Just like normal computed properties, async computed properties keep track of their dependencies, and are only
recalculated if those dependencies change. But often you'll have an async computed property you'll want to run again
without any of its (local) dependencies changing, such as for instance the data may have changed in the database.
You can set up a watch
property, listing the additional dependencies to watch.
Your async computed property will then be recalculated also if any of the watched
dependencies change, in addition to the real dependencies the property itself has:
new Vue({
data: {
postId: 1,
timesPostHasBeenUpdated: 0
},
asyncComputed: {
blogPostContent: {
get () {
return Vue.http.get('/post/' + this.postId)
.then(response => response.data.postContent)
},
watch: ['timesPostHasBeenUpdated']
}
}
}
Just like with Vue's normal watch
, you can use a dotted path in order to watch a nested property. For example, watch: ['a.b.c', 'd.e']
would declare a dependancy on this.a.b.c
and on this.d.e
.
You can trigger re-computation of an async computed property manually, e.g. to re-try if an error occured during evaluation. This should be avoided if you are able to achieve the same result using a watched property.
new Vue({
asyncComputed: {
blogPosts: {
get () {
return Vue.http.get('/posts')
.then(response => response.data)
},
}
},
methods: {
refresh() {
this.$asyncComputed.blogPosts.update();
}
}
}
Conditional Recalculation
Using watch
it is possible to run the computed property again but it will run regardless of the
value of the watched property. If you need more control over when the computation should be rerun
you can use shouldUpdate
:
new Vue({
data: {
postId: 1,
pageType: 'index'
},
asyncComputed: {
blogPostContent: {
get () {
return Vue.http.get('/post/' + this.postId)
.then(response => response.data.postContent)
},
shouldUpdate () {
return this.pageType !== 'index'
}
}
}
}
The main advantage over adding an if statement within the get function is that when the computation is
not rerun you are able to still access the old value.
Lazy properties
Normally, computed properties are run both immediately, and as necessary when their dependencies change.
With async computed properties, you sometimes don't want that. With lazy: true
, an async computed
property will only be computed the first time it's accessed.
For example:
new Vue({
data: {
id: 1
},
asyncComputed: {
mightNotBeNeeded: {
lazy: true,
get () {
return Vue.http.get('/might-not-be-needed/' + this.id)
.then(response => response.data.value)
}
}
}
}
Computation status
For each async computed property, an object is added to $asyncComputed
that contains information about the current computation state of that object. This object contains the following properties:
{
state: 'updating',
updating: true,
success: false,
error: false,
exception: null
}
It is meant to be used in your rendering code to display update / error information.
new Vue({
asyncComputed: {
posts() {
return Vue.http.get('/posts')
.then(response => response.data)
}
}
}
}
Global error handling
By default, in case of a rejected promise in an async computed property, vue-async-computed will take care of logging the error for you.
If you want to use a custom logging function, the plugin takes an errorHandler
option, which should be the function you want called with the error information.
By default, it will be called with only the error's stack trace as an argument,
but if you register the errorHandler
with useRawError
set to true
the
function will receive the raw error, a reference to the Vue
instance that
threw the error and the error's stack trace.
For example:
Vue.use(AsyncComputed, {
errorHandler (stack) {
console.log('Hey, an error!')
console.log('---')
console.log(stack)
}
)
Vue.use(AsyncComputed, {
useRawError: true,
errorHandler (err, vm, stack) {
console.log('An error occurred!')
console.log('The error message was: ' + err.msg)
console.log('And the stack trace was:')
console.log(stack)
}
)
You can pass false
as the errorHandler
in order to silently ignore rejected promises.
License
MIT © Benjamin Fox