So vermeiden Sie die Single-Threaded-Falle in JavaScript
JavaScript wird oft als Single-Threaded beschrieben, was bedeutet, dass es jeweils eine Aufgabe ausführt. Aber bedeutet das, dass jeder Codeabschnitt völlig isoliert läuft und keine Möglichkeit hat, andere Aufgaben zu erledigen, während er auf asynchrone Vorgänge wie HTTP-Antworten oder Datenbankanfragen wartet? Die Antwort ist Nein! Tatsächlich ermöglichen die Ereignisschleife und Versprechen von JavaScript die effiziente Verarbeitung asynchroner Aufgaben, während anderer Code weiterhin ausgeführt wird.
Die Wahrheit ist, dass Javascript tatsächlich Single-Threaded ist, jedoch kann ein Missverständnis darüber, wie dies funktioniert, zu häufigen Fallstricken führen. Eine solche Falle ist die Verwaltung asynchroner Vorgänge wie API-Anfragen, insbesondere wenn versucht wird, den Zugriff auf gemeinsam genutzte Ressourcen zu kontrollieren, ohne Race Conditions zu verursachen. Lassen Sie uns ein Beispiel aus der Praxis untersuchen und sehen, wie eine schlechte Implementierung zu schwerwiegenden Fehlern führen kann.
Ich bin auf einen Fehler in einer Anwendung gestoßen, der eine Anmeldung bei einem Backend-Dienst erforderlich machte, um Daten zu aktualisieren. Beim Anmelden erhält die App ein Zugriffstoken mit einem angegebenen Ablaufdatum. Nach Ablauf dieses Ablaufdatums mussten wir uns erneut authentifizieren, bevor wir neue Anfragen an den Update-Endpunkt stellen konnten. Die Herausforderung entstand, weil der Anmeldeendpunkt auf maximal eine Anfrage alle fünf Minuten gedrosselt wurde, während der Aktualisierungsendpunkt innerhalb desselben Fünf-Minuten-Fensters häufiger aufgerufen werden musste. Es war wichtig, dass die Logik ordnungsgemäß funktionierte, dennoch wurde der Anmeldeendpunkt innerhalb des Fünf-Minuten-Intervalls gelegentlich mehrmals ausgelöst, was dazu führte, dass der Aktualisierungsendpunkt nicht funktionierte. Obwohl es Zeiten gab, in denen alles wie erwartet funktionierte, stellte dieser zeitweise auftretende Fehler ein größeres Risiko dar, da er zunächst ein falsches Sicherheitsgefühl vermitteln und den Eindruck erwecken konnte, das System würde ordnungsgemäß funktionieren._
Um dieses Beispiel zu veranschaulichen, verwenden wir eine sehr einfache NestJS-App, die die folgenden Dienste umfasst:
- AppService: Fungiert als Controller, um zwei Varianten zu simulieren – die schlechte Version, die manchmal funktioniert und manchmal nicht, und die gute Version, die garantiert immer ordnungsgemäß funktioniert.
- BadAuthenticationService: Implementierung für die fehlerhafte Version.
- GoodAuthenticationService: Implementierung für die gute Version.
- AbstractAuthenticationService: Klasse, die für die Aufrechterhaltung des gemeinsamen Status zwischen GoodAuthenticationService und BadAuthenticationService verantwortlich ist.
- LoginThrottleService: Klasse, die den Drosselungsmechanismus des Anmeldeendpunkts für den Backend-Dienst simuliert.
- MockHttpService: Klasse, die bei der Simulation von HTTP-Anfragen hilft.
- MockAwsCloudwatchApiService: Simuliert einen API-Aufruf an das AWS CloudWatch-Protokollierungssystem.
Ich werde hier nicht den Code für alle diese Klassen zeigen; Sie finden es direkt im GitHub-Repository. Stattdessen werde ich mich speziell auf die Logik konzentrieren und darauf, was geändert werden muss, damit sie richtig funktioniert.
Der schlechte Ansatz:
@Injectable() export class BadAuthenticationService extends AbstractAuthenticationService { async loginToBackendService() { this.loginInProgress = true; // this is BAD, we are inside a promise, it's asynchronous. it's not synchronous, javascript can execute it whenever it wants try { const response = await firstValueFrom( this.httpService.post(`https://backend-service.com/login`, { password: 'password', }), ); return response; } finally { this.loginInProgress = false; } } async sendProtectedRequest(route: string, data?: unknown) { if (!this.accessToken) { if (this.loginInProgress) { await new Promise((resolve) => setTimeout(resolve, 1000)); return this.sendProtectedRequest(route, data); } try { await this.awsCloudwatchApiService.logLoginCallAttempt(); const { data: loginData } = await this.loginToBackendService(); this.accessToken = loginData.accessToken; } catch (e: any) { console.error(e?.response?.data); throw e; } } try { const response = await firstValueFrom( this.httpService.post(`https://backend-service.com${route}`, data, { headers: { Authorization: `Bearer ${this.accessToken}`, }, }), ); return response; } catch (e: any) { if (e?.response?.data?.statusCode === 401) { this.accessToken = null; return this.sendProtectedRequest(route, data); } console.error(e?.response?.data); throw e; } } }
Warum dies ein schlechter Ansatz ist:
Im BadAuthenticationService setzt die Methode loginToBackendService this.loginInProgress auf true, wenn eine Anmeldeanforderung initiiert wird. Da diese Methode jedoch asynchron ist, kann nicht garantiert werden, dass der Anmeldestatus sofort aktualisiert wird. Dies könnte innerhalb des Drosselungslimits zu mehreren gleichzeitigen Aufrufen des Anmeldeendpunkts führen.
Wenn sendProtectedRequest erkennt, dass das Zugriffstoken fehlt, prüft es, ob gerade eine Anmeldung durchgeführt wird. Ist dies der Fall, wartet die Funktion eine Sekunde und versucht es dann erneut. Wenn in dieser Zeit jedoch eine weitere Anfrage eintrifft, kann dies zu weiteren Anmeldeversuchen führen. Dies kann zu mehreren Aufrufen des Anmeldeendpunkts führen, der so gedrosselt ist, dass nur ein Anruf pro Minute möglich ist. Infolgedessen kann der Update-Endpunkt zeitweise ausfallen, was zu unvorhersehbarem Verhalten und einem falschen Sicherheitsgefühl führt, wenn das System zeitweise ordnungsgemäß zu funktionieren scheint.
Zusammenfassend lässt sich sagen, dass das Problem in der unsachgemäßen Handhabung asynchroner Vorgänge liegt, die zu potenziellen Race Conditions führt, die die Logik der Anwendung zerstören können.
Der gute Ansatz:
@Injectable() export class GoodAuthenticationService extends AbstractAuthenticationService { async loginToBackendService() { try { const response = await firstValueFrom( this.httpService.post(`https://backend-service.com/login`, { password: 'password', }), ); return response; } finally { this.loginInProgress = false; } } async sendProtectedRequest(route: string, data?: unknown) { if (!this.accessToken) { if (this.loginInProgress) { await new Promise((resolve) => setTimeout(resolve, 1000)); return this.sendProtectedRequest(route, data); } // Critical: Set the flag before ANY promise call this.loginInProgress = true; try { await this.awsCloudwatchApiService.logLoginCallAttempt(); const { data: loginData } = await this.loginToBackendService(); this.accessToken = loginData.accessToken; } catch (e: any) { console.error(e?.response?.data); throw e; } } try { const response = await firstValueFrom( this.httpService.post(`https://backend-service.com${route}`, data, { headers: { Authorization: `Bearer ${this.accessToken}`, }, }), ); return response; } catch (e: any) { if (e?.response?.data?.statusCode === 401) { this.accessToken = null; return this.sendProtectedRequest(route, data); } console.error(e?.response?.data); throw e; } } }
Warum dies ein guter Ansatz ist:
Im GoodAuthenticationService ist die loginToBackendService-Methode so strukturiert, dass sie die Anmeldelogik effizient verarbeitet. Die wichtigste Verbesserung ist die Verwaltung des loginInProgress-Flags. Es wird nach der Bestätigung, dass kein Zugriffstoken vorhanden ist, und bevor jegliche asynchronen Vorgänge festgelegt. Dadurch wird sichergestellt, dass nach dem Einleiten eines Anmeldeversuchs keine anderen Anmeldeaufrufe gleichzeitig erfolgen können, wodurch mehrere Anfragen an den gedrosselten Anmeldeendpunkt effektiv verhindert werden.
Demo-Anweisungen
Klonen Sie das Repository:
@Injectable() export class BadAuthenticationService extends AbstractAuthenticationService { async loginToBackendService() { this.loginInProgress = true; // this is BAD, we are inside a promise, it's asynchronous. it's not synchronous, javascript can execute it whenever it wants try { const response = await firstValueFrom( this.httpService.post(`https://backend-service.com/login`, { password: 'password', }), ); return response; } finally { this.loginInProgress = false; } } async sendProtectedRequest(route: string, data?: unknown) { if (!this.accessToken) { if (this.loginInProgress) { await new Promise((resolve) => setTimeout(resolve, 1000)); return this.sendProtectedRequest(route, data); } try { await this.awsCloudwatchApiService.logLoginCallAttempt(); const { data: loginData } = await this.loginToBackendService(); this.accessToken = loginData.accessToken; } catch (e: any) { console.error(e?.response?.data); throw e; } } try { const response = await firstValueFrom( this.httpService.post(`https://backend-service.com${route}`, data, { headers: { Authorization: `Bearer ${this.accessToken}`, }, }), ); return response; } catch (e: any) { if (e?.response?.data?.statusCode === 401) { this.accessToken = null; return this.sendProtectedRequest(route, data); } console.error(e?.response?.data); throw e; } } }
Installieren Sie die erforderlichen Abhängigkeiten:
@Injectable() export class GoodAuthenticationService extends AbstractAuthenticationService { async loginToBackendService() { try { const response = await firstValueFrom( this.httpService.post(`https://backend-service.com/login`, { password: 'password', }), ); return response; } finally { this.loginInProgress = false; } } async sendProtectedRequest(route: string, data?: unknown) { if (!this.accessToken) { if (this.loginInProgress) { await new Promise((resolve) => setTimeout(resolve, 1000)); return this.sendProtectedRequest(route, data); } // Critical: Set the flag before ANY promise call this.loginInProgress = true; try { await this.awsCloudwatchApiService.logLoginCallAttempt(); const { data: loginData } = await this.loginToBackendService(); this.accessToken = loginData.accessToken; } catch (e: any) { console.error(e?.response?.data); throw e; } } try { const response = await firstValueFrom( this.httpService.post(`https://backend-service.com${route}`, data, { headers: { Authorization: `Bearer ${this.accessToken}`, }, }), ); return response; } catch (e: any) { if (e?.response?.data?.statusCode === 401) { this.accessToken = null; return this.sendProtectedRequest(route, data); } console.error(e?.response?.data); throw e; } } }
Führen Sie die Anwendung aus:
git clone https://github.com/zenstok/nestjs-singlethread-trap.git
Anfragen simulieren:
- Um zwei Anfragen mit der schlechten Version zu simulieren, rufen Sie auf:
cd nestjs-singlethread-trap npm install
Um zwei Anfragen mit der guten Version zu simulieren, rufen Sie an:
npm run start
Fazit: Die Single-Threaded-Fallstricke von JavaScript vermeiden
Obwohl JavaScript Single-Threaded ist, kann es asynchrone Aufgaben wie HTTP-Anfragen mithilfe von Versprechen und der Ereignisschleife effizient verarbeiten. Allerdings kann ein unsachgemäßer Umgang mit diesen Versprechen, insbesondere in Szenarien mit gemeinsam genutzten Ressourcen (wie Token), zu Race Conditions und doppelten Aktionen führen.
Die wichtigste Erkenntnis besteht darin, asynchrone Aktionen wie Anmeldungen zu synchronisieren, um solche Fallen zu vermeiden. Stellen Sie immer sicher, dass Ihr Code über laufende Prozesse informiert ist und Anfragen auf eine Weise verarbeitet, die eine ordnungsgemäße Reihenfolge gewährleistet, auch wenn JavaScript hinter den Kulissen Multitasking betreibt.
Wenn Sie dem Rabbit Byte Club noch nicht beigetreten sind, haben Sie jetzt die Chance, Teil einer lebendigen Community aus Software-Enthusiasten, Tech-Gründern und Nicht-Tech-Gründern zu werden. Gemeinsam tauschen wir Wissen aus, lernen voneinander und bereiten uns auf den Aufbau des nächsten großen Startups vor. Begleiten Sie uns noch heute und seien Sie Teil einer spannenden Reise zu Innovation und Wachstum!
Das obige ist der detaillierte Inhalt vonSo vermeiden Sie die Single-Threaded-Falle in JavaScript. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Heiße KI -Werkzeuge

Undresser.AI Undress
KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover
Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool
Ausziehbilder kostenlos

Clothoff.io
KI-Kleiderentferner

Video Face Swap
Tauschen Sie Gesichter in jedem Video mühelos mit unserem völlig kostenlosen KI-Gesichtstausch-Tool aus!

Heißer Artikel

Heiße Werkzeuge

Notepad++7.3.1
Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version
Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1
Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6
Visuelle Webentwicklungstools

SublimeText3 Mac-Version
Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Heiße Themen

Häufig gestellte Fragen und Lösungen für das Ticket-Ticket-Ticket-Ticket in Front-End im Front-End-Entwicklungsdruck ist der Ticketdruck eine häufige Voraussetzung. Viele Entwickler implementieren jedoch ...

JavaScript ist der Eckpfeiler der modernen Webentwicklung. Zu den Hauptfunktionen gehören eine ereignisorientierte Programmierung, die Erzeugung der dynamischen Inhalte und die asynchrone Programmierung. 1) Ereignisgesteuerte Programmierung ermöglicht es Webseiten, sich dynamisch entsprechend den Benutzeroperationen zu ändern. 2) Die dynamische Inhaltsgenerierung ermöglicht die Anpassung der Seiteninhalte gemäß den Bedingungen. 3) Asynchrone Programmierung stellt sicher, dass die Benutzeroberfläche nicht blockiert ist. JavaScript wird häufig in der Webinteraktion, der einseitigen Anwendung und der serverseitigen Entwicklung verwendet, wodurch die Flexibilität der Benutzererfahrung und die plattformübergreifende Entwicklung erheblich verbessert wird.

Es gibt kein absolutes Gehalt für Python- und JavaScript -Entwickler, je nach Fähigkeiten und Branchenbedürfnissen. 1. Python kann mehr in Datenwissenschaft und maschinellem Lernen bezahlt werden. 2. JavaScript hat eine große Nachfrage in der Entwicklung von Front-End- und Full-Stack-Entwicklung, und sein Gehalt ist auch beträchtlich. 3. Einflussfaktoren umfassen Erfahrung, geografische Standort, Unternehmensgröße und spezifische Fähigkeiten.

JavaScript zu lernen ist nicht schwierig, aber es ist schwierig. 1) Verstehen Sie grundlegende Konzepte wie Variablen, Datentypen, Funktionen usw. 2) Beherrschen Sie die asynchrone Programmierung und implementieren Sie sie durch Ereignisschleifen. 3) Verwenden Sie DOM -Operationen und versprechen Sie, asynchrone Anfragen zu bearbeiten. 4) Vermeiden Sie häufige Fehler und verwenden Sie Debugging -Techniken. 5) Die Leistung optimieren und Best Practices befolgen.

Wie fusioniere ich Array -Elemente mit derselben ID in ein Objekt in JavaScript? Bei der Verarbeitung von Daten begegnen wir häufig die Notwendigkeit, dieselbe ID zu haben ...

Diskussion über die Realisierung von Parallaxe -Scrolling- und Elementanimationseffekten in diesem Artikel wird untersuchen, wie die offizielle Website der Shiseeido -Website (https://www.shiseeido.co.jp/sb/wonderland/) ähnlich ist ...

Zu den neuesten Trends im JavaScript gehören der Aufstieg von Typenkripten, die Popularität moderner Frameworks und Bibliotheken und die Anwendung der WebAssembly. Zukunftsaussichten umfassen leistungsfähigere Typsysteme, die Entwicklung des serverseitigen JavaScript, die Erweiterung der künstlichen Intelligenz und des maschinellen Lernens sowie das Potenzial von IoT und Edge Computing.

Eingehende Diskussion der Ursachen des Unterschieds in der Konsole.log-Ausgabe. In diesem Artikel wird die Unterschiede in den Ausgabeergebnissen der Konsolenfunktion in einem Code analysiert und die Gründe dafür erläutert. � ...
