首页 > 解决方案 > 以与搜索功能请求相同的顺序获取 axios 响应

问题描述

我目前正在使用 axios 在 React Native 中开发搜索功能。

在实现搜索功能时,我使用 lodash 的 debounce 来限制发送的请求数量。

但是,由于不是以相同的顺序接收请求响应,因此可能会显示不正确的搜索结果。

例如,当用户在输入字段中输入“家居装饰”时,将有两个请求。

一个带有“Home”的请求,下一个带有“Home deco”作为搜索查询文本的请求。

如果带有“Home”的请求比第二个请求需要更多的时间来返回,我们最终将显示“Home”查询文本而不是“Home deco”的结果

如果响应按顺序返回,但如果在“Home deco”请求之后返回“Home 请求,则应按顺序向用户显示这两个结果,则应忽略“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>
    )

}

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

标签: javascriptajaxreact-nativeaxios

解决方案


如果您想避免使用外部库来减小包大小,例如axios-hooks,我认为您最好使用 axios 中包含的 CancelToken 功能。

正确使用 CancelToken 功能还将防止任何警告对未能取消异步任务做出反应。

Axios 有一个很好的页面来解释如何在这里使用 CancelToken 功能。如果您想更好地了解它的工作原理以及它为何有用,我建议您阅读。

以下是我将如何在您提供的示例中实现 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 想要发生的事情,同时还包括在组件卸载时正确取消响应。


推荐阅读