JavaScript: asynchrone programmeertechnieken. Codes (asynchroon en synchroon): genereren en installeren

We hebben een nieuw boek uitgebracht “Content Marketing in sociale netwerken: Hoe je in de hoofden van je abonnees kunt kruipen en ze verliefd kunt maken op je merk.”

Abonneren

Wat is asynchrone tellercode?

Asynchrone trackingcode (teller) is een scriptcode die in de hoofdtekst van de site wordt ingevoegd en is ontworpen om verkeersstatistieken bij te houden. Deze omvatten Google Analytics- en Yandex.Metrica-scripts.

Deze code wordt asynchroon genoemd omdat deze parallel met alle andere scripts wordt uitgevoerd. Wat betekent het?

Standaard wordt alle JavaScript opeenvolgend uitgevoerd, en als er een fout zit in de eerste code of een element dat het laden van andere scripts vertraagt, kan er een fout sluipen in het bijhouden van bezoekstatistieken. Asynchrone code wordt parallel met andere processen uitgevoerd en is de allereerste code wanneer de pagina wordt geladen. Dit garandeert een 100% respons en nauwkeurige gegevensverzameling.

De gegevens die door dit script worden verzonden, vormen statistische objecten. Deze omvatten:

  • paginaweergave;
  • externe transitie;
  • gebruiker.

Deze gegevens zijn nodig om een ​​algemeen beeld van de site te vormen: het aantal bezoekers, hun geslacht en leeftijd, geografie en tijd doorgebracht op de site, in- en uitstappagina's, enz.

Waar te verkrijgen Google-code Analytics en Yandex.Metrics?

Om de Google Analytics-code te krijgen, heeft u het volgende nodig:

  • Registreer of log in bij Google-services.
  • Ga naar het tabblad “Administrators” en vul de velden “Accountnaam”, “Sitenaam”, “Site-URL” in. Het wordt aanbevolen om voor elke nieuwe site een nieuw account aan te maken.
  • 3. Accepteer de gebruiksvoorwaarden.

  • Hierna zijn de Google Analytics-ID's en trackingcode voor u beschikbaar.
  • Om asynchrone code in Yandex te ontvangen. Metriek:

  • Registreer of log in op de dienst.
  • Klik op de knop "Teller toevoegen".
  • Er wordt een nieuw loketvenster geopend. Vul de velden in en vink het selectievakje 'Ik accepteer de voorwaarden' aan.
  • Uw werfteller krijgt een nummer toegewezen. In het onderdeel “Tellercode” vindt u een code die u in de site kunt plakken.
  • Waar moet ik de Google Analytics- en Yandex.Metrica-code plaatsen?

    De nieuwe trackingcode wordt helemaal aan het begin van de pagina geplaatst, in het blok. In principe gebeurt dit via een editor of FTP.

    Maar bent u bijvoorbeeld eigenaar van een website op WordPress, dan zijn er speciale plug-ins met de code “Google Analytics voor WordPress” of Google Analyticator voor u beschikbaar. U plakt eenvoudig uw tracking-ID in het veld Analytics-profiel en het systeem voert de autorisatie zelf uit.

    Eigenaren CMS OpenCart kunt Google Analytics-code invoegen door de tabbladen Extensies -> Analytics te selecteren en de tellercode in het overeenkomstige Google Analytics-veld te plakken.

    Ook kunnen eigenaren van dynamic.php-sites de code zelfstandig in het headerbestand invoegen. php, maar dit dreigt dat de code zal verdwijnen wanneer het systeem wordt bijgewerkt en opnieuw zal moeten worden gepost. Of maak een analyticstracking. php en voeg het toe aan alle PHP-paginasjablonen met een regel code direct na de openingstag.

    Er is tegenwoordig veel vraag naar asynchrone programmering. Vooral bij webontwikkeling, waar de responsiviteit van applicaties een grote rol speelt. Niemand wil zijn tijd verspillen met wachten op een vastgelopen applicatie terwijl u meerdere databasequery's uitvoert, een e-mail verzendt of andere potentieel tijdrovende taken uitvoert. Gebruikers willen reacties op hun acties, en ze willen dat die reacties onmiddellijk plaatsvinden. Wanneer uw applicatie traag wordt, begint u uw klanten te verliezen. Zodra een gebruiker merkt dat een app vastloopt, sluit hij of zij deze in de meeste gevallen eenvoudigweg en keert nooit meer terug. Wanneer de interface vastloopt, is het vanuit het oogpunt van de gebruiker niet duidelijk of uw applicatie defect is, of dat deze langdurige taken uitvoert en enige tijd in beslag neemt.

    Reactievermogen

    Moderne toepassingen hebben de neiging dat te doen responsief, maar sommige potentieel lange taken of blokkerende bewerkingen, zoals netwerk- en bestandssysteem-I/O of databasequery's, kunnen de toepassing aanzienlijk vertragen. Om te voorkomen dat toepassingen blokkeren met deze operaties kunnen we ze uitvoeren achtergrond, waardoor de vertragingen die ze veroorzaken verborgen blijven. De toepassing blijft dus bestaan responsief, omdat het bijvoorbeeld kan blijven werken, kan het de controle teruggeven gebruikersinterface of reageren op andere gebeurtenissen.

    Parallellisme versus asynchronie

    De meeste mensen als ze het zien asynchrone code, denken ze meteen “O, dit is leuk! Ik kan mijn taken parallel uitvoeren!”. Ik zal je misschien teleurstellen, maar in feite is dit niet waar, gelijktijdigheid (asynchronie) en parallellisme zijn niet hetzelfde. Dit is een veel voorkomende fout, dus laten we proberen uit te zoeken waarom.

    Wanneer iets asynchroon wordt uitgevoerd, betekent dit dat het niet-blokkerend wordt uitgevoerd zonder te wachten op voltooiing. Terwijl parallellisme betekent dat meerdere afzonderlijke taken tegelijkertijd als onafhankelijke delen worden uitgevoerd.

    Asynchronie:

    Voer de taak zelf uit in wat vrije tijd en laat me weten wanneer je klaar bent en breng me de resultaten. Op dit moment kan ik doorgaan met mijn taak.

    Asynchrone code vereist het afhandelen van afhankelijkheden tussen uitvoeringsreeksen en doet dit met behulp van callbacks. Wanneer een taak is voltooid, meldt het een andere taak dat deze is voltooid. Asynchrone code heeft vooral betrekking op tijd (opeenvolging van gebeurtenissen).

    Parallellisme:

    Huur zoveel mensen in als je wilt en verdeel de taak onder hen om deze sneller te voltooien en laat me weten wanneer je klaar bent. Ik kan doorgaan met mijn taken of, als de taak dringend is, blijf ik hier en wacht tot u terugkomt met de resultaten. Dan kan ik de resultaten van deze jongens combineren. Parallelle uitvoering vereist vaak meer bronnen, d.w.z. het is vooral afhankelijk van hardware.

    Om het verschil tussen asynchrone en parallelle uitvoering te illustreren echte voorbeelden, kunnen we twee populaire webservers vergelijken: Apache en Nginx. Ze illustreren dit verschil perfect: Nginx is asynchroon en op gebeurtenissen gebaseerd, terwijl Apache parallelle threads gebruikt. Apache maakt voor elk nieuwe threads extra aansluiting, dus er is een maximaal toegestaan ​​aantal verbindingen afhankelijk van beschikbaar geheugen in het systeem. Wanneer deze verbindingslimiet wordt bereikt, weigert Apache aanvullende verbindingen. De beperkende factor bij Apache instellen is geheugen (onthoud dat parallelle uitvoering vaak hardware-afhankelijk is). Als de thread stopt, wacht de client op een reactie totdat de thread vrijkomt en retourneert een reactie.

    Nginx werkt anders dan Apache en creëert geen nieuwe threads voor elk binnenkomend verzoek. Het heeft een hoofdwerkproces (of meerdere processen; in de praktijk wordt het vaak aanbevolen om één werker per processor (CPU) te hebben), die single-threaded is. Deze werknemer kan duizenden verwerken gelijktijdige verbindingen. Het doet dit asynchroon met een enkele thread, in plaats van gebruik te maken van parallelle uitvoering met meerdere threads.

    Asynchronie (of gelijktijdigheid) is dus een manier om taken uit te voeren. Het is een samenstelling van onafhankelijk uitgevoerde taken. Gelijktijdigheid is de gelijktijdige uitvoering van verschillende taken (ze kunnen al dan niet gerelateerd zijn). Bij asynchronie hebben we te maken met veel verschillende taken tegelijkertijd. Gelijktijdigheid doet veel dingen tegelijk. Klinkt hetzelfde, maar de onderliggende ideeën zijn anders. Gelijktijdigheid gaat over structuur, terwijl parallellisme over uitvoering gaat.

    Van de vertaler:

    Asynchronie: je hebt een backend-taak die je uitvoert op dit moment. Een front-end developer heeft u benaderd met het verzoek de API enigszins te corrigeren. Je hebt opgeslagen in je branche, bent overgestapt naar een andere (je bent momenteel bezig met een API-taak), hebt deze gecorrigeerd en bent teruggekeerd naar je taak. Vervolgens werd je opgeroepen voor een bijeenkomst over een bepaald onderwerp, je nam een ​​pauze, ging (op het moment dat je de derde taak uitvoert, die wordt voortgezet bij de volgende bijeenkomst) en keerde weer terug naar je eerste taak. Je voerde deze drie taken asynchroon uit - met andere woorden, je onderbrak en schakelde tussen taken, en besteedde er wat tijd aan.

    Parallellisme: Als we het hebben over parallellisme in de persoon van één persoon, dan komen een paar voor de hand liggende voorbeelden in ons op. Speel bijvoorbeeld gitaar en zing. Op dit moment speelt een van je handen de akkoorden, de andere tokkelt (of tokkelt) en bovendien ben je nog steeds aan het zingen. Op dit punt voert u drie taken parallel uit. Of nog een voorbeeld: je luistert naar Mozart tijdens het diner. Hier voer je twee taken parallel uit: eten en luisteren. Maar als we terugkeren naar ontwikkelingstaken, krijgen we een duidelijker voorbeeld. Stel je voor dat je in een team van 4 ontwikkelaars werkt. Je team is een soort enkele machine quad-coreprocessor. Elke ontwikkelaar is één kern. Wanneer de sprint begint, voltooit elk van de 4 ontwikkelaars hun taken parallel met de andere 3 ontwikkelaars, en aan het einde van de sprint breng je alles samen.

    Waarom op de backend?

    Nu ben je misschien verontwaardigd dat je niet echt om het reactievermogen van de achterkant geeft. Je hebt al deze nare dingen zoals asynchrone JavaScript, en het enige dat uw server hoeft te doen, is eenvoudigweg reageren op verzoeken. Het garanderen van de responsiviteit van gebruikers is dus de taak van het front, niet die van u. Ja, dit is waar, maar de backend is niet beperkt tot alleen API-reacties. Soms moet u complexe taken beheren, zoals een server voor het downloaden van video's. In dit geval is reactie misschien niet de belangrijkste factor, maar daar komen we wel op uit gebrek aan middelen omdat de aanvraag moet wachten. Hij kan wachten op operaties bestandssysteem, netwerkverbinding, zoekopdrachten naar de database, enzovoort. Vaak zijn deze I/O-bewerkingen extreem traag vergeleken met berekeningen op de CPU, bijvoorbeeld wanneer we videobestanden converteren. En terwijl wij langzaam een ​​bestand opslaan of lezen, moet onze processor wachten en niets doen, in plaats van iets te doen nuttig werk. Zoals we al zeiden, kunnen we deze taken op de achtergrond uitvoeren in plaats van te wachten. Hoe? Lees hieronder.

    Asynchrone PHP

    De JavaScript-wereld beschikt al over ingebouwde ondersteuning en tools voor het schrijven van asynchrone code. En er is ook NodeJs, waarmee je asynchrone applicaties kunt schrijven. In JavaScript kunnen we de functie setTimeout() gebruiken om asynchrone code te demonstreren:

    SetTimeout(function() ( console.log("Na time-out"); ), 1); console.log("Voor de time-out");

    Wanneer we deze code uitvoeren, zien we het volgende:

    Voor time-out Na time-out

    De functie setTimeout() zet code in de wachtrij die moet worden uitgevoerd nadat de huidige call-stack is voltooid. Dit betekent dat we de synchrone codestroom onderbreken en de uitvoering van een deel van de code vertragen. De tweede aanroep van console.log() wordt uitgevoerd vóór de eerste (binnen de functie settimeout()), die in de wachtrij stond.

    Maar hoe zit het met PHP? Welnu, in PHP hebben we geen goede en handige tools om echt asynchrone code te schrijven. Er is geen functie die equivalent is aan settimeout() en we kunnen bepaalde code eenvoudigweg niet vertragen of in de wachtrij plaatsen. Dat is de reden waarom frameworks en bibliotheken zoals Amp en ReactPHP opdoken. Hun belangrijkste idee is om de fijne kneepjes van de taal op laag niveau voor ons te verbergen en tools en abstracties op hoog niveau te bieden die kunnen worden gebruikt om asynchrone code te schrijven en gelijktijdigheid te beheren, net zoals we zouden kunnen doen in JavaScript en NodeJS.

    Waarom zou ik PHP gebruiken als we NodeJs en Go hebben?

    Een soortgelijke vraag rijst het vaakst als het gaat om asynchrone PHP. Om de een of andere reden is de gemeenschap vaak tegen met behulp van PHP als hulpmiddel voor het schrijven van asynchrone code. Er is altijd wel iemand die voorstelt om gewoon Go en NodeJs te gebruiken.

    Dus kloon de repository en ga naar de Part_1 branch:

    Git-kloon https://github.com/Peleke/promises/ git checkout Part_1-Basics

    Je bent op het pad naar de waarheid. In onze route staan ​​de volgende vragen:

    • Probleem met terugbelfunctie
    • Beloften: definities en opmerkingen uit de specificatie
    • Beloften en niet-omkering van de controle
    • Flowcontrol met beloftes
    • Laten we de betekenis van dan begrijpen: afwijzen en oplossen
    Asynchronie

    Als je ooit met JavaScript hebt gewerkt, heb je al gehoord dat dit fundamenteel is niet-blokkerend of asynchroon. Maar wat betekent dit?

    Synchrone en asynchrone code

    Synchrone code wordt uitgevoerd vóór elke code die erop volgt. Je zult de term blokkeren vaak zien als synoniem voor synchroon, omdat het de rest van het programma blokkeert totdat het is voltooid.

    // readfile_sync.js "gebruik strikt"; // Dit voorbeeld komt van Node, dus voer het niet in een browser uit. const bestandsnaam = "tekst.txt", fs = require("fs"); console.log("Bestand lezen... "); // readFileSync BLOCKS totdat de waarde wordt geretourneerd. // Het programma wacht en doet niets // totdat deze bewerking is voltooid. const bestand = fs.readFileSync(`$(__mapnaam)/$(bestandsnaam)`); // Dit wordt ALTIJD uitgevoerd nadat readFileSync is voltooid. . . console.log("Klaar met lezen."); // . . . En hier wordt ALTIJD de inhoud van "bestand" weergegeven. console.log(`Inhoud: $(file.toString())`);

    Asynchrone code is precies het tegenovergestelde: het laat de rest van het programma draaien terwijl het bezig is met het verwerken van langlopende bewerkingen zoals I/O of netwerkverzoeken. Het wordt ook wel niet-blokkerende code genoemd. Hier is de asynchrone analoog van het vorige fragment:

    // readfile_async.js "gebruik strikt"; // Dit voorbeeld komt van Node, dus voer het niet in een browser uit. const bestandsnaam = "text.txt", fs = require("fs"), getContents = function printContent (bestand) ( try ( return file.toString(); ) catch (TypeError) ( return file; ) ) console.log( "Bestand lezen..."); console.log("=".herhaling(76)); // readFile wordt asynchroon uitgevoerd. // Het programma blijft doen wat er na LINE A komt // terwijl readFile zijn ding doet. We zullen binnenkort de callback-functies in detail bespreken, maar let voorlopig alleen op de volgorde van de logbestanden; fs.readFile(`$(__dirname)/$(filename)`, function (err, inhoud) ( file = inhoud; console.log(`Eh, eigenlijk ben ik nu klaar. De inhoud is: $( getContents(bestand ) )`); // LINE A // Dit wordt ALTIJD afgedrukt voordat het bestand is gelezen. // Deze logs zijn misleidend en nutteloos ))`); ;

    Het belangrijkste voordeel van synchrone code is dat deze gemakkelijker te lezen en te begrijpen is: synchrone programma's worden van boven naar beneden uitgevoerd en regel n eindigt altijd vóór regel n+1.

    Het grootste nadeel van synchrone code is de traagheid, die vaak pijnlijk is. Het feit dat de browser elke keer dat de gebruiker een verzoek probeert te doen aan de server twee seconden langzamer wordt, zorgt voor een uiterst onaangenaam gevoel voor hem.

    En dat is de reden waarom JavaScript in de kern non-blocking is gemaakt.

    Asynchroon bellen

    De overgang naar asynchronie geeft ons snelheid en neemt de lineariteit weg. Zelfs het eenvoudige script hierboven demonstreert dit. Herinneren:

  • Er is geen andere manier om te weten wanneer een bestand beschikbaar zal zijn dan door de controle door te geven aan readFile , die ons zal vertellen wanneer het klaar is;
  • Onze programma's worden niet langer in dezelfde volgorde uitgevoerd als waarin ze worden gelezen, waardoor ze moeilijker te begrijpen zijn.
  • Deze problemen zijn genoeg om ons tot het einde van het artikel bezig te houden.

    Terugbelverzoeken en terugvalverzoeken

    Laten we proberen ons readFile-voorbeeld een beetje te vereenvoudigen.

    "gebruik strikt"; const bestandsnaam = "wegwerp.txt", fs = require("fs"); laat bestand, nutteloos; nutteloos = fs.readFile(`$(__dirname)/$(filename)`, function callback (fout, inhoud) ( file = inhoud; console.log(`Begrepen. De inhoud is: $(content)`); console. log(`... Maar nutteloos is nog steeds $(nutteloos).` )); // Met dank aan Rava voor het ontdekken van een fout in deze regel. console.log(`Bestand is $(nutteloos), maar dat zal snel veranderen.`);

    Omdat readFile niet blokkeert, moet deze methode onmiddellijk terugkeren zodat het programma verder kan worden uitgevoerd. Omdat onmiddellijk dit is duidelijk niet genoeg om I/O-bewerkingen uit te voeren, de methode retourneert ongedefinieerd en we voeren de rest van het programma zo vaak uit als we kunnen zonder readFile uit te voeren... Daarna lezen we het bestand.

    De vraag is: hoe kunnen we weten dat de lezing voltooid is?

    Helaas, op geen enkele manier. Maar readFile kan dat wel. In het bovenstaande codefragment hebben we readFile twee argumenten doorgegeven: de naam van het bestand en een functie genaamd callback-functie die we onmiddellijk willen uitvoeren nadat het lezen van het bestand is voltooid.

    Het werkt ongeveer als volgt: readFile kijkt naar wat zich in $(__dirname)/$(filename) bevindt en het programma gaat verder met zijn werk. Zodra readFile weet wat er staat, voert het een callback uit met de inhoud als argument, en als er een fout optreedt, retourneert het error .

    Het is belangrijk om te begrijpen: we kunnen niet weten wanneer de inhoud van een bestand klaar zal zijn - alleen readFile kan dat. We geven er dus een callback-functie aan en vertrouwen erop dat deze deze correct gebruikt.

    Dit algemeen patroon om met asynchrone functies te werken: roep ze aan met parameters en geef ze een callback-functie door om deze uit te voeren met het resulterende resultaat.

    Terugbelfuncties zijn een werkbare oplossing, maar niet ideaal. Ze hebben twee grote problemen:

  • Omkering van controle
  • Complexe foutafhandeling
  • Omkering van controle

    Het eerste probleem is het vertrouwensprobleem.

    Wanneer we readFile doorgeven aan onze callback-functie, vertrouwen we erop dat deze zal worden aangeroepen. En daar hebben we absoluut geen garanties voor. Net zoals er geen garantie is dat bij het oproepen de juiste parameters, in de juiste volgorde en het vereiste aantal keren worden doorgegeven.

    In de praktijk is dit natuurlijk niet zo dodelijk: we schrijven al bijna 20 jaar callback-functies en hebben het internet nog steeds niet kapot gemaakt. En in dit geval weten we dat het veilig genoeg is om op Node te vertrouwen.

    Maar het overdragen van de controle is van cruciaal belang belangrijke aspecten het delen van uw applicatie met een derde partij is riskant en is vaak de oorzaak van moeilijk te vinden Heisenbugs.

    Impliciete foutafhandeling

    In synchrone code kunt u try/catch/finally gebruiken om fouten af ​​te handelen.

    "gebruik strikt"; // Dit voorbeeld komt van Node, dus voer het niet in een browser uit. const bestandsnaam = "tekst.txt", fs = require("fs"); console.log("Bestand lezen... "); laten bestand; try ( // Onjuiste bestandsnaam. D"oh! file = fs.readFileSync(`$(__dirname)/$(filename + "a")`); console.log(`Begrepen. De inhoud is: "$(file )"`); ) catch (err) ( console.log(`Er was een/n $(err): bestand is $(bestand)`); ) console.log("Fouten opsporen, zoals een bo$$. ");

    Asynchrone code probeert het natuurlijk, maar...

    "gebruik strikt"; // Dit voorbeeld komt van Node, dus voer het niet in een browser uit. const bestandsnaam = "wegwerp.txt", fs = require("fs"); console.log("Bestand lezen... "); laten bestand; try ( // Onjuiste bestandsnaam. D"oh! fs.readFile(`$(__dirname)/$(bestandsnaam + "a")`, function (err, inhoud) ( file = inhoud; )); // Dit is zal niet worden uitgevoerd zolang het bestand ongedefinieerd is console.log(`Begrepen. De inhoud is: "$(file)"`); catch (err) ( // In dit geval zou catch moeten vuren, maar dat gebeurt nooit. / / Dit komt omdat readFile fouten doorgeeft aan de callback // in plaats van deze terug te geven. console.log(`Er was een/n $(err): bestand is $(file)`);

    Dit werkt niet zoals verwacht. Omdat het try-blok readFile omsluit, welke keert altijd succesvol terug ongedefinieerd. In een dergelijke situatie zal een poging altijd geen incident opleveren.

    De enige manier De manier waarop readFile u over fouten kan vertellen, is door deze door te geven aan uw callback-functie, waar u ze zelf afhandelt.

    "gebruik strikt"; // Dit voorbeeld komt van Node, dus voer het niet in een browser uit. const bestandsnaam = "wegwerp.txt", fs = require("fs"); console.log("Bestand lezen... "); fs.readFile(`$(__dirname)/$(bestandsnaam + "a")`, function (err, inhoud) ( if (err) ( // catch console.log(`Er was een/n $(err). `); ) else ( // probeer console.log(`Begrepen. De inhoud van het bestand is: "$(file)"`); ) ));

    Dit voorbeeld is natuurlijk niet zo slecht, maar geef foutinformatie door aan grote programma's wordt snel onbeheersbaar.

    Beloftes lossen deze beide problemen en een aantal andere op zonder de controle om te keren en door onze asynchrone code te ‘synchroniseren’, zodat de vertrouwde foutafhandeling mogelijk is.

    Beloften

    Stel je voor dat je zojuist de volledige You Don't Know JS-catalogus bij O'Reilly hebt besteld. Voor je zuurverdiende geld stuurden ze je een ontvangstbewijs dat je volgende maandag een gloednieuwe stapel boeken zou ontvangen. Tot deze gelukkige maandag zul je geen boeken hebben, maar je gelooft dat ze zullen verschijnen, omdat ze beloofden ze naar je toe te sturen.

    Deze belofte is voldoende zodat je, zelfs vóór de bevalling, tijd kunt inplannen voor dagelijks lezen, een paar boeken kunt uitkiezen om aan vrienden uit te lenen, en je baas ook kunt laten weten dat je het volgende week te druk zult hebben met lezen om naar kantoor te komen . Je hoeft geen boeken te hebben om zulke plannen te maken; je hoeft alleen maar te weten dat je ze krijgt.

    Natuurlijk kan O’Reilly over een paar dagen melden dat maandag geen lot is en dat de boeken iets later zullen verschijnen, met andere woorden: de gewenste waarde zal in de toekomst liggen. Je beschouwt de belofte als een verwachte waarde en schrijft code alsof je die al hebt.

    Er is een kleine complicatie bij deze gebeurtenis: beloften behandelen de onderbreking van de volgorde van uitvoering van instructies intern en maken het gebruik van een speciaal catch-trefwoord mogelijk voor foutafhandeling. Dit wijkt enigszins af van de synchrone versie, maar is beter dan het coördineren van meerdere foutafhandelaars binnen ongecoördineerde callback-functies.

    En zodra de belofte u een waarde oplevert, heeft u al besloten wat u ermee gaat doen. Dit lost het inversion-of-control-probleem op: u beheert uw applicatielogica rechtstreeks zonder de controle aan derden over te dragen.

    Levenscyclus belofte: kort overzicht staten

    Stel je voor dat je een belofte gebruikt om een ​​API aan te roepen.

    Omdat de server niet onmiddellijk kan reageren, kan de belofte niet onmiddellijk de definitieve waarde of het foutenrapport bevatten. In deze staat worden beloften 'in behandeling' genoemd. Dit is hetzelfde geval als het wachten op een stapel boeken in ons voorbeeld.

    Zodra de server heeft gereageerd, hebben we twee mogelijke uitkomsten:

  • De belofte krijgt de verwachte waarde, wat betekent dat deze wordt vervuld. Je boeken zijn gearriveerd.
  • Ergens onderweg deed zich een fout voor: de belofte werd afgewezen. Je hebt een melding ontvangen dat er geen boeken zullen zijn.
  • In totaal krijgen we er drie mogelijke staten belofte, en de vervulde of verworpen staten kunnen niet door een andere staat worden vervangen.

    Nu we de basisconcepten hebben begrepen, gaan we kijken hoe we dit allemaal kunnen gebruiken.

    Fundamentele beloftemethoden

    Een belofte vertegenwoordigt het eindresultaat van een asynchrone operatie. De belangrijkste manier om met een belofte om te gaan, is door de toenmalige methode te gebruiken, die callback-functies registreert om het eindresultaat van de belofte te ontvangen of de reden te rapporteren waarom deze is mislukt.

    In deze sectie gaan we er dieper op in basisgebruik beloften:

  • Beloftes maken met een constructeur;
  • Ga vastberaden om met een succesvol resultaat;
  • Foutafhandeling bij afkeur;
  • Flow control instellen met then en catch .
  • In ons voorbeeld zullen we beloftes gebruiken om de code van onze fs.readFile-functie op te schonen.

    Beloften creëren

    De eenvoudigste manier is om rechtstreeks beloften te maken met behulp van de constructor.

    "gebruik strikt"; const fs = vereisen("fs"); const tekst = new Promise(function (oplossen, afwijzen) ( // Doet niets ))

    Merk op dat we een functie als argument doorgeven aan de belofteconstructor. Dit is waar we de belofte vertellen hoe de asynchrone bewerking moet worden uitgevoerd; wat we moeten doen als we krijgen wat we verwachten en wat we moeten doen als er een fout optreedt. In het bijzonder:

  • Het oplossingsargument is een functie die samenvat wat we willen doen wanneer de verwachte waarde wordt geretourneerd. Wanneer we de verwachte waarde (val) ontvangen, geven we deze door als argument om op te lossen: solve(val) .
  • Het afwijzingsargument is ook een functie die onze acties vertegenwoordigt in geval van een fout. Als we een fout (err) ontvangen, zullen we daarmee afkeuren: afwijzen(err) .
  • Ten slotte verwerkt de functie die we aan de belofte-constructor hebben doorgegeven de asynchrone code zelf. Als het het verwachte resultaat oplevert, roepen we de oplossing aan met de resulterende waarde. Als er een fout optreedt, roepen we een afwijzing aan met die fout.
  • In ons voorbeeld zullen we fs.readFile in een belofte verpakken. Hoe moeten onze vastberadenheid en afwijzing eruitzien?

  • Als dit lukt, willen we console.log aanroepen om de inhoud van het bestand uit te voeren.
  • Als het niet lukt, doen we hetzelfde: we geven de fout weer in de console.
  • Zo krijgen we het volgende:

    // constructor.js const solve = console.log, afwijzen = console.log;

    Vervolgens moeten we een functie schrijven die we doorgeven aan de constructor. Vergeet niet dat we het volgende moeten doen:

  • Bestand lezen;
  • Indien succesvol, los het op met de inhoud ervan;
  • Indien dit niet lukt, wijs dan af met de ontvangen foutmelding.
  • Dus:

    // constructor.js const text = new Promise(function (oplossen, weigeren) ( // Normale fs.readFile-oproep, maar binnen Promise-constructor . . fs.readFile("text.txt", function (err, text) ( / / . Roep 'reject' aan als er een fout is... if (err) 'reject(err);//' en bel 'resolve' anders aan. else // fs.readFile retourneert buffer, dus je moet de toString() gebruiken. .resolve(tekst.naarString());

    Dus technisch gezien is alles klaar: deze code creëert een belofte, die precies doet wat we nodig hebben. Maar als we deze code uitvoeren, zult u merken dat deze wordt uitgevoerd zonder enig resultaat of fout op te leveren.

    Ze deed een belofte en toen...

    Het probleem is dat we onze methoden voor vastberadenheid en afwijzing hebben geschreven, maar deze niet daadwerkelijk in de belofte hebben opgenomen. Om dit te kunnen doen, moeten we er nog één leren kennen basisfunctie om de stroom te controleren op basis van beloften: dan.

    Elke belofte heeft een methode die twee functies als argumenten gebruikt: oplossen en afwijzen, in die volgorde. Door vervolgens een belofte aan te roepen en deze twee functies eraan door te geven, worden ze beschikbaar voor de belofteconstructeur.

    // constructor.js const tekst = new Promise(function (oplossen, afwijzen) ( fs.readFile("text.txt", function (err, tekst) ( if (err) afwijzen(err); else solve(text.toString) ()); )) )).dan(oplossen, afwijzen);

    Dus de belofte zal het bestand lezen en de oplossingsmethode aanroepen die we hebben geschreven als dit lukt.

    Het is belangrijk om te onthouden dat er dan altijd een belofte-object wordt geretourneerd. Dit betekent dat u meerdere oproepen kunt koppelen om een ​​complexe en synchroon ogende stroom van asynchrone bewerkingen te creëren. In het volgende artikel zullen we dit in meer detail bespreken, en we zullen begrijpen hoe het eruit ziet door het vangstvoorbeeld te analyseren.

    Syntactische suiker voor foutafhandeling

    We hebben toen twee functies doorgegeven: besluiten om te worden gebeld in geval van succes en afwijzen in geval van een fout.

    Beloften hebben ook een toenmalige functie genaamd catch. Het neemt de afwijzingshandler als enige argument.

    Omdat dan altijd een belofte wordt geretourneerd, kunnen we in ons voorbeeld alleen de oplossingshandler aan toen doorgeven en deze vervolgens met de afwijzingshandler verbinden met de catch-keten.

    Const tekst = new Promise(functie (oplossen, afwijzen) ( fs.readFile("tex.txt", functie (err, tekst) ( if (err) afwijzen(err); else solve(text.toString()); ) ) )).dan(oplossen) .catch(afwijzen);

    Ten slotte is het de moeite waard om te vermelden dat catch(reject) slechts syntactische suiker is voor then(undefinieerd, afwijzen) . Dat wil zeggen, we kunnen ook schrijven:

    Const tekst = new Promise(functie (oplossen, afwijzen) ( fs.readFile("tex.txt", functie (err, tekst) ( if (err) afwijzen(err); else solve(text.toString()); ) ) )).dan(oplossen) .dan(ongedefinieerd, afwijzen);

    Maar dergelijke code zal minder leesbaar zijn.

    Conclusie

    Beloften zijn dat onmisbaar hulpmiddel voor asynchrone programmering. In het begin kunnen ze intimiderend zijn, maar alleen totdat je er vertrouwd mee bent: gebruik ze een paar keer en ze zullen net zo natuurlijk voor je worden alsof / anders.

    In het volgende artikel gaan we aan de slag met het converteren van op callback gebaseerde code naar code die beloften gebruikt. We bekijken ook , een populaire beloftebibliotheek.

    Voor meer informatie kunt u het artikel States and Fates van Domenic Denicola raadplegen om inzicht te krijgen in de terminologie en het hoofdstuk van Kyle Simpson over beloften uit de stapel boeken waarin we vroeger naar beloften keken.

    Eén van de sterken kanten van JavaScript- verwerking van asynchrone code. In plaats van de draad van een taak te blokkeren, plaatst asynchrone code gebeurtenissen in de wachtrij die worden uitgevoerd nadat andere delen van het programma zijn voltooid. Voor beginners kan het begrijpen van asynchrone code echter een moeilijk proces zijn. Deze les bedoeld om de situatie te verduidelijken.

    Basisbeschrijving van asynchrone code

    De belangrijkste functies van asynchrone JavaScript-code zijn setTimeout en setInterval. De setTimeout-functie doet dat wel gegeven functie nadat een bepaald tijdsinterval is verstreken. Er is een return-functie nodig als eerste argument en een tijd (in milliseconden) als tweede argument. Hier is een voorbeeld van gebruik:

    Console.log("a"); setTimeout(function() (console.log("c")), 500); setTimeout(function() (console.log("d")), 500); setTimeout(function() (console.log("e")), 500); console.log("b");

    We verwachten “a”, “b” in de console te zien, en na ongeveer 500 ms – “c”, “d” en “e”. Ik gebruik de term 'ongeveer' omdat setTimeout in werkelijkheid onvoorspelbaar werkt. Zelfs in de HTML5-specificatie stelt: "De API garandeert dus niet dat de timer precies volgens het opgegeven schema zal worden uitgevoerd. Vertragingen zijn waarschijnlijk te wijten aan processorbelasting, andere taken en andere factoren."

    Interessant genoeg zal de time-out pas optreden als alle andere code in het blok is uitgevoerd. Dat wil zeggen: als er een time-out is ingesteld en het duurt lang voordat een functie wordt uitgevoerd, begint de time-out pas te tellen als de functie is voltooid. In werkelijkheid worden de asynchrone functies setTimeout en setInterval in de wachtrij geplaatst, ook wel een gebeurtenislus genoemd.

    De gebeurtenislus is een wachtrij met retourfuncties. Wanneer een asynchrone functie wordt uitgevoerd, wordt de return-functie in de wachtrij geplaatst. JavaScript activeert geen gebeurtenislusverwerking terwijl de code wordt uitgevoerd nadat de asynchrone functie wordt uitgevoerd. Dit feit betekent dat JavaScript-code is niet multi-threaded, hoewel dat wel zo lijkt. De gebeurtenislus is een FIFO-wachtrij (first in, first out), wat betekent dat retourfuncties worden uitgevoerd in de volgorde waarin ze worden ontvangen. Voor het node.js-platform is gekozen voor JavaScript juist vanwege het eenvoudige proces van het ontwikkelen van dergelijke code.

    AJAX

    Asynchrone JavaScript en XML (AJAX) hebben het profiel van JavaScript voor altijd veranderd. De browser kan de webpagina bijwerken zonder opnieuw op te starten. AJAX-implementatiecode verschillende browsers kan lang en saai zijn. Maar dankzij jQuery(en andere bibliotheken) is AJAX een zeer eenvoudige en elegante oplossing geworden voor het leveren van client-server-communicatie.

    Gegevens asynchroon ontvangen met behulp van jQuery-methode$.ajax is een eenvoudig cross-browserproces dat het echte proces verbergt. Bijvoorbeeld:

    Het is gebruikelijk, maar onjuist, om aan te nemen dat de gegevens onmiddellijk beschikbaar zullen zijn nadat $.ajax is aangeroepen. Maar de werkelijkheid ziet er anders uit:

    Xmlhttp.open("GET", "sommige/ur/1", true); xmlhttp.onreadystatechange = functie(data) ( if (xmlhttp.readyState === 4) ( console.log(data); ) ); xmlhttp.send(null);

    Een vergelijkbare methode voor het publiceren van gebeurtenissen wordt gebruikt in het mediatorpatroon, dat wordt gebruikt in de postal.js-bibliotheek. Het mediatorpatroon heeft een mediator beschikbaar voor alle objecten die gebeurtenissen opvangt en publiceert. Met deze aanpak heeft het ene object geen directe koppelingen naar een ander object en zijn daarom alle objecten van elkaar losgekoppeld.

    Beantwoord nooit beloften via openbare API's. Deze praktijk bindt API-gebruikers belooft en bemoeilijkt de modernisering van de code. Maar een combinatie van beloften voor interne behoeften en gebeurtenissen voor externe API's kan resulteren in een geweldige, ontkoppelde en gemakkelijk te onderhouden applicatie.

    In het vorige voorbeeld wordt de callback-functie doSomethingCoolWithDirections uitgevoerd wanneer de twee voorgaande aanroepen van de geocodefuncties zijn voltooid. De functie doSomethingCoolWithDirections kan het antwoord van getRoute overnemen en als bericht publiceren.

    Var doSomethingCoolWithDirections = function(route) ( postal.channel("ui").publish("directions.done", ( route: route )); );

    Deze aanpak maakt het mogelijk dat andere delen van de applicatie kunnen reageren op asynchrone callbacks zonder directe verwijzingen naar het object dat het verzoek genereert. wat doet het mogelijke update verschillende gebieden op de pagina wanneer de richting wordt verkregen. In een typische jQuery Ajax-configuratie vereist het wijzigen van n=direction een succesvolle aanroep van de callback-functie. Deze aanpak is moeilijk te handhaven en het gebruik van berichten maakt het veel gemakkelijker om meerdere delen van de gebruikersinterface bij te werken.

    Var UI = function() ( this.channel = postal.channel("ui"); this.channel.subscribe("directions.done", this.updateDirections).withContext(this); ); UI.prototype.updateDirections = function(data) ( // De route is beschikbaar in data.route, nu hoeft u alleen maar de interface bij te werken); app.ui = nieuwe gebruikersinterface();

    Andere implementaties van het proxypatroon worden gebruikt in de amplify-bibliotheken, PubSubJS en radio.js.

    Conclusie

    JavaScript wel eenvoudig proces creatie asynchrone applicaties. Het gebruik van beloftes, gebeurtenissen of benoemde functies vermijdt de callback-hel.

    Declaraties van variabelen en functies, maar om van deze eigenschap van JS een probleem te maken, moet je heel je best doen. Synchrone JavaScript-code heeft slechts één ernstig nadeel: alleen kom je er niet ver mee.

    Bijna elk bruikbaar JS-programma is geschreven met behulp van asynchrone ontwikkelingsmethoden. Dit is waar callback-functies, of ‘callbacks’ in het gewone taalgebruik, een rol gaan spelen. Hier gebruiken we ‘beloften’, of Promise-objecten, gewoonlijk beloften genoemd. Hier kun je generatoren en async/await-constructies tegenkomen. Asynchrone code is, vergeleken met synchrone code, doorgaans moeilijker te schrijven, lezen en onderhouden. Soms verandert het in volledig griezelige structuren zoals de callback-hel. Je kunt echter niet zonder.

    Vandaag stellen we voor om te praten over de kenmerken van callbacks, beloftes, generatoren en async/await-constructies, en na te denken over hoe je eenvoudige, begrijpelijke en effectieve asynchrone code kunt schrijven.

    Over synchrone en asynchrone code Laten we beginnen met het bekijken van fragmenten van synchrone en asynchrone JS-code. Hier is bijvoorbeeld een typische synchrone code:

    Console.log("1") console.log("2") console.log("3")
    Het geeft zonder veel moeite cijfers van 1 tot 3 weer op de console.

    Nu is de code asynchroon:

    Console.log("1") setTimeout(functie afterTwoSeconds() (console.log("2")), 2000) console.log("3")
    Hier wordt de reeks 1, 3, 2 uitgevoerd. Het getal 2 wordt uitgevoerd door de callback, die de gebeurtenis verwerkt van de timer die is opgegeven bij het aanroepen van de setTimeout-functie. Het terugbelverzoek wordt binnengeroepen in dit voorbeeld, binnen 2 seconden. De applicatie stopt niet met wachten totdat deze twee seconden zijn verstreken. In plaats daarvan zal het programma doorgaan met uitvoeren en de functie afterTwoSeconds aanroepen wanneer de timer afloopt.

    Als je net begint als JS-ontwikkelaar, vraag je je misschien af: “Waarom is dit allemaal nodig? Misschien is het mogelijk om asynchrone code om te zetten in synchroon?” Laten we zoeken naar antwoorden op deze vragen.

    Probleemstelling Stel dat we worden geconfronteerd met de taak om een ​​GitHub-gebruiker te vinden en gegevens over zijn repository's te downloaden. Belangrijkste probleem Het punt hier is dat we de exacte gebruikersnaam niet weten, dus moeten we alle gebruikers vermelden met namen die lijken op wat we zoeken, en hun repository's.

    Qua interface beperken we ons tot iets eenvoudigs.


    Eenvoudige interface om te zoeken naar GitHub-gebruikers en hun bijbehorende repository's

    In de voorbeelden worden verzoeken uitgevoerd met behulp van XMLHttpRequest (XHR), maar u kunt hier eenvoudig jQuery ($.ajax) of een modernere versie gebruiken standaard aanpak, gebaseerd op het gebruik van de fetch-functie. Beide komen neer op het gebruiken van beloftes. De code verandert afhankelijk van de reis, maar hier is een voorbeeld om mee te beginnen:

    // het url-argument zou zoiets kunnen zijn als "https://api.github.com/users/daspinola/repos" function request(url) ( const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = function(e) ( if (xhr.readyState === 4) ( if (xhr.status === 200) ( // Code voor het verwerken van de succesvolle voltooiing van het verzoek ) else ( // Verwerk het antwoord met een foutmelding ) ) ) xhr .ontimeout = function () ( // Het wachten op een antwoord duurde te lang, hier is de code die een soortgelijke situatie afhandelt ) xhr.open("get", url, true) xhr.send();
    Houd er rekening mee dat het in deze voorbeelden niet belangrijk is wat er uiteindelijk van de server zal komen en hoe het zal worden verwerkt, maar de organisatie van de code zelf met behulp van verschillende benaderingen die u kunt gebruiken bij uw asynchrone ontwikkeling.

    Terugbelfuncties Je kunt veel dingen doen met functies in JS, inclusief het doorgeven ervan als argumenten aan andere functies. Dit wordt meestal gedaan om de doorgegeven functie aan te roepen nadat een proces is voltooid, wat enige tijd kan duren. We hebben het over callback-functies. Hier is een eenvoudig voorbeeld:

    // Roep de functie "doThis" aan met een andere functie als parameter, in dit geval de functie "andThenThis". De functie "doThis" voert de daarin opgenomen code uit, waarna in juiste moment, zal de functie "andThenThis" aanroepen. doThis(andThenThis) // Binnen "doThis" is de functie die eraan wordt doorgegeven toegankelijk via de parameter "callback". In feite is het slechts een variabele die een verwijzing opslaat naar de functie function andThenThis() ( console.log(" en dan dit") ) / / U kunt de parameter een naam geven waarin de callback-functie zal zijn wat u maar wilt, "callback" is slechts een veel voorkomende optiefunctie doThis(callback) ( console.log("this first") // In volgorde voor de functie waarnaar de verwijzing is opgeslagen in de aangeroepen variabele, moet u een haakje plaatsen achter de naam van de variabele, “()”, anders werkt niets callback() )
    Met behulp van deze aanpak om ons probleem op te lossen, kunnen we een verzoekfunctie als volgt schrijven:

    Functieverzoek(url, callback) ( const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = function(e) ( if (xhr.readyState === 4) ( if (xhr.status === 200) ( callback(null, xhr.response) ) else ( callback(xhr.status, null) ) ) xhr.ontimeout = function () ( console.log("Timeout") ) xhr.open("get", url , waar) xhr.send();
    Nu accepteert de functie voor het uitvoeren van het verzoek de callback-parameter. Daarom wordt, na het uitvoeren van het verzoek en het ontvangen van het serverantwoord, de callback zowel in geval van een fout als in geval van succesvolle voltooiing van de bewerking aangeroepen.

    Const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` request(userGet, function handleUsersList(error, users) ( if (error) throw error const list = JSON.parse (users).items list.forEach(function(user) ( request(user.repos_url, function handleReposList(err, repos) ( if (err) throw err //We zullen de lijst met repositories hier verwerken)) )) ))
    Laten we eens kijken wat hier gebeurt:

    • Er wordt een zoekopdracht uitgevoerd om de repository's van de gebruiker op te halen (in dit geval laad ik mijn eigen repository's);
    • Nadat het verzoek is voltooid, wordt de callback handleUsersList aangeroepen;
    • Als er geen fouten zijn, parseren we het serverantwoord met J SON.parse en converteren we het voor het gemak naar een object;
    • Daarna herhalen we de lijst met gebruikers, omdat deze meer dan één element kan bevatten, en voor elk van hen doorzoeken we de lijst met repositories met behulp van de URL die voor elke gebruiker wordt geretourneerd nadat het eerste verzoek is gedaan. De implicatie is dat repos_url de URL is voor onze volgende verzoeken, en dat we deze vanaf het eerste verzoek hebben gekregen.
    • Wanneer het verzoek om de repositorygegevens te laden is voltooid, wordt de callback aangeroepen, nu handleReposList . Hier kunt u, net als bij het laden van een lijst met gebruikers, fouten of payloads afhandelen die een lijst met gebruikersrepository's bevatten.
    Houd er rekening mee dat het gebruik van een error-object als eerste parameter gebruikelijk is, vooral bij de ontwikkeling van Node.js.

    Als we onze code een completer uiterlijk geven, voorzien van tools voor foutafhandeling en de definitie van callback-functies scheiden van de code voor het uitvoeren van verzoeken, wat de leesbaarheid van het programma zal verbeteren, krijgen we het volgende:

    Try ( request(userGet, handleUsersList) ) catch (e) ( console.error("Request boom!", e) ) function handleUsersList(error, users) ( if (error) throw error const list = JSON.parse(users) .items list.forEach(function(user) ( request(user.repos_url, handleReposList) )) ) function handleReposList(err, repos) ( if (err) throw err // Verwerk de lijst met repositories hier console.log("Mijn zeer weinig repo's", repo's) )
    Deze aanpak werkt, maar als we deze gebruiken, lopen we het risico dat we problemen tegenkomen zoals raceomstandigheden en problemen bij het omgaan met fouten. De grootste overlast die gepaard gaat met terugbellen, wat, gezien wat er gebeurt, is echter voor elke lus, hier drie, is dat dergelijke code moeilijk te lezen en te onderhouden is. Gelijkaardig probleem bestaat al sinds de komst van callback-functies en staat algemeen bekend als de callback-hel.


    Callback-hel op zijn best. Afbeelding vanaf hier genomen.

    In dit geval bedoelen we met ‘race condition’ een situatie waarin we geen controle hebben over de volgorde waarin gegevens over gebruikersrepository’s worden opgehaald. We vragen gegevens op voor alle gebruikers en het is mogelijk dat de antwoorden op deze vragen gemengd zijn. Laten we zeggen dat het antwoord voor de tiende gebruiker als eerste komt, en voor de op één na laatste. Hieronder zullen we het hebben over een mogelijke oplossing voor dit probleem.

    Beloften Met beloften kunt u de leesbaarheid van uw code verbeteren. Met als resultaat bijvoorbeeld als jouw project komt nieuwe ontwikkelaar, hij zal snel begrijpen hoe alles daar werkt.

    Om een ​​belofte te creëren, kun je de volgende constructie gebruiken:

    Const myPromise = new Promise(function(resolve, afwijzen) ( // De code komt hier terecht if (codeIsFine) (solving("fine")) else (reject("error") ) )) myPromise .then(function whenOk( response) ( console.log(response) return response )).catch(function notOk(err) ( console.error(err) ))
    Laten we naar dit voorbeeld kijken:

    • De belofte wordt geïnitialiseerd met behulp van een functie die aanroepen naar de methoden Oplossen en Afwijzen bevat;
    • Asynchrone code wordt in een functie geplaatst die is gemaakt met behulp van de Promise-constructor. Als de code succesvol wordt uitgevoerd, wordt de oplossingsmethode aangeroepen; als dit niet het geval is, wordt weigering aangeroepen;
    • Als de functieaanroepen worden opgelost, wordt de methode.then op het Promise-object uitgevoerd. Op dezelfde manier wordt de methode.catch uitgevoerd als weigeren wordt aangeroepen.
    Dit is wat u moet onthouden als u met beloften werkt:
    • De methoden solve en afwijzen gebruiken slechts één parameter. Als gevolg hiervan wordt bij het uitvoeren van een opdracht als solve("yey", "works") bijvoorbeeld alleen "yey" doorgegeven aan callback.then;
    • Als je meerdere .then-oproepen aan elkaar koppelt, moet je altijd return gebruiken aan het einde van de corresponderende callbacks, anders worden ze allemaal tegelijkertijd uitgevoerd, wat uiteraard niet is wat je wilt bereiken;
    • Als bij het uitvoeren van een afwijzingsopdracht .then de volgende in de keten is, zal deze worden uitgevoerd (je kunt .then zien als een expressie die toch wordt uitgevoerd);
    • Als in een reeks .then-aanroepen een van deze mislukt, worden de daaropvolgende oproepen overgeslagen totdat een .catch-expressie wordt gevonden;
    • Beloften hebben drie statussen: 'in behandeling' - de status van wachten op een oproep tot oplossing of afwijzing, evenals de status 'opgelost' en 'afgewezen', die overeenkomen met succesvol, met een oproep om op te lossen, en niet succesvol, met een oproep verwerpen, voltooiing van de belofte. Zodra een belofte de status ‘opgelost’ of ‘afgewezen’ heeft, kan deze niet meer worden gewijzigd.
    Houd er rekening mee dat beloften kunnen worden gemaakt zonder afzonderlijk gedefinieerde functies te gebruiken, door de functies te beschrijven op het moment dat de beloften worden gemaakt. Wat in ons voorbeeld wordt getoond, is slechts een gebruikelijke manier om beloften te initialiseren.

    Laten we, om niet in de theorie te verzanden, terugkeren naar ons voorbeeld. Laten we het herschrijven met behulp van beloften.

    Functieverzoek(url) ( return new Promise(function (oplossen, afwijzen) ( const xhr = new XMLHttpRequest(); xhr.timeout = 2000; xhr.onreadystatechange = function(e) ( if (xhr.readyState === 4) ( if (xhr.status === 200) ( solve(xhr.response) ) else ( afwijzen(xhr.status) ) ) xhr.ontimeout = functie () ( afwijzen("time-out") ) xhr.open(" get ", url, true) xhr.send(); )) )
    Met deze aanpak zal het rendement er ongeveer als volgt uitzien als u request belt.

    Dit is een belofte in een hangende staat. Het kan met succes worden opgelost of afgewezen

    Nu, gebruiken nieuwe functie request , laten we de rest van de code herschrijven.

    Const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const myPromise = request(userGet) console.log("zal in behandeling zijn wanneer ingelogd", myPromise) myPromise .then (functie handleUsersList(users) (console.log("wanneer de oplossing wordt gevonden, komt deze hier met het antwoord, in dit geval gebruikers", gebruikers) const list = JSON.parse(users).items return Promise.all(list.map (function(user) ( return request(user.repos_url) ))) )) .then(function handleReposList(repos) ( console.log("Alle gebruikersrepository's in een array", repos) )) .catch(function handleErrors( error) ( console.log("wanneer een weigering wordt uitgevoerd, zal deze hier komen en de then-instructie negeren ", error) ))
    Dit is waar we terechtkomen in de eerste .then-verklaring wanneer de belofte met succes is opgelost. We hebben een lijst met gebruikers. In de tweede .then-expressie geven we een array met repositories door. Als er iets misgaat, komen we terecht in een .catch-statement.

    Door deze aanpak te gebruiken, begrepen we de raceomstandigheden en enkele van de problemen die deze veroorzaakten. Er is hier geen terugbelhel, maar de code is nog steeds niet zo gemakkelijk te lezen. Ons voorbeeld kan zelfs verder worden verbeterd door de callback-functiedeclaraties te verwijderen:

    Const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const userRequest = request(userGet) // Als u dit deel van het programma hardop voorleest, kunt u onmiddellijk begrijp wat de code precies doet userRequest .then(handleUsersList) .then(repoRequest) .then(handleReposList) .catch(handleErrors) function handleUsersList(users) ( return JSON.parse(users).items ) function repoRequest(users) ( return Promise.all(users. map(function(user) ( return request(user.repos_url) ))) ) function handleReposList(repos) ( console.log("Alle gebruikersrepository's in een array", repos)) function handleErrors(error ) ( console.error( "Er is iets misgegaan", fout) )
    Met deze aanpak onthult één blik op de callback-namen in de .then-expressies de betekenis van de userRequest-aanroep. De code is gemakkelijk om mee te werken en gemakkelijk te lezen.

    In feite is dit slechts het topje van de ijsberg van wat beloften worden genoemd. Hier is het materiaal dat ik aanraad om te lezen voor degenen die dieper in dit onderwerp willen duiken.

    Generatoren Een andere benadering om ons probleem op te lossen, die echter zelden wordt gezien, zijn generatoren. Dit onderwerp is iets complexer dan de andere, dus als je denkt dat het te vroeg is om dit te bestuderen, kun je meteen doorgaan naar het volgende deel van dit materiaal.

    Om een ​​generatorfunctie te definiëren, kunt u het sterretje “*” achter het functietrefwoord gebruiken. Met behulp van generatoren kan asynchrone code sterk op synchrone code lijken. Het kan er bijvoorbeeld zo uitzien:

    Functie* foo() ( levert 1 const args op = levert 2 console.log(args) op) var fooIterator = foo() console.log(fooIterator.next().value) // levert 1 console.log(fooIterator.next op) ( ).value) // zal 2 fooIterator.next("aParam") uitvoeren // zal resulteren in het aanroepen van console.log in de generator en het uitvoeren van "aParam"
    Het punt hier is dat generatoren, in plaats van return , een yield-expressie gebruiken, die de uitvoering van de functie stopt tot de volgende aanroep van de .next iterator. Dit is vergelijkbaar met de .then-verklaring in beloften, die wordt uitgevoerd wanneer de belofte wordt opgelost.

    Laten we nu kijken hoe we dit allemaal op ons probleem kunnen toepassen. Dus hier is de verzoekfunctie:

    Functieverzoek(url) ( return function(callback) ( const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(e) ( if (xhr.readyState === 4) ( if (xhr.status === 200) ( callback(null, xhr.response) ) else ( callback(xhr.status, null) ) ) ) xhr.ontimeout = function () ( console.log("timeout") ) xhr.open("get", url, waar) xhr.send() ) )
    Hier gebruiken we, zoals gewoonlijk, het url-argument, maar in plaats van het verzoek onmiddellijk uit te voeren, willen we het alleen uitvoeren als we een callback-functie hebben om het antwoord te verwerken.

    De generator ziet er als volgt uit:

    Functie* lijst() ( const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const gebruikers = rendementsverzoek(userGet) rendement voor (let i = 0; ik console .log("na 2 seconden", resultaat)) asynchrone functie sumTwentyAfterTwoSeconds(waarde) ( const rest = afterTwoSeconds(20) retourneert waarde + wacht rest ) function afterTwoSeconds(waarde) ( retourneert nieuwe Promise(resolve => ( setTimeout(() => (oplossen(waarde)), 2000));
    Dit is wat er gebeurt:

    • Er is een asynchrone functie sumTwentyAfterTwoSeconds;
    • We stellen voor dat de code wacht tot de belofte van afterTwoSeconds is opgelost, wat kan eindigen met een oproep voor oplossen of weigeren;
    • De uitvoering van de code eindigt op .then, waar de await-bewerking is voltooid, in dit geval slechts één bewerking.
    Laten we de verzoekfunctie voorbereiden voor gebruik in de async/await-constructie:

    Functieverzoek(url) ( return new Promise(function(resolve, weiger) ( const xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(e) ( if (xhr.readyState === 4) ( if (xhr.status === 200) ( solve(xhr.response) ) else ( afwijzen(xhr.status) ) ) xhr.ontimeout = function () ( afwijzen("time-out") ) xhr.open("get", url, true ) xhr.send() )) )
    Nu maken we een functie met het async-trefwoord, waarin we het await-trefwoord gebruiken:

    Asynchrone functielijst() (const userGet = `https://api.github.com/search/users?page=1&q=daspinola&type=Users` const gebruikers = wacht op verzoek(userGet) const usersList = JSON.parse(gebruikers). items usersList.forEach (asynchrone functie (gebruiker) ( const repos = wacht op aanvraag (user.repos_url) handleRepoList (gebruiker, repos) )) functie handleRepoList (gebruiker, repos) ( const userRepos = JSON.parse (repos) // Verwerk hier zijn repository's voor elke gebruiker console.log(user, userRepos) )
    We hebben dus een asynchrone lijstfunctie die het verzoek zal verwerken. We hebben ook de constructie async/await in de forEach-lus nodig om een ​​lijst met opslagplaatsen te genereren. Dit alles noemen is heel eenvoudig:

    Lijst() .catch(e => console.error(e))
    Deze aanpak en het gebruik van beloftes zijn mijn favoriete asynchrone programmeertechnieken. Code die met behulp hiervan wordt geschreven, is gemakkelijk te lezen en te bewerken. U kunt meer lezen over async/await.

    Het nadeel van async/await, evenals het nadeel van generatoren, is dat dit ontwerp niet wordt ondersteund door oudere browsers, en om het te gebruiken bij serverontwikkeling moet je Node 8 gebruiken. Ook in een dergelijke situatie zal een transpiler hulp, bijvoorbeeld - babel.

    De resultaten zijn te zien in de projectcode, die het probleem aan het begin van het materiaal oplost met behulp van async/await . Als je goed wilt begrijpen waar we het over hadden, experimenteer dan met deze code en met alle besproken technologieën.

    Merk op dat onze voorbeelden kunnen worden verbeterd en beknopter kunnen worden gemaakt door ze te herschrijven met behulp van alternatieve querymethoden, zoals $.ajax en fetch . Als u ideeën heeft over hoe u de codekwaliteit kunt verbeteren met behulp van de hierboven beschreven technieken, zou ik het op prijs stellen als u mij daarover vertelt.

    Afhankelijk van de specifieke kenmerken van de taak die aan u is toegewezen, kan het blijken dat u async/await, callbacks of een combinatie daarvan gebruikt. verschillende technologieën. In feite hangt het antwoord op de vraag welke asynchrone ontwikkelingsmethodologie moet worden gekozen af ​​van de specifieke kenmerken van het project. Als een aanpak een probleem oplost in code die leesbaar, gemakkelijk te onderhouden en begrijpelijk is (en in de loop van de tijd begrijpelijk zal zijn) voor jou en andere teamleden, dan is die aanpak wat je nodig hebt.

    Beste lezers! Welke technieken gebruik je voor het schrijven van asynchrone code in JavaScript?

    Tags:

    • JavaScript
    • ontwikkeling
    • terugbellen
    • asynchroon
    • wachten
    • belofte
    • generator
    • asynchrone code
    Tags toevoegen