4

I have a simple Vue script, learning Vue for the first time:

<!DOCTYPE html>
<html>
<head>
  <title>My first Vue app</title>
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
  <div id="watch-example">
      <p>
          Ask a yes/no question:
          <input v-model="question" type="text">
          <img v-bind:src="image">
      </p>
      <p>{{ answer }}</p>
  </div>
  <script>
    let watchExample = new Vue({

        // Root element of app
        el   : '#watch-example',

        // Properties keep track of
        data : {
            question : '',
            answer   : 'I cannot give you an answer until you ask a question!',
            image    : '',
        },
        watch : {
            question : function(newVal, oldVal) {
                const vm  = this;
                vm.answer = 'Waitinng for you to stop typing.';

                setTimeout(function() {
                    vm.getAnswer();
                }, 350);
            }
        },

        // App methods
        methods : {
            getAnswer: function() {
                const vm = this;

                if(!vm.question.includes('?')) {
                    vm.answer = 'Questions usually contain a question mark';
                    vm.image  = '';
                    return;
                }
                vm.answer = 'Thinking...';

                setTimeout(function() {
                    axios.get('https://yesno.wtf/api')
                        .then(function (response) {
                            vm.answer = response.data.answer;
                            vm.image  = response.data.image;
                        });
                }, 500);
            }
        }
    });
  </script>
</body>
</html>

I noticed that when I type a question, containing a question mark(?) too fast, it makes multiple requests, and I get multiple responses. It clears out images upon multiple returned responses and adds new ones. If I type a question slowly, only one response is returned.

console.log(response) shows the multiple responses in the console.

How can I make only one request to get one response to a question no matter the typing speed?

tony19
  • 125,647
  • 18
  • 229
  • 307
Robert
  • 10,126
  • 19
  • 78
  • 130
  • Possible duplicate of [Can someone explain the "debounce" function in Javascript](https://stackoverflow.com/questions/24004791/can-someone-explain-the-debounce-function-in-javascript) – Alexander Staroselsky Apr 16 '19 at 23:47

3 Answers3

1

What you need here is debounce from lodash

A good example was given in the Vue documentation

Pay attention to this particular code, as an example:

created: function () {
    // _.debounce is a function provided by lodash to limit how
    // often a particularly expensive operation can be run.
    // In this case, we want to limit how often we access
    // yesno.wtf/api, waiting until the user has completely
    // finished typing before making the ajax request. To learn
    // more about the _.debounce function (and its cousin
    // _.throttle), visit: https://lodash.com/docs#debounce
    this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
  },
tony19
  • 125,647
  • 18
  • 229
  • 307
Ru Chern Chong
  • 3,692
  • 13
  • 33
  • 43
1

Currently your are just delaying your call by wrapping it in a setTimeout. I think you're trying to accomplish a debounce effect.

Lodash has this function but if you don't already use lodash and don't want to include it in your project you could easily write it yourself in a few lines of code.

Y. Gherbi
  • 890
  • 8
  • 22
1

You could just restart the timer on every change (i.e., stop the current timer if any, and then start another timer). Note that setTimeout() returns its associated timer ID, which you could pass to clearTimeout() to stop the timer. Your code should look similar to this:

watch: {
    question : function(newVal, oldVal) {
        ...

        clearTimeout(this._timerId);
        this._timerId = setTimeout(function() {
            vm.getAnswer();
        }, 350);
    }
},

<!DOCTYPE html>
<html>
<head>
  <title>My first Vue app</title>
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
</head>
<body>
  <div id="watch-example">
      <p>
          Ask a yes/no question:
          <input v-model="question" type="text">
          <img v-bind:src="image">
      </p>
      <p>{{ answer }}</p>
  </div>
  <script>
    let watchExample = new Vue({

        // Root element of app
        el   : '#watch-example',

        // Properties keep track of
        data : {
            question : '',
            answer   : 'I cannot give you an answer until you ask a question!',
            image    : '',
        },
        watch : {
            question : function(newVal, oldVal) {
                const vm  = this;
                vm.answer = 'Waitinng for you to stop typing.';

                clearTimeout(this._timerId);
                this._timerId = setTimeout(function() {
                    vm.getAnswer();
                }, 350);
            }
        },

        // App methods
        methods : {
            getAnswer: function() {
                const vm = this;

                if(!vm.question.includes('?')) {
                    vm.answer = 'Questions usually contain a question mark';
                    vm.image  = '';
                    return;
                }
                vm.answer = 'Thinking...';

                setTimeout(function() {
                    axios.get('https://yesno.wtf/api')
                        .then(function (response) {
                            vm.answer = response.data.answer;
                            vm.image  = response.data.image;
                        });
                }, 500);
            }
        }
    });
  </script>
</body>
</html>
tony19
  • 125,647
  • 18
  • 229
  • 307