
Các tính năng yêu cầu xử lý các sự kiện theo thời gian thực (realtime) là 1 trong những tính năng phổ biến trong các ứng dụng ngày nay. Có nhiều cách để thực hiện được việc này như Websocket, Server sent events (SSE), … Nhân tiện hôm nay mình đang có 1 task mới yêu cầu realtime và có biết về 1 kỹ thuật nữa là HTTP Polling, trong quá trình research tìm thấy vài bài viết khá hay nên muốn viết 1 bài để tổng hợp lại cho bản thân cũng như anh em nào có nhu cầu tham khảo.
1. Giải thích về Polling
Polling là 1 kỹ thuật cho phép Client liên tục thực hiện việc truy xuất dữ liệu từ Server theo 1 chu kỳ nào đó (Ví dụ: 3s hoặc 5s sẽ gọi 1 lần) để kiểm tra xem dữ liệu có thay đổi chưa.
Các bước thực hiện kỹ thuật polling khá đơn giản:
- Khởi tạo yêu cầu lên server
- Chờ kết quả
- Sau khoảng thời gian chờ đã được thiết lập trước (VD: 5s), sẽ thực hiện 1 yêu cầu lên server nữa để lấy về dữ liệu mới nhất
Kỹ thuật này nghe qua khá cồng kềnh và tốn network, nhưng cách triển khai nó sẽ đơn giản và phù hợp với nhiều trình duyệt hơn các kỹ thuật realtime khác như websocket hay SSE (Server Sent Event). Cách này sẽ phù hợp với những tính năng không đòi hỏi sự realtime 1 cách tuyệt đối và có thể ngưng khi dữ liệu trả về đặt yêu cầu.
2. Triển khai Polling trong React
Trường hợp thực tế: Bạn đang làm 1 trang thanh toán cho website và bạn cần kiểm tra xem khách hàng đã thanh toán đơn hàng thành công chưa để xử lý những bước tiếp theo (Hiện thông báo, chuyển qua trang đơn hàng thành công)
Các bước thực hiện:
- Dùng setInterval để thực hiện gọi API đều đặn (vd: sau mỗi 5s)
- Kiểm tra kết quả trả về xem status của payment đã thành công chưa
- Xử lý timeout: cleanup interval để tránh bị leak memory
Tham khảo đoạn code sau:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const PaymentStatus = ({ transactionId }) => {
const [status, setStatus] = useState('pending'); // Initial status is 'pending'
const [error, setError] = useState(null);
const POLL_INTERVAL = 5000; // Poll every 5 seconds
// Polling function to check payment status
const checkPaymentStatus = async () => {
try {
const response = await axios.get(`/api/payment/status/${transactionId}`);
const { status: paymentStatus } = response.data;
setStatus(paymentStatus); // Update status based on API response
// Stop polling if payment is either 'success' or 'failed'
if (paymentStatus === 'success' || paymentStatus === 'failed') {
clearInterval(pollingInterval); // Stop polling
}
} catch (err) {
console.error('Error fetching payment status:', err);
setError('Failed to retrieve payment status');
clearInterval(pollingInterval); // Stop polling on error
}
};
// Polling effect
useEffect(() => {
const pollingInterval = setInterval(() => {
if (status === 'pending') {
checkPaymentStatus(); // Keep polling if status is 'pending'
}
}, POLL_INTERVAL);
// Cleanup function to stop polling when component unmounts
return () => clearInterval(pollingInterval);
}, [status]); // Re-run polling only when the status changes
return (
<div>
<h2>Transaction Status: {status}</h2>
{error && <p style={{ color: 'red' }}>{error}</p>}
{status === 'success' && <p>Payment was successful!</p>}
{status === 'failed' && <p>Payment failed. Please try again.</p>}
</div>
);
};
export default PaymentStatus;
3. Cách tạo 1 Custom Hook về polling
Để tránh việc viết lại đoạn code dài dòng như trên mỗi khi cần triển khai polling, bạn có thể tạo 1 custom hook để thực hiện việc trên và dễ quản lý cũng như bảo trì
Tham khảo đoạn code sau:
// usePolling.ts
import { useEffect } from "react"
import { useRouter } from "next/navigation"
export function usePolling(func: any, ms: number) {]
useEffect(() => {
const intervalId = setInterval(() => {
func()
}, ms)
return () => clearInterval(intervalId)
}, [])
}
Cách sử dụng
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const PaymentStatus = ({ transactionId }) => {
const [status, setStatus] = useState('pending'); // Initial status is 'pending'
const [error, setError] = useState(null);
const POLL_INTERVAL = 5000; // Poll every 5 seconds
// Polling function to check payment status
const checkPaymentStatus = async () => {
try {
const response = await axios.get(`/api/payment/status/${transactionId}`);
const { status: paymentStatus } = response.data;
setStatus(paymentStatus); // Update status based on API response
// Stop polling if payment is either 'success' or 'failed'
if (paymentStatus === 'success' || paymentStatus === 'failed') {
clearInterval(pollingInterval); // Stop polling
}
} catch (err) {
console.error('Error fetching payment status:', err);
setError('Failed to retrieve payment status');
clearInterval(pollingInterval); // Stop polling on error
}
};
usePolling(checkPaymentStatus, POLL_INTERVAL)
return (
<div>
<h2>Transaction Status: {status}</h2>
{error && <p style={{ color: 'red' }}>{error}</p>}
{status === 'success' && <p>Payment was successful!</p>}
{status === 'failed' && <p>Payment failed. Please try again.</p>}
</div>
);
};
export default PaymentStatus;
Referrence:
