React.js和Redux-如何防止实时搜索组件进行过多的api调用? [英] Reactjs and redux - How to prevent excessive api calls from a live-search component?

查看:87
本文介绍了React.js和Redux-如何防止实时搜索组件进行过多的api调用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我创建了这个实时搜索组件:

I have created this live-search component:

class SearchEngine extends Component {
  constructor (props) {
      super(props);
      this.handleChange = this.handleChange.bind(this);
      this.handleSearch = this.handleSearch.bind(this);
  }
  handleChange (e) {
      this.props.handleInput(e.target.value); //Redux
  }
  handleSearch (input, token) {
     this.props.handleSearch(input, token) //Redux
  };
  componentWillUpdate(nextProps) {
      if(this.props.input !== nextProps.input){
          this.handleSearch(nextProps.input,  this.props.loginToken);
      }
  }
  render () {
      let data= this.props.result;
      let searchResults = data.map(item=> {
                  return (
                      <div key={item.id}>
                              <h3>{item.title}</h3>
                              <hr />
                              <h4>by {item.artist}</h4>
                              <img alt={item.id} src={item.front_picture} />
                      </div>
                  )
              });
      }
      return (
          <div>
              <input name='input'
                     type='text'
                     placeholder="Search..."
                     value={this.props.input}
                     onChange={this.handleChange} />
              <button onClick={() => this.handleSearch(this.props.input, this.props.loginToken)}>
                  Go
              </button>
              <div className='search_results'>
                  {searchResults}
              </div>
          </div>
      )
}

它是React& amp;的一部分我正在使用的Redux应用程序已连接到Redux商店. 事实是,当用户键入搜索查询时,它会为输入中的每个字符触发一个API调用,并创建过多的API调用,从而导致出现诸如显示先前查询的结果(而不是跟进当前查询的结果)之类的错误.搜索输入.

It is part of a React & Redux app I'm working on and is connected to the Redux store. The thing is that when a user types in a search query, it fires an API call for each of the characters in the input, and creating an excessive API calling, resulting in bugs like showing results of previous queries, not following up with the current search input.

我的api调用(this.props.handleSearch):

My api call (this.props.handleSearch):

export const handleSearch = (input, loginToken) => {
  const API= `https://.../api/search/vector?query=${input}`;
  }
  return dispatch => {
      fetch(API, {
          headers: {
              'Content-Type': 'application/json',
              'Authorization': loginToken
          }
      }).then(res => {
          if (!res.ok) {
              throw Error(res.statusText);
          }
          return res;
      }).then(res => res.json()).then(data => {
          if(data.length === 0){
              dispatch(handleResult('No items found.'));
          }else{
              dispatch(handleResult(data));
          }
      }).catch((error) => {
          console.log(error);
         });
      }
};

我的意图是进行实时搜索,并根据用户输入进行更新.但我试图找到一种方法来等待用户完成输入,然后应用更改以防止过多的API调用和错误.

My intention is that it would be a live-search, and update itself based on user input. but I am trying to find a way to wait for the user to finish his input and then apply the changes to prevent the excessive API calling and bugs.

建议?

这对我有用. 感谢 Hammerbot的惊人答案,我设法创建了自己的QueueHandler类.

Here's what worked for me. Thanks to Hammerbot's amazing answer I managed to create my own class of QueueHandler.

export default class QueueHandler {

constructor () { // not passing any "queryFunction" parameter
    this.requesting = false;
    this.stack = [];
}

//instead of an "options" object I pass the api and the token for the "add" function. 
//Using the options object caused errors.

add (api, token) { 
    if (this.stack.length < 2) {
        return new Promise ((resolve, reject) => {
            this.stack.push({
                api,
                token,
                resolve,
                reject
            });
            this.makeQuery()
        })
    }
    return new Promise ((resolve, reject) => {
        this.stack[1] = {
            api,
            token,
            resolve,
            reject
        };
        this.makeQuery()
    })

}

makeQuery () {
    if (! this.stack.length || this.requesting) {
        return null
    }

    this.requesting = true;
// here I call fetch as a default with my api and token
    fetch(this.stack[0].api, {
        headers: {
            'Content-Type': 'application/json',
            'Authorization': this.stack[0].token
        }
    }).then(response => {
        this.stack[0].resolve(response);
        this.requesting = false;
        this.stack.splice(0, 1);
        this.makeQuery()
    }).catch(error => {
        this.stack[0].reject(error);
        this.requesting = false;
        this.stack.splice(0, 1);
        this.makeQuery()
    })
}
}

为了使它对我有用(请参阅注释),我进行了一些更改.

I made a few changes in order for this to work for me (see comments).

我导入了它并分配了一个变量:

I imported it and assigned a variable:

//searchActions.js file which contains my search related Redux actions

import QueueHandler from '../utils/QueueHandler';

let queue = new QueueHandler();

然后在我原来的handleSearch函数中:

Then in my original handleSearch function:

export const handleSearch = (input, loginToken) => {
  const API= `https://.../api/search/vector?query=${input}`;
  }
  return dispatch => {
    queue.add(API, loginToken).then...  //queue.add instead of fetch.

希望这对任何人都有帮助!

Hope this helps anyone!

推荐答案

我认为它们是解决该问题的几种策略.我将在这里谈论3种方式.

I think that they are several strategies to handle the problem. I'm going to talk about 3 ways here.

前两种方式是调节"和消除"输入.这里有一篇很好的文章介绍了不同的技术: https://css-tricks .com/debouncing-throttling-explained-examples/

The two first ways are "throttling" and "debouncing" your input. There is a very good article here that explains the different techniques: https://css-tricks.com/debouncing-throttling-explained-examples/

防抖动等待给定的时间,以实际执行您要执行的功能.如果在给定的时间内您进行了相同的呼叫,它将在此给定的时间再次等待以查看是否再次呼叫.如果不这样做,它将执行该功能.这张图片对此进行了说明(摘自上述文章):

Debounce waits a given time to actually execute the function you want to execute. And if in this given time you make the same call, it will wait again this given time to see if you call it again. If you don't, it will execute the function. This is explicated with this image (taken from the article mentioned above):

节气门直接执行该函数,在给定时间等待新的调用,并执行在此给定时间内进行的最后一次调用.以下架构对此进行了说明(摘自本文 http://artemdemo.me/blog/throttling- vs-debouncing/):

Throttle executes the function directly, waits a given time for a new call and executes the last call made in this given time. The following schema explains it (taken from this article http://artemdemo.me/blog/throttling-vs-debouncing/):

我最初使用的是第一种技术,但是我发现它有一些缺点.主要的问题是我无法真正控制组件的渲染.

I was using those first techniques at first but I found some downside to it. The main one was that I could not really control the rendering of my component.

让我们想象一下以下功能:

Let's imagine the following function:

function makeApiCall () {
  api.request({
    url: '/api/foo',
    method: 'get'
  }).then(response => {
    // Assign response data to some vars here
  })
}

如您所见,请求使用异步过程,稍后将分配响应数据.现在,让我们想象两个请求,我们始终希望使用已完成的最后一个请求的结果. (这就是您在搜索输入中想要的).但是第二个请求的结果先于第一个请求的结果.这将导致您的数据包含错误的响应:

As you can see, the request uses an asynchronous process that will assign response data later. Now let's imagine two requests, and we always want to use the result of the last request that have been done. (That's what you want in a search input). But the result of the second request comes first before the result of the first request. That will result in your data containing the wrong response:

1. 0ms -> makeApiCall() -> 100ms -> assigns response to data
2. 10ms -> makeApiCall() -> 50ms -> assigns response to data

对我来说,解决方案是创建某种队列".该队列的行为是:

The solution for that to me was to create some sort of "queue". The behaviour of this queue is:

1-如果将任务添加到队列中,则该任务将排在队列的前面. 2-如果我们将第二个任务添加到队列中,则该任务将移至第二个位置. 3-如果我们将第三个任务添加到队列中,则该任务将替换第二个任务.

1 - If we add a task to the queue, the task goes in front of the queue. 2 - If we add a second task to the queue, the task goes in the second position. 3 - If we add a third task to the queue, the task replaces the second.

因此,队列中最多有两个任务.第一个任务结束后,第二个任务就会执行,依此类推...

So there is a maximum of two tasks in the queue. As soon as the first task has ended, the second task is executed etc...

因此,您始终会有相同的结果,并且您可以在许多参数的函数中限制api调用.如果用户的互联网连接速度较慢,则第一个请求将需要一些时间来执行,因此不会有很多请求.

So you always have the same result, and you limit your api calls in function of many parameters. If the user has a slow internet connexion, the first request will take some time to execute, so there won't be a lot of requests.

这是我用于此队列的代码:

Here is the code I used for this queue:

export default class HttpQueue {

  constructor (queryFunction) {
    this.requesting = false
    this.stack = []
    this.queryFunction = queryFunction
  }

  add (options) {
    if (this.stack.length < 2) {
      return new Promise ((resolve, reject) => {
        this.stack.push({
          options,
          resolve,
          reject
        })
        this.makeQuery()
      })
    }
    return new Promise ((resolve, reject) => {
      this.stack[1] = {
        options,
        resolve,
        reject
      }
      this.makeQuery()
    })

  }

  makeQuery () {
    if (! this.stack.length || this.requesting) {
      return null
    }

    this.requesting = true

    this.queryFunction(this.stack[0].options).then(response => {
      this.stack[0].resolve(response)
      this.requesting = false
      this.stack.splice(0, 1)
      this.makeQuery()
    }).catch(error => {
      this.stack[0].reject(error)
      this.requesting = false
      this.stack.splice(0, 1)
      this.makeQuery()
    })
  }
}

您可以像这样使用它:

// First, you create a new HttpQueue and tell it what to use to make your api calls. In your case, that would be your "fetch()" function:

let queue = new HttpQueue(fetch)

// Then, you can add calls to the queue, and handle the response as you would have done it before:

queue.add(API, {
    headers: {
        'Content-Type': 'application/json',
        'Authorization': loginToken
    }
}).then(res => {
    if (!res.ok) {
        throw Error(res.statusText);
    }
    return res;
}).then(res => res.json()).then(data => {
    if(data.length === 0){
        dispatch(handleResult('No vinyls found.'));
    }else{
        dispatch(handleResult(data));
    }
}).catch((error) => {
    console.log(error);
   });
}

这篇关于React.js和Redux-如何防止实时搜索组件进行过多的api调用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆