Perfect solution for JavaScript front-end timeout asynchronous operation

Perfect solution for JavaScript front-end timeout asynchronous operation

Since the release of ECMAScript's Promise ES2015 and async/await ES2017 features, asynchrony has become a particularly common operation in the front-end world. There are some differences in the order in which asynchronous code and synchronous code process problems. Writing asynchronous code requires a different "consciousness" than writing synchronous code.

What happens if a piece of code takes a long time to execute?

If this is synchronous code, we will see a phenomenon called "unresponsiveness", or in layman's terms - "death"; but what if it is an asynchronous code? We may not get the result, but other code continues as if nothing happened.

Of course, it’s not that the thing didn’t really happen, it’s just that different phenomena will occur under different circumstances. For example, a page with a loading animation looks like it is always loading; another example is a page that should update data but you cannot see the data change;

For example, a dialog box can’t be closed no matter what... We call these phenomena BUGs. But there are also times when an asynchronous operation process does not "echo" and it dies there silently. No one knows about it, and after the page is refreshed, not even a trace will be left.

Axios comes with timeout handling

Using Axios to make Web Api calls is a common asynchronous operation process. Usually our code will be written like this:

try {
    const res = await axios.get(url, options);
    //TODO proceed with subsequent business as normal} catch(err) {
    // TODO perform fault tolerance or report an error}

This code usually works well until one day a user complains: Why is there no response after waiting for so long?

Then the developer realized that due to the increased pressure on the server, it was difficult to respond to this request instantly. Considering the user's feelings, a loading animation is added:

try {
    showLoading();
    const res = await axios.get(url, options);
    //TODO normal business} catch (err) {
    //TODO fault tolerance processing} finally {
    hideLoading();
}

However, one day, a user said: "I waited for half an hour, but it just kept going in circles!" So the developer realized that the request was stuck for some reason. In this case, the request should be resent or reported directly to the user - well, a timeout check should be added.

Fortunately, Axios can handle timeouts. Just add a timeout: 3000 in options to solve the problem. If a timeout occurs, you can detect and handle it in catch block:

try {...}
catch (err) {
    if (err.isAxiosError && !err.response && err.request
        && err.message.startsWith("timeout")) {
        // If the Axios request is wrong and the message is a delayed message // TODO handles the timeout}
}
finally {...}

Axios is fine, but what if we use fetch() ?

Handling fetch() timeouts

fetch() itself does not have the ability to handle timeouts, so we need to determine the timeout and use AbortController to trigger the "cancel" request operation.

If you need to abort a fetch() operation, simply get signal from an AbortController object and pass the signal object as an option fetch() . It's probably like this:

const ac = new AbortController();
const { signal } = ac;
fetch(url, { signal }).then(res => {
    //TODO handle business});
 
// Cancel the fetch operation after 1 second setTimeout(() => ac.abort(), 1000);

ac.abort() will send a signal to signal , trigger its abort event, and set its .aborted property to true . fetch() internal processing uses this information to abort the request.

The example above demonstrates how to implement timeout handling for fetch() operations. If you use await to handle it, you need to put setTimeout(...) before fetch(...) :

const ac = new AbortController();
const { signal } = ac;
setTimeout(() => ac.abort(), 1000);
const res = await fetch(url, { signal }).catch(() => undefined);

In order to avoid using try ... catch ... to handle request failures, a .catch(...) is added after fetch() to ignore errors. If an error occurs, res will be assigned the value undefined . Actual business processing may require more reasonable catch() processing to allow res to contain identifiable error information.

We could have ended here, but writing such a long piece of code for each fetch() call would be cumbersome, so let's encapsulate it:

async function fetchWithTimeout(timeout, resoure, init = {}) {
    const ac = new AbortController();
    const signal = ac.signal;
    setTimeout(() => ac.abort(), timeout);
    return fetch(resoure, { ...init, signal });
}

Is that ok? No, there is a problem.

If we output a message in setTimeout(...) of the above code:

setTimeout(() => {
    console.log("It's timeout");
    ac.abort();
}, timeout);

And give enough time for the call:

fetchWithTimeout(5000, url).then(res => console.log("success"));

We will see the output success and after 5 seconds we will see the output It's timeout .

By the way, although we handled the timeout for fetch(...) , we did not kill timer when fetch(...) succeeded. How could a thoughtful programmer make such a mistake? Kill him!

async function fetchWithTimeout(timeout, resoure, init = {}) {
    const ac = new AbortController();
    const signal = ac.signal;    
    const timer = setTimeout(() => {
        console.log("It's timeout");
        return ac.abort();
    }, timeout);    
    try {
        return await fetch(resoure, { ...init, signal });
    finally
        clearTimeout(timer);
    }
}

Perfect! But the problem is not over yet.

Everything can time out

Both Axios and fetch provide ways to interrupt asynchronous operations, but what about a normal Promise that does not have abort capability?

For such a Promise, I can only say, let him go, let him do it till the end of time - I can't stop him anyway. But life has to go on, I can't keep waiting!

In this case, we can encapsulate setTimeout() into a Promise, and then use Promise.race() to implement "no waiting after the time has expired":

Race means racing, so the behavior of Promise.race() is easy to understand, right?

function waitWithTimeout(promise, timeout, timeoutMessage = "timeout") {
    let timer;
    const timeoutPromise = new Promise((_, reject) => {
        timer = setTimeout(() => reject(timeoutMessage), timeout);
    }); 
    return Promise.race([timeoutPromise, promise])
        .finally(() => clearTimeout(timer)); // Don't forget to clear the timer
}

You can write a Timeout to simulate the effect:

(async () => {
    const business = new Promise(resolve => setTimeout(resolve, 1000 * 10));
    try {
        await waitWithTimeout(business, 1000);
        console.log("[Success]");
    } catch (err) {
        console.log("[Error]", err); // [Error] timeout
    }
})();

The above is the detailed content of the perfect solution to JavaScript front-end timeout asynchronous operation. For more information on solving front-end timeout asynchronous operations, please pay attention to other related articles on 123WORDPRESS.COM!

You may also be interested in:
  • Summary of several common processing methods for JavaScript asynchronous operations
  • Three solutions for asynchronous loading of js
  • A brief discussion on how to handle JavaScript asynchronous errors gracefully
  • Learn JavaScript from me to solve asynchronous programming exceptions
  • Correct handling of Vue axios request timeout

<<:  The latest Linux installation process of tomcat8

>>:  Example of utf8mb4 collation in MySQL

Recommend

Implementation example of video player based on Vue

When the existing video player cannot meet the ne...

Understanding and solutions of 1px line in mobile development

Reasons why the 1px line becomes thicker When wor...

New settings for text and fonts in CSS3

Text Shadow text-shadow: horizontal offset vertic...

Vue advanced usage tutorial dynamic components

Table of contents Basic description AST parsing R...

Implementing simple tabs with js

Tab selection cards are used very frequently on r...

How to manage large file uploads and breakpoint resume based on js

Table of contents Preface Front-end structure Bac...

Centos7 install mysql5.6.29 shell script

This article shares the shell script of mysql5.6....

Mysql Sql statement comments

You can add comments to MySQL SQL statements. Her...

Mysql Workbench query mysql database method

Mysql Workbench is an open source database client...

Talking about Less and More in Web Design (Picture)

Less is More is a catchphrase for many designers....

Discuss the application of mixin in Vue

Mixins provide a very flexible way to distribute ...

Detailed explanation of Nginx access restriction configuration

What is Nginx access restriction configuration Ng...