javascript 以与搜索功能的请求相同的顺序获取axios响应

rhfm7lfc  于 2023-04-04  发布在  Java
关注(0)|答案(3)|浏览(244)

我目前正在使用Axios在React Native中开发搜索功能。
在实现搜索功能时,我使用lodash的debounce来限制发送的请求数量。
然而,由于请求响应不是以相同的顺序接收的,因此存在显示不正确的搜索结果的可能性。
例如,当用户在输入字段中输入“Home deco”时,将存在两个请求。
一个请求使用 'Home',下一个请求使用 *'Home deco'*作为搜索查询文本。
如果使用 'Home' 的请求比第二个请求需要更多的时间返回,我们最终将显示 'Home' 查询文本的结果,而不是 'Home deco'
如果响应按顺序返回,但如果 'Home' 请求在 'Home deco' 请求之后返回,则应忽略 'Home' 响应。
下面是一个示例代码

function Search (){
    const [results, setResults] = useState([]);
    const [searchText, setSearchText] = useState('');

    useEffect(() => {
            getSearchResultsDebounce(searchText);
    }, [searchText]);

    const getSearchResultsDebounce = useCallback(
        _.debounce(searchText => {
            getSearchResults(searchText)
        }, 1000),
        []
    );

    function getSearchResults(searchText) {

        const urlWithParams = getUrlWithParams(url, searchText);
        axios.get(urlWithParams, { headers: config.headers })
             .then(response => {
              if (response.status === 200 && response.data) 
              {
                setResults(response.data);

              } else{
                  //Handle error
              }
            })
            .catch(error => {
                //Handle error
            });
    }

    return (
     <View>
        <SearchComponent onTextChange={setSearchText}/>
        <SearchResults results={results}/>
     </View>
    )

}

解决上述问题的最佳方法是什么?

qyzbxkaa

qyzbxkaa1#

如果你想避免使用外部库来减小包的大小,比如axios-hooks,我认为你最好使用axios中包含的CancelToken功能。
正确使用CancelToken功能还可以防止任何关于取消异步任务失败的警告。
Axios有一个很好的页面解释了如何使用CancelToken功能here。如果你想更好地了解它的工作原理和为什么它是有用的,我建议阅读。
下面是我如何在你给出的例子中实现CancelToken功能:
OP在回复中澄清了他们不想实现取消功能,在这种情况下,我会使用如下的时间戳系统:

function Search () {
    //change results to be a object with 2 properties, timestamp and value, timestamp being the time the request was issued, and value the most recent results
    const [results, setResults] = useState({
        timeStamp: 0,
        value: [],
    });
    const [searchText, setSearchText] = useState('');

    //create a ref which will be used to store the cancel token
    const cancelToken = useRef();
   
    //create a setSearchTextDebounced callback to debounce the search query
    const setSearchTextDebounced = useCallback(
        _.debounce((text) => {
            setSearchText(text)
        ), [setSearchText]
    );
   
    //put the request inside of a useEffect hook with searchText as a dep
    useEffect(() => {
        //generate a timestamp at the time the request will be made
        const requestTimeStamp = new Date().valueOf();

        //create a new cancel token for this request, and store it inside the cancelToken ref
        cancelToken.current = CancelToken.source();            
        
        //make the request
        const urlWithParams = getUrlWithParams(url, searchText);
        axios.get(urlWithParams, { 
            headers: config.headers,

            //provide the cancel token in the axios request config
            cancelToken: source.token 
        }).then(response => {
            if (response.status === 200 && response.data) {
                //when updating the results compare time stamps to check if this request's data is too old
                setResults(currentState => {
                    //check if the currentState's timeStamp is newer, if so then dont update the state
                    if (currentState.timeStamp > requestTimeStamp) return currentState;
                  
                    //if it is older then update the state
                    return {
                        timeStamp: requestTimeStamp,
                        value: request.data,
                    };
                });
            } else{
               //Handle error
            }
        }).catch(error => {
            //Handle error
        });
        
        //add a cleanup function which will cancel requests when the component unmounts
        return () => { 
            if (cancelToken.current) cancelToken.current.cancel("Component Unmounted!"); 
        };
    }, [searchText]);

    return (
        <View>
            {/* Use the setSearchTextDebounced function here instead of setSearchText. */}
            <SearchComponent onTextChange={setSearchTextDebounced}/>
            <SearchResults results={results.value}/>
        </View>
    );
}

正如你所看到的,我还改变了搜索本身去抖动的方式。我改变了searchText值本身去抖动的地方,当searchText值改变时,一个带有搜索请求的useEffect钩子会运行。这样我们就可以取消以前的请求,运行新的请求,并在同一个钩子中进行卸载清理。
我修改了我的响应,希望能实现OP希望发生的事情,同时还包括在组件卸载时适当的响应取消。

9rygscc1

9rygscc12#

我认为解决这个问题最好的方法之一是使用请求的实际顺序作为状态,并根据实际顺序使每个请求都有自己的顺序。下面是伪代码概述:

const itemList = reactive([])
const searchOrder = reactive(0)

function search(query) { // call on 'input' event
  searchOrder.value++
  const localOrder = searchOrder.value

  myAxios.get('/search?query=' + query)
    .then(function (response) {
      // if the order that was assigned on this request is the same as the current order
      if (localOrder === searchOrder.value) {
        itemList.value = response // then update DOM with itemList
      }
    })
}
ffx8fchx

ffx8fchx3#

我们可以这样做来获得最新的API响应。

function search() {
    ...
    const [timeStamp, setTimeStamp] = "";
    ...

    function getSearchResults(searchText) {

        //local variable will always have the timestamp when it was called
    const reqTimeStamp = new Date().getTime();

        //timestamp will update everytime the new function call has been made for searching. so will always have latest timestampe of last api call
    setTimeStamp(reqTimeStamp)

    axios.get(...)
        .then(response => {

            // so will compare reqTimeStamp with timeStamp(which is of latest api call) if matched then we have got latest api call response 
            if(reqTimeStamp === timeStamp) {
                return result; // or do whatever you want with data
            } else {

            // timestamp did not match
            return ;
            }

        })
        
     }

}

相关问题