Why don't you do it when the browser is free instead of setInterval?
Tags: webdev, javascript, react, typescript
Published: 2023-02-09 14:12:34

You may have the browser execute periodically with `setInterval` .
For example, periodically check whether the access token is valid or not and refresh it.

![Busy Man](https://storage.googleapis.com/zenn-user-upload/dddcfa94a97c-20230205.png)

The browsers is busy executing JavaScript and updating the UI to display page content. We want not to affect these important tasks.

In this article, I will show you how to make a process run periodically **without affecting important browser tasks as much as possible**, using access token rotation as an example.

The code example shows the following processes by using React Hooks.

1. check if the access token is valid while the browser is idle, and get a new token if it is invalid
2. schedule the process 1 to be executed periodically, so that it will be executed again a few seconds after the process 1 is completed
3. force checking to performed once every few seconds when the access token check is not running because the browser is too busy

Use the library `idle-task` (v3.3.3) to run the process while the browser is idle.

https://github.com/hiroki0525/idle-task

This library internally uses a Web API called [`requestIdleCallback`](https://developer.mozilla.org/ja/docs/Web/API/Window/requestIdleCallback). 

## Source Code

Before showing you the source code, let me first show you the screen we are going to implement.

https://8hic7w.csb.app/

[CodeSandbox](https://codesandbox.io/s/useaccesstokenwhenidle-8hic7w?from-embed)

If you leave the screen alone for a while, you will see a new token at the "now token is ~" point.

The implementation of the React Hooks is as follows.

```tsx
import { useEffect, useState } from "react";
import { setIdleTask, cancelIdleTask } from "idle-task";

interface MockResponse {
  readonly status: number;
  readonly json: () => Promise<any>;
}

const mockFetchCheckAccessToken = async (
  _token: string
): Promise<MockResponse> => {
  if (Math.random() < 0.3) {
    return {
      status: 401,
      json: async () => null
    }
  }
  return {
    status: 200,
    json: async () => null
  };
};

const mockFetchRefreshToken = async (
  _oldToken: string
): Promise<MockResponse> => {
  return {
    status: 200,
    json: async () =>
      [... .Array(10)].map(() => Math.floor(Math.random() * 10)).join("")
  };
};

const useAccessTokenWhenIdle = (): string => {
  const [nowToken, setNowToken] = useState("none");

  useEffect(() => {
    const checkAccessToken = async (): Promise<void> => {
      const checkTokenResponse = await mockFetchCheckAccessToken(nowToken);
      if (checkTokenResponse.status === 200) {
        return;
      }
      const newTokenResponse = await mockFetchRefreshToken(nowToken);
      const newToken = await newTokenResponse.json();
      console.log("refresh", newToken);
      setNowToken(newToken);
    };
    const idleTaskId = setIdleTask(checkAccessToken, {
      revalidateInterval: 5000
    });
    return () => {
      cancelIdleTask(idleTaskId);
    };
  }, [nowToken]);

  return nowToken;
};

export default useAccessTokenWhenIdle;
```

Here is the key code.

```tsx
    const idleTaskId = setIdleTask(checkAccessToken, {
      revalidateInterval: 5000
    });
```

With [`setIdleTask`](https://github.com/hiroki0525/idle-task#setidletask), you can register a function that you want to execute while the browser is idle.
`checkAccessToken` will be executed while the browser is idle.

The option `revalidateInterval` is specified.
This will schedule the function to run again while the browser is idle after the specified time.
In this example, it means that schedule `checkAccessToken` to run in 5 seconds while the browser is idle.

In this way, you can schedule the process to run periodically while the browser is idle.

>1. check if the access token is valid while the browser is idle, and get a new token if it is invalid
>2. schedule the process 1 to be executed periodically, so that it will be executed again a few seconds after the process 1 is completed

We have implemented 1 and 2 as described.
While 1 and 2 are sufficient, there is a concern.
It is that **the browser is too busy and do not always become idle**.

>3. force checking to performed once every few seconds when the access token check is not running because the browser is too busy

This is to guarantee that the process runs as much as possible.

Let's look at the code on the use side of the `useAccessTokenWhenIdle` hooks.

```tsx
import ". /styles.css";
import useAccessTokenWhenIdle from ". /useAccessTokenWhenIdle";
import { configureIdleTask } from "idle-task";

configureIdleTask({
  debug: true,
  interval: 1000 * 10
});

export default function App() {
  const token = useAccessTokenWhenIdle();

  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      Now token is {token}
    </div>
  );
}
```

The point is this.

```tsx
configureIdleTask({
  debug: true,
  interval: 1000 * 10
});
```

The [`configureIdleTask`](https://github.com/hiroki0525/idle-task#configureidletask) function is specified with an interval.
This will execute the function registered with setIdleTask at each specified interval with as little impact on browser performance as possible.

In this example, once every 10 seconds, if a `checkAccessToken` registered with `setIdleTask` remains in the queue managed by `idle-task`, it will be executed without affecting browser performance.

**If you want to guarantee the execution of as much processing as possible, specify the `interval` of the `configureIdleTask` in this way**.

>3. force checking to performed once every few seconds when the access token check is not running because the browser is too busy

So we've got 3 implemented!!! 🎉

## Summary
I have shown an example implementation of periodically rotating access tokens while the browser is idle.

Besides access token rotation, it could be used, for example, to save a draft of an article the user is editing.

If you have any bugs or questions about `idle-task`, please contact us at GitHub issue.

https://github.com/hiroki0525/idle-task

This article will also help you to optimize your website.

https://dev.to/nuko_suke/improve-responsiveness-to-user-interactions-4a3p

If you found this helpful, please help someone else by sharing it or tagging someone in the comments!

Thanks!