From f9c5478bbaea694a698e8dcb8362185b7b9843f2 Mon Sep 17 00:00:00 2001 From: norami Date: Tue, 18 Sep 2018 14:55:39 +0900 Subject: [PATCH] Non-debounce option --- README.md | 26 ++++++++++++++++++++ src/index.js | 29 ++++++++++++++++++++-- test/index.js | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a2030cc..774bb89 100644 --- a/README.md +++ b/README.md @@ -323,6 +323,32 @@ new Vue({ } ``` +## Cancel debouncing + +Normally, computed properties work with debouncing +in the way the result of a change is not committed if other changes are triggered before the result is committed. +For example, if a source property is continuously changing every 100ms and API call takes 500ms, +normal async computed properties will not be changed until the change of the source property stops. +With non-debounce option, async computed properties will be updated with the last source value. + +```js +new Vue({ + data: { + searchText: "" + }, + asyncComputed: { + calculatedValue: { + debounce: false, + get () { + return fetch.get('/some-api/' + searchText) + .then(response => response.data.value) + } + } + } +} +``` + + ## 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. diff --git a/src/index.js b/src/index.js index 13d5141..6194d79 100644 --- a/src/index.js +++ b/src/index.js @@ -58,8 +58,15 @@ const AsyncComputed = { } for (const key in this.$options.asyncComputed || {}) { + const item = this.$options.asyncComputed[key] + const debounce = (pluginOptions.debounce == null && item.debounce == null) ? null : (pluginOptions.debounce !== false && item.debounce !== false) let promiseId = 0 + let reflectedPromiseId = 0 this.$watch(prefix + key, newPromise => { + if (debounce === true && promiseId !== reflectedPromiseId) { + return + } + const thisPromise = ++promiseId if (newPromise === DidNotUpdate) { @@ -71,10 +78,28 @@ const AsyncComputed = { } newPromise.then(value => { - if (thisPromise !== promiseId) return + if (debounce == null) { + if (thisPromise !== promiseId) { + return + } + } else { + if (thisPromise <= reflectedPromiseId) { + return + } + reflectedPromiseId = thisPromise + } this[key] = value }).catch(err => { - if (thisPromise !== promiseId) return + if (debounce == null) { + if (thisPromise !== promiseId) { + return + } + } else { + if (thisPromise <= reflectedPromiseId) { + return + } + reflectedPromiseId = thisPromise + } if (pluginOptions.errorHandler === false) return diff --git a/test/index.js b/test/index.js index cd9e578..ebe87d2 100644 --- a/test/index.js +++ b/test/index.js @@ -716,3 +716,70 @@ test("shouldUpdate works with lazy", t => { }) }) }) + +test("Async computed values are consistent in repeatable change in non-debounce mode", t => { + t.plan(2) + const wait = (time) => new Promise(resolve => { + setTimeout(() => { + resolve() + }, time) + }) + const updateInterval = 40 + const apiExecutionTimes = [20, 70, 120, 70, 20] + const completedTimes = apiExecutionTimes.map((executionTime, index) => { + return index * updateInterval + executionTime + }) + let lastCompletedIndex = -1 + const expectedValue = completedTimes.map((completedTime, index) => index) + .sort((a, b) => completedTimes[a] - completedTimes[b]) + .filter(index => { + if (index > lastCompletedIndex) { + lastCompletedIndex = index + return true + } + }) + console.log('completedTimes', completedTimes) + console.log('expectedValue', expectedValue) + const apiResultValue = [] + let apiCalllCount = 0 + + const mockApi = async (value) => { + const executionTime = apiExecutionTimes[apiCalllCount] + ++apiCalllCount + await wait(executionTime) + return value + } + + const vm = new Vue({ + created () { + console.log('length', apiExecutionTimes.length); + (async () => { + for (let i = 1; i < apiExecutionTimes.length; ++i) { + await wait(updateInterval) + this.srcValue = this.srcValue + 1 + } + await wait(apiExecutionTimes.slice(-1)[0] + 10) + t.deepEqual(apiResultValue, expectedValue) + t.equal(vm.dstValue, expectedValue.slice(-1)[0]) + })() + }, + data () { + return { + srcValue: 0, + } + }, + watch: { + dstValue (dstValue) { + apiResultValue.push(dstValue) + }, + }, + asyncComputed: { + dstValue: { + debounce: false, + get () { + return mockApi(this.srcValue) + }, + }, + }, + }) +})