Search with axios: cancel previous request when new character

I was able to get this to work.. The trick was to check if the cancel token existed before kicking off the API call, among other things.. I had to move the CancelToken and cancel variables outside of the Vue object/component..


This example searches GitHub for repositories...

var cancel;
var CancelToken = axios.CancelToken;

new Vue({
  el: "#app",
  data: {
    query: "",
    results: "",
    isLoading: false
  },
  methods: {
    clear() {
      this.isLoading = false;
      this.results = "";
      this.query = "";
    },

    handleSearch: _.debounce(function() {
      this.preApiCall();
    }, 300),

    preApiCall() {
      if (cancel != undefined) {
        cancel();
        console.log("cancelled");
      }
      this.apiCall(this.query);
    },

    apiCall(query) {
      if (query !== "") {
        this.isLoading = true;
        axios({
          method: "get",
          url: "https://api.github.com/search/repositories",
          cancelToken: new CancelToken(function executor(c) {
            cancel = c;
          }),
          params: {
            q: query
          }
        }).then(res => {
          this.results = JSON.parse(JSON.stringify(res.data.items));
          this.isLoading = false;
        }).catch(err => {
          this.results = err.message;
          throw Error(err.message);
          this.isLoading = false;
        });
      } else {
        this.clear();
      }
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

<div id="app">
  <input v-model="query" @keyup.stop="handleSearch" type="text" class="form-control" placeholder="Search">
  <button @click.stop="clear">Clear</button>
  <div v-if="isLoading">Loading...</div>
  <ul v-if="results !== ''">
    <li v-for="(r, index) in results" :key="index">
      {{ r.name }}
    </li>
  </ul>
</div>

[CodePen mirror]


Cancelled requests:

enter image description here


The problem is that you're creating a new canceltoken in the newSearch and canceling that instead of the original one. If you save the source on the Vue component you can check in newSearch if it exists and only cancel then. When the post Promise has completed you delete the source again so you can't cancel when it's not needed (or possible).

{
  searchItems() {
      let filters = {
          q: this.query
      };

      const CancelToken = axios.CancelToken;
      this.searchItemsSource = CancelToken.source();

      axios.post('/Items/find', filters, {
          cancelToken: this.searchItemsSource.token
      })
      .catch(function(thrown) {
          if (axios.isCancel(thrown)) {
              console.log('Request canceled', thrown.message);
          }
      })
      .then(
          response => {
              this.searchItemsSource = undefined;
              if(this.Items!=null){
                  response.data.forEach(element => {
                      this.Items.push(element);
                  });
              }
              else
                  this.Items=response.data;
          }
      );
  },
  newSearch(){
      if (this.searchItemsSource) {
          this.searchItemsSource.cancel('Cancel previous request');
      }
      this.countItems();
      this.searchItems();
  },
}

A side note about this code; I'd actually move the canceling of the previous request into the searchItems method since it never makes sense to have two running call's at the same time. It would look more like this:

{
  searchItems() {
      let filters = {
          q: this.query
      };

      if (this.searchItemsSource) {
          this.searchItemsSource.cancel('Cancel previous request');
      }
      const CancelToken = axios.CancelToken;
      this.searchItemsSource = CancelToken.source();

      axios.post('/Items/find', filters, {
          cancelToken: this.searchItemsSource.token
      })
      .catch(function(thrown) {
          if (axios.isCancel(thrown)) {
              console.log('Request canceled', thrown.message);
          }
      })
      .then(
          response => {
              this.searchItemsSource = undefined;
              if(this.Items!=null){
                  response.data.forEach(element => {
                      this.Items.push(element);
                  });
              }
              else
                  this.Items=response.data;
          }
      );
  },
  newSearch(){
      this.countItems();
      this.searchItems();
  },
}

At this point you can ask yourself if the newSearch method is needed at all, you might remove it and move the countItems call into searchItems as well. This all depends on the rest of your code and functionality though.

Tags:

Vue.Js

Axios