diff --git a/src/index.js b/src/index.js index e848881..f6c3a09 100644 --- a/src/index.js +++ b/src/index.js @@ -7,9 +7,9 @@ import { silentSetLazy, } from './lazy' import { getWatchedGetter } from './watch' +import { getGetterWithShouldUpdate, shouldNotUpdate } from './shouldUpdate' const prefix = '_async_computed$' -const DidNotUpdate = typeof Symbol === 'function' ? Symbol('did-not-update') : {} const AsyncComputed = { install (Vue, pluginOptions) { @@ -25,38 +25,22 @@ const AsyncComputed = { _asyncComputed: {}, } }, + computed: { + $asyncComputed () { + return this.$data._asyncComputed + } + }, beforeCreate () { - const optionData = this.$options.data const asyncComputed = this.$options.asyncComputed || {} - if (!this.$options.computed) this.$options.computed = {} - - this.$options.computed.$asyncComputed = () => this.$data._asyncComputed - if (!Object.keys(asyncComputed).length) return for (const key in asyncComputed) { - const getter = getterFn(key, this.$options.asyncComputed[key]) + const getter = getterFn(key, asyncComputed[key]) this.$options.computed[prefix + key] = getter } - this.$options.data = function vueAsyncComputedInjectedDataFn (vm) { - const data = ( - (typeof optionData === 'function') - ? optionData.call(this, vm) - : optionData - ) || {} - for (const key in asyncComputed) { - const item = this.$options.asyncComputed[key] - if (isComputedLazy(item)) { - initLazy(data, key) - this.$options.computed[key] = makeLazyComputed(key) - } else { - data[key] = null - } - } - return data - } + this.$options.data = initDataWithAsyncComputed(this.$options) }, created () { for (const key in this.$options.asyncComputed || {}) { @@ -70,54 +54,76 @@ const AsyncComputed = { } for (const key in this.$options.asyncComputed || {}) { - let promiseId = 0 - const watcher = newPromise => { - const thisPromise = ++promiseId - - if (newPromise === DidNotUpdate) { - return - } - - if (!newPromise || !newPromise.then) { - newPromise = Promise.resolve(newPromise) - } - setAsyncState(this, key, 'updating') - - newPromise.then(value => { - if (thisPromise !== promiseId) return - setAsyncState(this, key, 'success') - this[key] = value - }).catch(err => { - if (thisPromise !== promiseId) return - - setAsyncState(this, key, 'error') - Vue.set(this.$data._asyncComputed[key], 'exception', err) - if (pluginOptions.errorHandler === false) return - - const handler = (pluginOptions.errorHandler === undefined) - ? console.error.bind(console, 'Error evaluating async computed property:') - : pluginOptions.errorHandler - - if (pluginOptions.useRawError) { - handler(err) - } else { - handler(err.stack) - } - }) - } - Vue.set(this.$data._asyncComputed, key, { - exception: null, - update: () => { - watcher(getterOnly(this.$options.asyncComputed[key]).apply(this)) - } - }) - setAsyncState(this, key, 'updating') - this.$watch(prefix + key, watcher, { immediate: true }) + handleAsyncComputedPropetyChanges(this, key, pluginOptions, Vue) } } }) } } +function handleAsyncComputedPropetyChanges (vm, key, pluginOptions, Vue) { + let promiseId = 0 + const watcher = newPromise => { + const thisPromise = ++promiseId + + if (shouldNotUpdate(newPromise)) return + + if (!newPromise || !newPromise.then) { + newPromise = Promise.resolve(newPromise) + } + setAsyncState(vm, key, 'updating') + + newPromise.then(value => { + if (thisPromise !== promiseId) return + setAsyncState(vm, key, 'success') + vm[key] = value + }).catch(err => { + if (thisPromise !== promiseId) return + + setAsyncState(vm, key, 'error') + Vue.set(vm.$data._asyncComputed[key], 'exception', err) + if (pluginOptions.errorHandler === false) return + + const handler = (pluginOptions.errorHandler === undefined) + ? console.error.bind(console, 'Error evaluating async computed property:') + : pluginOptions.errorHandler + + if (pluginOptions.useRawError) { + handler(err) + } else { + handler(err.stack) + } + }) + } + Vue.set(vm.$data._asyncComputed, key, { + exception: null, + update: () => { + watcher(getterOnly(vm.$options.asyncComputed[key]).apply(vm)) + } + }) + setAsyncState(vm, key, 'updating') + vm.$watch(prefix + key, watcher, { immediate: true }) +} + +function initDataWithAsyncComputed (options) { + const optionData = options.data + const asyncComputed = options.asyncComputed || {} + + return function vueAsyncComputedInjectedDataFn (vm) { + const data = ((typeof optionData === 'function') + ? optionData.call(this, vm) + : optionData) || {} + for (const key in asyncComputed) { + const item = this.$options.asyncComputed[key] + if (isComputedLazy(item)) { + initLazy(data, key) + this.$options.computed[key] = makeLazyComputed(key) + } else { + data[key] = null + } + } + return data + } +} function setAsyncState (vm, stateObject, state) { vm.$set(vm.$data._asyncComputed[stateObject], 'state', state) @@ -142,13 +148,7 @@ function getterFn (key, fn) { } if (fn.hasOwnProperty('shouldUpdate')) { - const previousGetter = getter - getter = function getter () { - if (fn.shouldUpdate.call(this)) { - return previousGetter.call(this) - } - return DidNotUpdate - } + getter = getGetterWithShouldUpdate(fn, getter) } if (isComputedLazy(fn)) { diff --git a/src/shouldUpdate.js b/src/shouldUpdate.js new file mode 100644 index 0000000..c6d0e25 --- /dev/null +++ b/src/shouldUpdate.js @@ -0,0 +1,11 @@ +const DidNotUpdate = typeof Symbol === 'function' ? Symbol('did-not-update') : {} + +export const getGetterWithShouldUpdate = (asyncProprety, currentGetter) => { + return function getter () { + return (asyncProprety.shouldUpdate.call(this)) + ? currentGetter.call(this) + : DidNotUpdate + } +} + +export const shouldNotUpdate = (value) => DidNotUpdate === value