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

Install mysql5.7.17 using RPM under Linux

The installation method of MySQL5.7 rpm under Lin...

Tutorial on how to create a comment box with emoticons using HTML and CSS

HTML comment box with emoticons. The emoticons ar...

Completely delete MySQL steps

Table of contents 1. Stop MySQL Server first 2. U...

Steps to transfer files and folders between two Linux servers

Today I was dealing with the issue of migrating a...

Detailed steps for creating a Vue scaffolding project

vue scaffolding -> vue.cli Quickly create a la...

VMware Workstation Installation (Linux Kernel) Kylin Graphic Tutorial

This article shares with you how to install Kylin...

javascript countdown prompt box

This article example shares the specific code of ...

Web page creation for beginners: Learn to use HTML's hyperlink A tag

The hyperlink a tag represents a link point and i...

Some findings and thoughts about iframe

This story starts with an unexpected discovery tod...

JS implements a simple brick-breaking pinball game

This article shares the specific code of JS to im...

iFrame is a great way to use it as a popup layer to cover the background

I have been working on a project recently - Budou ...