Chào anh em, trong lập trình web, chắc hẳn nhiều anh em đã từng dùng setInterval và setTimeout để thực hiện các tác vụ định kỳ hoặc trì hoãn một số hành động nào đó. Tuy nhiên, có thể anh em chưa biết là khi tab trình duyệt không hoạt động, các hàm này thường bị trình duyệt throttling, khiến chúng không chạy đúng thời gian mà mình mong đợi.

Gần đây mình gặp một vấn đề khá đau đầu khi sử dụng setInterval để gửi các yêu cầu định kỳ đến server (đây là tính năng yêu cầu như vậy mình có đề xuất sử dụng websocket nhưng không được). Vấn đề ở chỗ là hàm setInterval không chạy đúng thời gian như mong đợi, đặc biệt là khi mình chuyển sang tab khác. Khi quay lại tab, các yêu cầu đến server bị gián đoạn và dẫn đến nhiều lỗi không mong muốn.


Sau khi tìm hiểu kỹ hơn, mình phát hiện ra nguyên nhân là do trình duyệt thực hiện throttling các timer (bao gồm setIntervalsetTimeout) khi tab không còn hoạt động, nhằm tiết kiệm tài nguyên và pin cho người dùng. Điều này đã khiến các tác vụ bị trì hoãn hoặc chạy không chính xác.


Trong bài viết này, mình sẽ cùng anh em tìm hiểu lý do vì sao các trình duyệt lại làm vậy, và quan trọng hơn, mình sẽ chia sẻ một số cách để khắc phục vấn đề này, trong đó có sử dụng HackTimer – một giải pháp đơn giản nhưng cực kỳ hiệu quả.


Tại Sao Trình Duyệt Lại Throttling Timer?


Khi anh em mở nhiều tab trên trình duyệt, hoặc khi tab đang mở không còn được kích hoạt, trình duyệt thường thực hiện cơ chế throttling các hàm setTimeoutsetInterval. Có hai lý do chính cho việc này:


  1. Tiết kiệm tài nguyên: Trình duyệt muốn giảm tải cho CPU và bộ nhớ để máy của người dùng không bị chậm khi có quá nhiều tab hoặc ứng dụng chạy.
  2. Tiết kiệm pin: Đặc biệt trên các thiết bị di động, việc giới hạn các tác vụ nền giúp tiết kiệm pin và kéo dài tuổi thọ của thiết bị.


Ảnh Hưởng Của Throttling


Việc throttling có thể gây nhiều vấn đề cho ứng dụng web:


  • Mất kết nối với server: Nếu ứng dụng của anh em cần gửi các yêu cầu ping hoặc heartbeat đến server để duy trì kết nối, việc này có thể bị chậm hoặc không chính xác, khiến kết nối bị ngắt.


  • Tác vụ chạy sai thời gian: Nếu anh em cần các tác vụ chạy đúng thời gian (như đồng hồ đếm ngược hay các hành động tự động), chúng sẽ bị ảnh hưởng nếu setInterval không còn hoạt động đúng nhịp.


Các Giải Pháp Để Khắc Phục


Có nhiều cách để giải quyết vấn đề này, dưới đây là một số giải pháp phổ biến kèm ví dụ để anh em dễ hình dung.


1. Sử Dụng Web Worker


Web Worker giúp tách tác vụ ra khỏi luồng chính của ứng dụng, giúp các bộ đếm thời gian không bị ảnh hưởng bởi throttling.


Ví dụ:

// worker.js
self.onmessage = function() {
  setInterval(function() {
    postMessage('Ping');
  }, 10000);
};


// MyComponent.jsx
import React, { useEffect } from 'react';

const MyComponent = () => {
  useEffect(() => {
    const worker = new Worker(new URL('./worker.js', import.meta.url));

    worker.onmessage = function() {
      sendRequestToPingServer();
    };

    return () => {
      worker.terminate();
    };
  }, []);

  const sendRequestToPingServer = () => {
    console.log('Gửi yêu cầu ping đến server');
  };

  return <div>Trạng thái kết nối: Đã kết nối</div>;
};

export default MyComponent;


2. Sử Dụng Service Worker


Service Worker là một cách mạnh mẽ để chạy các tác vụ nền, ngay cả khi ứng dụng của anh em không được mở. Tuy nhiên, nó phức tạp hơn và thường được dùng trong các ứng dụng Progressive Web App (PWA).


Ví dụ:

// service-worker.js

self.addEventListener('activate', () => {
  setInterval(() => {
    fetch('/ping');
  }, 10000);
});


// MyComponent.jsx
import React, { useEffect } from 'react';

const MyComponent = () => {
  useEffect(() => {
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('/service-worker.js').then(() => {
        console.log('Service Worker đã được đăng ký');
      });
    }
  }, []);

  return <div>Trạng thái kết nối: Đã kết nối</div>;
};

export default MyComponent;


3. Sử Dụng HackTimer


HackTimer là một thư viện đơn giản nhưng cực kỳ hiệu quả giúp khắc phục vấn đề throttling của setTimeoutsetInterval. Cách hoạt động của HackTimer là thay vì sử dụng các hàm timer mặc định của trình duyệt, nó sử dụng Web Worker để giữ cho các tác vụ chạy đúng thời gian, ngay cả khi tab không hoạt động.


Cách Sử Dụng HackTimer


Đầu tiên, anh em cần cài đặt HackTimer bằng lệnh:


npm install hacktimer


Sau đó, anh em chỉ cần import thư viện vào trong ứng dụng của mình. HackTimer sẽ tự động ghi đè các hàm setTimeoutsetInterval mà không cần phải thay đổi code hiện tại.


Ví dụ:

import React, { useEffect, useState } from 'react';
import 'hacktimer/HackTimer'; // Ghi đè setIntervalsetTimeout

const MyComponent = () => {
  const [connectStatus, setConnectStatus] = useState('DISCONNECTED');

  useEffect(() => {
    if (connectStatus === 'CONNECTED') {
      const intervalId = setInterval(() => {
        sendRequestToPingServer();
      }, 10000);

      return () => {
        clearInterval(intervalId);
      };
    }
  }, [connectStatus]);

  const sendRequestToPingServer = () => {
    console.log('Gửi yêu cầu ping để giữ kết nối sống');
  };

  return <div>Trạng thái kết nối: {connectStatus}</div>;
};

export default MyComponent;


Cơ Chế Hoạt Động Của HackTimer:


HackTimer ghi đè các hàm setTimeoutsetInterval mặc định của trình duyệt, nhưng thay vì sử dụng chính những hàm này, nó sử dụng Web Worker để đảm bảo các timer luôn hoạt động ngay cả khi tab trình duyệt không được kích hoạt.


Cụ thể hơn, HackTimer tải mã worker từ file HackTimerWorker.js và sử dụng worker này để thực thi các timer thay vì dựa vào cơ chế của trình duyệt.


Lưu ý: Nếu anh em sử dụng HackTimer, cần đảm bảo rằng mã của HackTimer.jsHackTimerWorker.js phải tương thích với nhau. HackTimer.js chỉ nên sử dụng với HackTimerWorker.js, và HackTimer.min.js chỉ nên sử dụng với HackTimerWorker.min.js, tránh việc trộn lẫn giữa hai phiên bản.


Kết Luận


Throttling là cơ chế tối ưu hóa của trình duyệt nhằm tiết kiệm tài nguyên và năng lượng. Tuy nhiên, đối với các ứng dụng yêu cầu tính toán thời gian chính xác hoặc cần gửi ping định kỳ đến server, anh em cần phải tìm cách vượt qua giới hạn này. HackTimer là một giải pháp nhanh gọn, dễ tích hợp và hiệu quả giúp anh em tránh được việc bị throttling mà không cần thay đổi quá nhiều code.


Nếu anh em đang tìm một cách đơn giản nhưng vẫn đảm bảo hiệu quả, thì mình khuyến khích nên sử dụng HackTimer.


Hy vọng bài viết này sẽ giúp anh em hiểu rõ hơn về vấn đề throttling và tìm ra giải pháp phù hợp cho dự án của mình khi gặp vấn đề với các timer mà không biết lỗi từ đâu.


Gavin Nguyen!