What is a race? How to gracefully solve the problem of out-of-order interface response? How do I cancel the network request triggered by fetch()? A higher-order function will do it

What is a race

First, what is a race for an asynchronous task? The front end encounters races in slightly different scenarios and handles them differently than the back end, but the root cause of races is the same: asynchronous tasks are uncontrollable and unpredictable. See figure

task3
task5

This is where state competition comes in, because it’s very likely that the results you render when task5 responds will be overwritten by task4’s response and then overwritten by Task3’s response.

As a general business design, we should keep only the query results from Task5. This requires that a previously launched competing task be terminated when a new task is started

The ideal result is shown below:

To achieve this effect, use xmlHttprequest.abort (), or use a tripartite library such as Axios. Network requests initiated using FETCH cannot be terminated on the client side. But we can think differently and encapsulate a Promise that can be broken.

InterruptablePromise

In business development, a interruptible Promise can sometimes make all the difference.

function task() {
  let cancel;
  const cancelablePromise = new Promise(function (resolve, reject) {
    cancel = reject
    // do sth.
    do_sth()
  })
  cancelablePromise.cancel = cancel;
  return cancelablePromise;
}

const p = task();
p.cancel();
Copy the code

Note that the execution of the do_sth() function is not interrupted. The only thing that’s broken is const p, which is a promise. If p is in an resolved state before p.canel () is called, p.canel will do nothing.

Isolate with higher-order functionsDirty operation

As mentioned above, breaking promises externally is a dirty operation that must be used with extreme caution.

Next, I show you how to isolate dirty operations into a relatively safe space by wrapping higher-order functions. Let a pheasant become a phoenix.

function autoCancel(fn, onCancelErr = null) {
  const cancelSymbol = Symbol('cancel');
  let running;
  return async function r(. args) {
    running && running()
    running = null;
    const result = await Promise.race([fn(...args), (function () {
      return new Promise(function (resolve) {
        running = resolve.bind(undefined, cancelSymbol)
      })
    })()]);
    if (result === cancelSymbol) {
      if (onCancelErr) {
        throw onCancelErr
      }
      return null;
    }
    returnresult; }}Copy the code

In 20 lines of code, you have a higher-order function that seals a powerful beast inside.

The principle is simple, leveraging the promise.race feature to resolve the second Promise first. When the previous task has not completed and a new task is created, undefined is returned (or onCancelErr is thrown incorrectly).

Here’s how to harness the beast:

const queryList = autoCancel(async function(param) {
  const resp = await fetch(`/query? name=${param}`);
  return await resp.json();
}, new Error('canceled'));

try {
  const result = await queryList('helo');
} catch(e) {
  // do sth
}
Copy the code

It is also important to note that the interrupted request is just a promise. The actual network request is not terminated. It will return normally, but the result is ignored.

example

Here is a complete example:

As shown above, Task5 starts last but finishes first. The results of task1-4 should be ignored and only the return values of Task5 should be retained.

Complete sample code

The last

If my sharing is helpful to you, you can follow my official account DevStudio and check the high-quality technology sharing as soon as possible.