Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
107 views
in Technique[技术] by (71.8m points)

javascript - let only last API call get through

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'.

enter image description here

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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>

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...