只让最后一个 API 调用通过 [英] let only last API call get through

查看:26
本文介绍了只让最后一个 API 调用通过的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

所以,我有这个输入框,当你在里面输入一些东西时,它会对我们的后端进行 API 调用,我遇到的问题是:

假设我们正在打字测试,我将使用它进行 4 次调用:

  • t
  • te
  • 测试
  • 测试

因为't'的调用时间比'test'要长得多,'t'的数据将在最后加载.这意味着我没有从测试"中获得请求的数据.

我的问题是,有什么办法可以取消之前的请求吗?('t', 'te', 'tes') 并且只让你的最后一个电话打通?还是这只是为了优化 API 速度的性能?

我已经尝试过半秒超时,但有时问题仍然存在.

解决方案

当您对用户输入进行异步调用时,异步结果解析的顺序可能不是用户提供输入的顺序(因此不是去抖动问题).当用户输入 s 然后你用 s 进行异步调用,然后用户输入 e 然后你用 se 进行异步调用.现在有 2 个未解决的异步调用,一个是 s,另一个是 se.

假设 s 调用需要一秒钟,se 调用需要 10 毫秒,然后 se 首先解析,UI 设置为 se 但在那之后 s 解析并且 UI 设置为 s 的结果.您现在有一个不一致的用户界面.

解决此问题的一种方法是使用 debounce,希望您永远不会收到持续时间超过 debounce 时间的异步调用,但不能保证.另一种方法是取消较旧的请求,但实现起来太麻烦,而且并非所有浏览器都支持.我在下面展示的方式只是在提出新请求时拒绝异步承诺.所以当用户输入 se 请求 sse 但当 s> 在 se 之后解析它将被拒绝,因为它被更新的请求替换了.

const REPLACED = 'REPLACED';const last = (fn) =>{const current = { 值:{} };返回 (...args) =>{现在常量 = {};current.value = 现在;返回 Promise.resolve(args).then((args) => fn(...args)).then((解决) =>current.value === 现在?解决: Promise.reject(REPLACED));};};const later = (value, time) =>新承诺((解决)=>setTimeout(() => resolve(value), time));const apiCall = (值) =>//当值长度为1时更慢值.长度 === 1?稍后(值,1000)//需要一秒钟:稍后(值,100);//需要100msconst 工作 = 最后(apiCall);const Api = ({ api, title }) =>{const [value, setValue] = React.useState('');const [apiResult, setApiResult] = React.useState('');React.useEffect(() => {api(value).then((resolve) => {console.log(title, 'resolved with', resolve);setApiResult(resolve);});}, [api, title, value]);返回 (<div><h1>{title}</h1><h3>api 结果:{apiResult}</h3><输入类型=文本"价值={价值}onChange={(e) =>setValue(e.target.value)}/>

);};const App = () =>(<div><APIapi={apiCall}title="Broken (快速输入 2 个字符)"/><Api api={working} title="工作中"/>

);ReactDOM.render(, document.getElementById('root'));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script><div id="root"></div>

So, I have this inputfield, when you type something in it, it makes an API call to our backend, the problem I have is:

Let's say we are typing test, I will make 4 calls out of this:

  • t
  • te
  • tes
  • test

because the call for 't' takes much longer than 'test', the data from 't' will be loaded in at the end. which means I don't get the requested data from 'test'.

My question is, is there any way you can cancel the previous request? ('t', 'te', 'tes') and only let your last call get through? Or is this just optimizing performance of the API speed?

I've already tried with a timeout of half a second but the problem still remains sometimes.

解决方案

When you make an async call on user input then the order the async results resolve may not be the order the user gave the inputs (so not a debounce issue). When user types s then you make an async call with s, then the user types e and you make an async call with se. There are now 2 async calls unresolved, one with s and one with se.

Say the s call takes a second and the se call takes 10 milliseconds then se resolves first and UI is set to result of se but after that s resolves and the UI is set to result of s. You now have an inconsistent UI.

One way to solve this is with debounce and hope you'll never get async calls that last longer than the debounce time but there is no guarantee. Another way is cancel older requests but that is too much of a pain to implement and not supported by all browsers. The way I show below is just to reject the async promise when a newer request was made. So when user types s and e requests s and se are made but when s resolves after se it will be rejected because it was replaced with a newer request.

const REPLACED = 'REPLACED';
const last = (fn) => {
  const current = { value: {} };
  return (...args) => {
    const now = {};
    current.value = now;
    return Promise.resolve(args)
      .then((args) => fn(...args))
      .then((resolve) =>
        current.value === now
          ? resolve
          : Promise.reject(REPLACED)
      );
  };
};
const later = (value, time) =>
  new Promise((resolve) =>
    setTimeout(() => resolve(value), time)
  );
const apiCall = (value) =>
  //slower when value length is 1
  value.length === 1
    ? later(value, 1000) //takes a second
    : later(value, 100); //takes 100ms
const working = last(apiCall);
const Api = ({ api, title }) => {
  const [value, setValue] = React.useState('');
  const [apiResult, setApiResult] = React.useState('');
  React.useEffect(() => {
    api(value).then((resolve) => {
      console.log(title, 'resolved with', resolve);
      setApiResult(resolve);
    });
  }, [api, title, value]);
  return (
    <div>
      <h1>{title}</h1>
      <h3>api result: {apiResult}</h3>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
    </div>
  );
};
const App = () => (
  <div>
    <Api
      api={apiCall}
      title="Broken (type 2 characters fast)"
    />
    <Api api={working} title="Working" />
  </div>
);
ReactDOM.render(<App />, document.getElementById('root'));

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

这篇关于只让最后一个 API 调用通过的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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