Skip to content

Commit b3872c2

Browse files
Merge pull request #45 from fumiX/master
Add $asyncComputed status property
2 parents 0fc27db + 42d43dd commit b3872c2

File tree

3 files changed

+194
-6
lines changed

3 files changed

+194
-6
lines changed

README.md

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,29 @@ new Vue({
263263
}
264264
```
265265
266+
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.
267+
268+
````js
269+
270+
new Vue({
271+
asyncComputed: {
272+
blogPosts: {
273+
get () {
274+
return Vue.http.get('/posts')
275+
.then(response => response.data)
276+
},
277+
}
278+
},
279+
methods: {
280+
refresh() {
281+
// Triggers an immediate update of blogPosts
282+
// Will work even if an update is in progress.
283+
this.$asyncComputed.blogPosts.update();
284+
}
285+
}
286+
}
287+
````
288+
266289
### Conditional Recalculation
267290
268291
Using `watch` it is possible to run the computed property again but it will run regardless of the
@@ -323,7 +346,50 @@ new Vue({
323346
}
324347
```
325348
326-
## Error handling
349+
## Computation status
350+
351+
For each async comptued property, an object is added to `$asyncComputed` that contains information about the current computation state of that object. This object contains the following properties:
352+
353+
```js
354+
{
355+
// Can be one of updating, success, error
356+
state: 'updating',
357+
// A boolean that is true while the property is updating.
358+
updating: true,
359+
// The property finished updating wihtout errors (the promise was resolved) and the current value is available.
360+
success: false,
361+
// The promise was rejected.
362+
error: false,
363+
// The raw error/exception with which the promise was rejected.
364+
exception: null
365+
}
366+
```
367+
368+
It is meant to be used in your rendering code to display update / error information.
369+
370+
````js
371+
new Vue({
372+
asyncComputed: {
373+
posts() {
374+
return Vue.http.get('/posts')
375+
.then(response => response.data)
376+
}
377+
}
378+
}
379+
}
380+
// This will display a loading message every time the posts are updated:
381+
// <div v-if="$asyncComputed.posts.updating"> (Re)loading posts </div>
382+
383+
// If you only want to display the message the first times the posts load, you can use the fact that the default value is null:
384+
// <div v-if="$asyncComputed.posts.updating && posts === null"> Loading posts </div>
385+
386+
// You can display an error message if loading the posts failed.
387+
// The vue-resources library passes the error response on to the rejection handler.
388+
// It is therefore available in $asyncComputed.posts.exception
389+
// <div v-else-if="$asyncComputed.posts.error"> Error while loading posts: $asyncComputed.posts.exception.statusText </div>
390+
````
391+
392+
## Global error handling
327393
328394
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.
329395

src/index.js

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,16 @@ const AsyncComputed = {
2121
Vue.mixin({
2222
beforeCreate () {
2323
const optionData = this.$options.data
24+
const asyncComputed = this.$options.asyncComputed || {}
25+
this.$asyncComputed = {}
26+
27+
if (!Object.keys(asyncComputed).length) return
2428

2529
if (!this.$options.computed) this.$options.computed = {}
2630

27-
for (const key in this.$options.asyncComputed || {}) {
28-
this.$options.computed[prefix + key] = getterFn(key, this.$options.asyncComputed[key])
31+
for (const key in asyncComputed) {
32+
const getter = getterFn(key, this.$options.asyncComputed[key])
33+
this.$options.computed[prefix + key] = getter
2934
}
3035

3136
this.$options.data = function vueAsyncComputedInjectedDataFn () {
@@ -34,7 +39,7 @@ const AsyncComputed = {
3439
? optionData.call(this)
3540
: optionData
3641
) || {}
37-
for (const key in this.$options.asyncComputed || {}) {
42+
for (const key in asyncComputed) {
3843
const item = this.$options.asyncComputed[key]
3944
if (isComputedLazy(item)) {
4045
initLazy(data, key)
@@ -59,7 +64,7 @@ const AsyncComputed = {
5964

6065
for (const key in this.$options.asyncComputed || {}) {
6166
let promiseId = 0
62-
this.$watch(prefix + key, newPromise => {
67+
const watcher = newPromise => {
6368
const thisPromise = ++promiseId
6469

6570
if (newPromise === DidNotUpdate) {
@@ -69,13 +74,17 @@ const AsyncComputed = {
6974
if (!newPromise || !newPromise.then) {
7075
newPromise = Promise.resolve(newPromise)
7176
}
77+
setAsyncState(this.$asyncComputed[key], 'updating')
7278

7379
newPromise.then(value => {
7480
if (thisPromise !== promiseId) return
81+
setAsyncState(this.$asyncComputed[key], 'success')
7582
this[key] = value
7683
}).catch(err => {
7784
if (thisPromise !== promiseId) return
7885

86+
setAsyncState(this.$asyncComputed[key], 'error')
87+
this.$asyncComputed[key].exception = err
7988
if (pluginOptions.errorHandler === false) return
8089

8190
const handler = (pluginOptions.errorHandler === undefined)
@@ -88,13 +97,34 @@ const AsyncComputed = {
8897
handler(err.stack)
8998
}
9099
})
91-
}, { immediate: true })
100+
}
101+
this.$asyncComputed[key] = {
102+
exception: null,
103+
update: () => {
104+
watcher(getterOnly(this.$options.asyncComputed[key])())
105+
}
106+
}
107+
setAsyncState(this.$asyncComputed[key], 'updating')
108+
this.$watch(prefix + key, watcher, { immediate: true })
92109
}
93110
}
94111
})
95112
}
96113
}
97114

115+
function setAsyncState (stateObject, state) {
116+
stateObject.state = state
117+
stateObject.updating = state === 'updating'
118+
stateObject.error = state === 'error'
119+
stateObject.success = state === 'success'
120+
}
121+
122+
function getterOnly (fn) {
123+
if (typeof fn === 'function') return fn
124+
125+
return fn.get
126+
}
127+
98128
function getterFn (key, fn) {
99129
if (typeof fn === 'function') return fn
100130

test/index.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,3 +716,95 @@ test("shouldUpdate works with lazy", t => {
716716
})
717717
})
718718
})
719+
720+
test("$asyncComputed is empty if there are no async computed properties", t => {
721+
t.plan(1)
722+
const vm = new Vue({
723+
})
724+
t.deepEqual(vm.$asyncComputed, {})
725+
})
726+
727+
test("$asyncComputed[name] is created for all async computed properties", t => {
728+
t.plan(15)
729+
const vm = new Vue({
730+
asyncComputed: {
731+
a () {
732+
return Promise.resolve(1)
733+
},
734+
b () {
735+
return Promise.resolve(2)
736+
}
737+
}
738+
})
739+
t.deepEqual(Object.keys(vm.$asyncComputed), ['a', 'b'])
740+
t.equal(vm.$asyncComputed['a'].state, 'updating')
741+
t.equal(vm.$asyncComputed['b'].state, 'updating')
742+
t.equal(vm.$asyncComputed['a'].updating, true)
743+
t.equal(vm.$asyncComputed['a'].success, false)
744+
t.equal(vm.$asyncComputed['a'].error, false)
745+
t.equal(vm.$asyncComputed['a'].exception, null)
746+
747+
Vue.nextTick(() => {
748+
t.equal(vm.a, 1)
749+
t.equal(vm.b, 2)
750+
t.equal(vm.$asyncComputed['a'].state, 'success')
751+
t.equal(vm.$asyncComputed['b'].state, 'success')
752+
t.equal(vm.$asyncComputed['a'].updating, false)
753+
t.equal(vm.$asyncComputed['a'].success, true)
754+
t.equal(vm.$asyncComputed['a'].error, false)
755+
t.equal(vm.$asyncComputed['a'].exception, null)
756+
})
757+
})
758+
759+
test("$asyncComputed[name] handles errors and captures exceptions", t => {
760+
t.plan(7)
761+
const vm = new Vue({
762+
asyncComputed: {
763+
a () {
764+
// eslint-disable-next-line prefer-promise-reject-errors
765+
return Promise.reject('error-message')
766+
}
767+
}
768+
})
769+
t.equal(vm.$asyncComputed['a'].state, 'updating')
770+
pluginOptions.errorHandler = stack => {
771+
t.equal(vm.a, null)
772+
t.equal(vm.$asyncComputed['a'].state, 'error')
773+
t.equal(vm.$asyncComputed['a'].updating, false)
774+
t.equal(vm.$asyncComputed['a'].success, false)
775+
t.equal(vm.$asyncComputed['a'].error, true)
776+
t.equal(vm.$asyncComputed['a'].exception, 'error-message')
777+
pluginOptions.errorHandler = baseErrorCallback
778+
}
779+
})
780+
781+
test("$asyncComputed[name].update triggers re-evaluation", t => {
782+
let valueToReturn = 1
783+
t.plan(5)
784+
const vm = new Vue({
785+
asyncComputed: {
786+
a () {
787+
return new Promise(resolve => {
788+
resolve(valueToReturn)
789+
})
790+
}
791+
}
792+
})
793+
794+
Vue.nextTick(() => {
795+
t.equal(vm.a, 1)
796+
valueToReturn = 2
797+
t.equal(vm.$asyncComputed['a'].state, 'success')
798+
vm.$asyncComputed['a'].update()
799+
t.equal(vm.$asyncComputed['a'].state, 'updating')
800+
801+
Vue.nextTick(() => {
802+
t.equal(vm.a, 2)
803+
valueToReturn = 3
804+
805+
Vue.nextTick(() => {
806+
t.equal(vm.a, 2)
807+
})
808+
})
809+
})
810+
})

0 commit comments

Comments
 (0)