DEV Community

Cover image for Why don't you do it when the browser is free instead of setInterval?
nuko_suke
nuko_suke

Posted on

Why don't you do it when the browser is free instead of setInterval?

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

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.

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

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.

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;
Enter fullscreen mode Exit fullscreen mode

Here is the key code.

    const idleTaskId = setIdleTask(checkAccessToken, {
      revalidateInterval: 5000
    });
Enter fullscreen mode Exit fullscreen mode

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

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

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

The point is this.

configureIdleTask({
  debug: true,
  interval: 1000 * 10
});
Enter fullscreen mode Exit fullscreen mode

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

  1. 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!

Top comments (0)