Pourquoi votre await fetch() ralentit votre application — et comment y remédier

Why Your await fetch() is Slowing Down — And How to Fix It

Pourquoi votre await fetch() ralentit votre application — et comment y remédier

Lors du développement d'applications JavaScript, les problèmes de performance liés à fetch() sont souvent négligés. Pourtant, même un code simple comme await fetch() peut ralentir votre application de manière inattendue, provoquant des retards dans les requêtes réseau et frustrant les utilisateurs. Cet article explore les raisons de ces ralentissements et propose des solutions pour les résoudre.

1. Connexions TCP froides : des retards de 200ms sans raison apparente Les symptômes incluent une première requête à une API systématiquement plus lente que les suivantes. Chaque fetch() crée une nouvelle connexion socket, impliquant une résolution DNS, une poignée de main TCP et une négociation TLS. En Europe, le temps moyen d'un aller-retour (RTT) est d'environ 50ms, ce qui peut ajouter des centaines de millisecondes à chaque nouvelle requête.

La solution consiste à utiliser apiAgent pour gérer les connexions persistantes (keep-alive). La fonction fetchOrders() peut alors utiliser undici pour réutiliser efficacement les connexions ouvertes.

2. DNS + TLS : les dragons cachés Même avec keep-alive, la première requête vers un nouveau domaine reste lente en raison des recherches DNS et des négociations TLS. Une recherche DNS peut bloquer le thread JavaScript et prendre jusqu'à 100ms sur les réseaux mobiles.

Pour y remédier, il faut mettre en cache les requêtes DNS et augmenter maxSockets pour gérer plusieurs domaines. Une alternative consiste à utiliser QUIC/HTTP-3 avec 0-RTT pour contourner ces délais.

3. response.json() bloquant la boucle d'événements Lorsqu'un serveur envoie des réponses JSON volumineuses (5-10MB ou plus), l'appel à response.json() peut bloquer la boucle d'événements et consommer 100% du CPU.

La solution est d'utiliser un parsing en flux continu, qui traite le JSON au fur et à mesure de sa réception plutôt que de charger tout le contenu en mémoire d'un coup.

4. Optimisation de la taille des réponses : compression et formats Si la vitesse réseau est bonne mais que le chargement reste lent, le problème vient souvent de formats de données inefficaces ou d'un manque de compression.

Il faut activer la compression Brotli à la fois côté frontend et backend. Brotli permet d'économiser jusqu'à 25% d'espace supplémentaire par rapport à Gzip.

5. await dans une boucle : le tueur de concurrence Lorsque de nombreuses tâches asynchrones sont exécutées dans une boucle, elles le sont séquentiellement même si elles pourraient être concurrentes.

Pour éviter cela, il faut limiter la concurrence en s'assurant qu'un nombre limité de requêtes soient envoyées simultanément, évitant ainsi de surcharger le backend.

6. Utiliser undici.request pour des requêtes plus rapides Pour de meilleures performances, il est recommandé d'utiliser la fonction request de undici plutôt que le fetch natif, car elle est plus rapide et réduit la surcharge.

7. Simplification des pré-vérifications CORS Les requêtes de pré-vérification CORS ajoutent des délais supplémentaires. Simplifier les requêtes et mettre en cache les réponses de pré-vérification peut aider à réduire cette surcharge.

8. Blocage HOL dans HTTP/2 : les téléchargements de fichiers volumineux affectent toutes les requêtes Dans HTTP/2, les téléchargements de fichiers volumineux peuvent bloquer d'autres requêtes en raison du multiplexage sur une seule connexion TCP. Pour éviter cela, il faut séparer les requêtes volumineuses des petites.

Pour des performances encore meilleures, HTTP/3 fonctionne sur UDP et évite le goulot d'étranglement de TCP.

9. JSON.stringify() avant envoi La sérialisation de charges utiles volumineuses avec JSON.stringify() peut bloquer la boucle d'événements. Il faut plutôt utiliser des téléchargements multipart en flux continu ou d'autres méthodes de sérialisation efficaces.

Conclusion Optimiser les performances de await fetch() implique de résoudre plusieurs goulots d'étranglement. De la réutilisation des connexions au passage à des formats de streaming efficaces, chaque solution contribue à des requêtes réseau plus rapides et plus efficaces.

Tại sao await fetch() làm chậm ứng dụng của bạn — và cách khắc phục

Khi phát triển ứng dụng JavaScript, các vấn đề hiệu suất liên quan đến fetch() thường bị bỏ qua. Tuy nhiên, ngay cả đoạn code đơn giản như await fetch() cũng có thể làm chậm ứng dụng một cách bất ngờ, gây ra độ trễ trong các yêu cầu mạng và khiến người dùng khó chịu. Bài viết này sẽ phân tích nguyên nhân gây ra tình trạng chậm và đề xuất các giải pháp khắc phục.

1. Kết nối TCP lạnh: Độ trễ 200ms không rõ nguyên nhân Biểu hiện là yêu cầu đầu tiên tới API luôn chậm hơn các yêu cầu sau. Mỗi lệnh fetch() tạo một kết nối socket mới, bao gồm: tra cứu DNS, bắt tay TCP và thiết lập TLS. Ở châu Âu, thời gian round-trip (RTT) trung bình khoảng 50ms, khiến mỗi yêu cầu mới tốn thêm hàng trăm mili giây.

Giải pháp là sử dụng apiAgent để quản lý kết nối keep-alive. Hàm fetchOrders() có thể dùng undici để tái sử dụng kết nối hiệu quả.

2. DNS + TLS: Những 'con rồng' ẩn mình Ngay cả với keep-alive, yêu cầu đầu tiên tới domain mới vẫn chậm do tra cứu DNS và bắt tay TLS. Tra cứu DNS có thể chặn luồng JavaScript và mất tới 100ms trên mạng di động.

Cần cache truy vấn DNS và tăng maxSockets để xử lý nhiều domain. Có thể dùng QUIC/HTTP-3 với 0-RTT để tránh các độ trễ này.

3. response.json() làm nghẽn vòng lặp sự kiện Khi server gửi phản hồi JSON lớn (5-10MB trở lên), lệnh response.json() sẽ chiếm 100% CPU.

Giải pháp là phân tích luồng JSON khi đang tải, tránh nạp toàn bộ dữ liệu vào bộ nhớ cùng lúc.

4. Tối ưu kích thước phản hồi: Nén và định dạng Nếu tốc độ mạng tốt nhưng tải vẫn chậm, nguyên nhân thường do định dạng dữ liệu kém hiệu quả hoặc thiếu nén.

Cần kích hoạt nén Brotli cả phía frontend và backend. Brotli tiết kiệm thêm 25% dung lượng so với Gzip.

5. await trong vòng lặp: 'Sát thủ' xử lý đồng thời Các tác vụ bất đồng bộ trong vòng lặp sẽ chạy tuần tự dù có thể chạy song song.

Cần giới hạn số yêu cầu đồng thời để tránh quá tải backend.

6. Dùng undici.request để yêu cầu nhanh hơn Hàm request của undici nhanh hơn fetch tích hợp sẵn và giảm overhead.

7. Đơn giản hóa CORS preflight Yêu cầu CORS preflight gây thêm độ trễ. Cần đơn giản hóa yêu cầu và cache phản hồi preflight.

8. Nghẽn HOL trong HTTP/2: Tải file lớn ảnh hưởng mọi yêu cầu Trong HTTP/2, tải file lớn có thể chặn các yêu cầu khác. Cần tách yêu cầu lớn/nhỏ.

HTTP/3 dùng UDP và tránh nghẽn TCP.

9. JSON.stringify() trước khi gửi Tuần tự hóa dữ liệu lớn bằng JSON.stringify() có thể nghẽn vòng lặp. Nên dùng tải lên multipart dạng luồng.

Kết luận Tối ưu hiệu suất await fetch() cần giải quyết nhiều điểm nghẽn. Từ tái dụng kết nối đến dùng định dạng luồng hiệu quả, mỗi giải pháp giúp yêu cầu mạng nhanh hơn.