最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Throttle or debounce async calls in Vue 2 while passing arguments to debounced function - Stack Overflow

programmeradmin4浏览0评论

I have a Vue 2 application that uses an array of objects to back a search/multiselect widget provided by vue-multiselect.

I have looked at the Vue 1 -> 2 migration guide on debouncing calls, but the example they give did not propagate the arguments from the DOM elements to the business logic.

Right now the select fires change events with every keystroke, but I would like to throttle this (EG with lodash#throttle) so I'm not hitting my API every few milliseconds while they're typing.

import {mapGetters} from 'vuex';
import { throttle } from 'lodash';

import Multiselect from 'vue-multiselect'

export default {
  ponents: {
    Multiselect
  },
  data() {
    return {
      selectedWork: {},
      works: [],
      isLoading: false
    }
  },
  puted: {
    ...mapGetters(['worksList']),
  },
  methods: {
    getWorksAsync: throttle((term) => {
      // the plan is to replace this with an API call
      this.works = this.worksList.filter(work => titleMatches(work, term));
    }, 200)
  }
}

Problem: when the user types in the select box, I get the error:

TypeError: Cannot read property 'filter' of undefined

which is happening because this.worksList is undefined inside the throttle function.

Curiously, when I use the dev tools debugger, this.worksList has the value I need to dereference, with this referring to the Vue ponent.

Currently I am not calling the API from within the ponent, but the problem remains the same:

  1. How can I throttle this call, and have the proper this context to update my this.works list? EDIT: this is explained in Vue Watch doesnt Get triggered when using axios
  2. I also need to capture the user's query string from the multiselect widget to pass to the API call.

What is the proper pattern in Vue 2?

I have a Vue 2 application that uses an array of objects to back a search/multiselect widget provided by vue-multiselect.

I have looked at the Vue 1 -> 2 migration guide on debouncing calls, but the example they give did not propagate the arguments from the DOM elements to the business logic.

Right now the select fires change events with every keystroke, but I would like to throttle this (EG with lodash#throttle) so I'm not hitting my API every few milliseconds while they're typing.

import {mapGetters} from 'vuex';
import { throttle } from 'lodash';

import Multiselect from 'vue-multiselect'

export default {
  ponents: {
    Multiselect
  },
  data() {
    return {
      selectedWork: {},
      works: [],
      isLoading: false
    }
  },
  puted: {
    ...mapGetters(['worksList']),
  },
  methods: {
    getWorksAsync: throttle((term) => {
      // the plan is to replace this with an API call
      this.works = this.worksList.filter(work => titleMatches(work, term));
    }, 200)
  }
}

Problem: when the user types in the select box, I get the error:

TypeError: Cannot read property 'filter' of undefined

which is happening because this.worksList is undefined inside the throttle function.

Curiously, when I use the dev tools debugger, this.worksList has the value I need to dereference, with this referring to the Vue ponent.

Currently I am not calling the API from within the ponent, but the problem remains the same:

  1. How can I throttle this call, and have the proper this context to update my this.works list? EDIT: this is explained in Vue Watch doesnt Get triggered when using axios
  2. I also need to capture the user's query string from the multiselect widget to pass to the API call.

What is the proper pattern in Vue 2?

Share Improve this question edited Jul 14, 2022 at 1:20 tony19 139k23 gold badges277 silver badges347 bronze badges asked Apr 20, 2018 at 18:51 Matt MorganMatt Morgan 5,3134 gold badges23 silver badges33 bronze badges 2
  • Possible duplicate of Vue Watch doesnt Get triggered when using axios – zero298 Commented Apr 23, 2018 at 15:52
  • @zero298 I think the issue of scope (arrow fn vs function) is the same as in the issue you referenced. However, the issue of getting the query string value from the UI when it's not bound to the model was an additional wrinkle. Maybe they should be two separate issues, but it seemed like this was potentially a situation others might find themselves in. – Matt Morgan Commented Apr 23, 2018 at 18:29
Add a ment  | 

2 Answers 2

Reset to default 2

I ran into the same issue when using lodash.debounce. I'm a huge fan of arrow syntax, but I discovered that it was causing _.throttle() and _.debounce(), etc. to fail.

Obviously my code differs from yours, but I have done the following and it works:

export default {
   ...,
   methods: {
     onClick: _.debounce(function() {
       this.$emit('activate', this.item)
     }, 500)
  }
}

Even though I'm not using arrow syntax here, this still references the ponent inside the debounced function.

In your code, it'd look like this:

export default {
  ...,
  methods: {
    getWorksAsync: throttle(function(term) {
      // the plan is to replace this with an API call
      this.works = this.worksList.filter(work => titleMatches(work, term));
    }, 200)
  }
}

Hope that helps!

I was unable to find an answer on SO (or anywhere) for this, but I eventually cobbled it together through trial and error, and from related materials here and in the docs.

Things that work that I didn't do, and why

It is possible to get get the value directly using a JavaScript DOM query, and it is also possible to dig in to the multiselect ponent's structure and get the value. The first solution circumvents the framework, the second depends on undocumented attributes of the multiselect ponent. I am avoiding both of those solutions as non-idiomatic and brittle.

My current solution

  1. Updated an attribute on the ponent whenever there was a change event in the search box. This allowed me to capture the user's query string.
  2. Called my throttled async function from inside the event listener.
  3. Passed a regular function instead of an arrow function to throttle, which gave the correct this (the Vue ponent.)

If anyone has a suggestion for a better way to do this in Vue 2, I'm all ears.

Here's what my solution looked like in the end:

<template>    
  <div>
    <label
      class="typo__label"
      for="ajax">Async select</label>
    <multiselect
      id="ajax"
      v-model="selectedWork"
      label="title"
      track-by="id"
      placeholder="Type to search"
      :options="works"
      :searchable="true"
      :loading="isLoading"
      :internal-search="false"
      :multiple="false"
      :clear-on-select="true"
      :close-on-select="true"
      :options-limit="300"
      :limit="3"
      :limit-text="limitText"
      :max-height="600"
      :show-no-results="false"
      open-direction="bottom"
      @select="redirect"
      @search-change="updateSearchTerm">
      <span slot="noResult">Oops! No elements found. Consider changing the search query.</span>
    </multiselect>
  </div>
</template>

<script>
  import {mapGetters} from 'vuex';
  import { throttle } from 'lodash';

  import Multiselect from 'vue-multiselect'

  export default {
    ponents: {
      Multiselect
    },
    data() {
      return {
        searchTerm: '',
        selectedWork: {},
        works: [],
        isLoading: false
      }
    },
    puted: {
      ...mapGetters(['worksList']),
    },
    methods: {
      limitText(count) {
        return `and ${count} other works`;
      },
      redirect(work) {
        // redirect to selected page
      },
      updateSearchTerm(term){
        this.searchTerm = term;
        this.isLoading = true;
        this.getWorksAsync();
      },
      getWorksAsync: throttle(function() {
        const term = this.searchTerm.toLowerCase();
        callMyAPI(term)
        .then(results => {
            this.works = results;
            this.isLoading = false;
        })
      }, 200)
    }
  }

</script>

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论