Een primer op ES7 Async-functies

Als je de wereld van JavaScript hebt gevolgd, heb je waarschijnlijk wel van beloftes gehoord. Er zijn een aantal geweldige tutorials online als je meer wilt weten over beloften, maar ik zal ze hier niet uitleggen; dit artikel gaat ervan uit dat je al een praktische kennis hebt van beloften. 

Beloften worden aangeprezen als de toekomst van asynchrone programmering in JavaScript. Beloften zijn echt geweldig en helpen bij het oplossen van veel problemen die zich voordoen bij asynchrone programmering, maar die bewering is slechts enigszins correct. In werkelijkheid zijn beloftes de fundament van de toekomst van asynchrone programmering in JavaScript. Idealiter worden beloften achter de schermen weggestopt en kunnen we onze asynchrone code schrijven alsof het synchroon is.

In ECMAScript 7 wordt dit meer dan een fantasievolle droom: het zal realiteit worden en ik zal je nu die realiteit laten zien - async-functies genoemd. Waarom praten we hier nu over? Immers, ES6 is nog niet eens volledig gefinaliseerd, dus wie weet hoe lang het zal duren voordat we ES7 zien. De waarheid is dat je deze technologie nu kunt gebruiken, en aan het einde van dit bericht zal ik je laten zien hoe.

De huidige stand van zaken

Voordat ik begin met het demonstreren van het gebruik van asynchroonfuncties, wil ik enkele voorbeelden met beloften doornemen (met ES6-beloften). Later zal ik deze voorbeelden converteren om async-functies te gebruiken, zodat u kunt zien wat een groot verschil het maakt.

Voorbeelden

Voor ons eerste voorbeeld doen we iets heel eenvoudigs: een asynchrone functie aanroepen en de waarde registreren die het retourneert.

function getValues ​​() return Promise.resolve ([1,2,3,4]);  getValues ​​(). then (function (values) console.log (values););

Nu we dat basisvoorbeeld hebben gedefinieerd, laten we iets ingewikkelder ingaan. Ik gebruik en wijzig voorbeelden van een bericht op mijn eigen blog dat enkele patronen doorloopt om beloften in verschillende scenario's te gebruiken. Elk van de voorbeelden haalt asynchroon een array met waarden op, voert een asynchrone bewerking uit die elke waarde in de array transformeert, elke nieuwe waarde logt en als resultaat de array retourneert die is gevuld met de nieuwe waarden.

Eerst zullen we een voorbeeld bekijken waarin meerdere asynchrone bewerkingen parallel worden uitgevoerd en vervolgens onmiddellijk daarop reageren zodra ze zijn voltooid, ongeacht de volgorde waarin ze zijn voltooid. De GetValues functie is dezelfde als in het vorige voorbeeld. De asyncOperation functie zal ook worden hergebruikt in de komende voorbeelden.

function asyncOperation (value) return Promise.resolve (waarde + 1);  functie foo () return getValues ​​(). then (function (values) var operations = values.map (function (value) return asyncOperation (value) .then (function (newValue) console.log (newValue); return newValue;);); return Promise.all (operaties);). catch (function (err) console.log ('We hadden een', err);); 

We kunnen precies hetzelfde doen, maar zorg ervoor dat het loggen gebeurt in de volgorde van de elementen in de array. Met andere woorden, in het volgende voorbeeld wordt het asynchrone werk parallel uitgevoerd, maar het synchrone werk zal opeenvolgend zijn:

function foo () return getValues ​​(). then (functie (waarden) var operations = values.map (asyncOperation); return Promise.all (operaties) .then (function (newValues) newValues.forEach (function (newValue) console.log (newValue);); return newValues;);). catch (function (err) console.log ('We hadden een', err);); 

Ons laatste voorbeeld toont een patroon waarbij we wachten tot een eerdere asynchrone bewerking is voltooid voordat de volgende asynchrone bewerking wordt gestart. Er is niets parallel aan dit voorbeeld; alles is sequentieel.

function foo () var newValues ​​= []; return getValues ​​(). then (function (values) return values.reduce (function (previousOperation, value) return previousOperation.then (function () return asyncOperation (value);) then (function (newValue) console .log (newValue); newValues.push (newValue););, Promise.resolve ()). then (function () return newValues;);). catch (function (err) console.log ('We hadden een', err);); 

Zelfs met het vermogen van beloften om callback-nesten te verminderen, helpt het niet echt veel. Het uitvoeren van een onbekend aantal opeenvolgende asynchrone oproepen zal rommelig zijn, ongeacht wat u doet. Het is vooral afschuwelijk om al die genest te zien terugkeer zoekwoorden. Als we de newvalues rij door de beloften in de verminderenterugbellen in plaats van het globaal te maken voor het geheel foo functie, moeten we de code aanpassen om nog meer geneste returns te krijgen, zoals deze:

function foo () return getValues ​​(). then (function (values) return values.reduce (functionOveration, value) return previousOperation.then (function (newValues) return asyncOperation (value) .then (function (newValue ) console.log (newValue); newValues.push (newValue); return newValues;););, Promise.resolve ([]));) catch (function (err) console.log ( 'We hadden een', err);); 

Ben je het er niet mee eens dat we dit moeten oplossen? Laten we naar de oplossing kijken.

Async-functies voor de redding

Zelfs met beloftes is asynchroon programmeren niet bepaald eenvoudig en verloopt niet altijd mooi van A naar Z. Synchroon programmeren is zoveel eenvoudiger en wordt veel natuurlijker geschreven en gelezen. De specificatie Async Functies kijkt naar een middel (met behulp van ES6-generatoren achter de schermen) van het schrijven van uw code alsof deze synchroon is.

Hoe gebruiken we ze??

Het eerste dat we moeten doen, is onze functies een prefix geven met de async trefwoord. Zonder dit sleutelwoord kunnen we het uiterst belangrijke niet gebruiken wachten sleutelwoord binnen die functie, die ik in een beetje zal uitleggen. 

De async zoekwoord stelt ons niet alleen in staat om te gebruiken wachten, het zorgt er ook voor dat de functie a retourneert Belofte voorwerp. Binnen een async-functie, altijd terugkeer een waarde, de functie zal werkelijk terugkeer a Belofte dat is opgelost met die waarde. De manier om te weigeren is om een ​​fout te genereren, in welk geval de afwijzingswaarde het foutobject is. Hier is een eenvoudig voorbeeld:

async-functie foo () if (Math.round (Math.random ())) retourneert 'Success!'; anders gooien 'falen!';  // Is gelijk aan ... functie foo () if (Math.round (Math.random ())) return Promise.resolve ('Success!'); else return Promise.reject ('Failure!'); 

We zijn nog niet eens tot het beste einde gekomen en we hebben onze code al eerder als synchrone code gemaakt omdat we hebben kunnen stoppen met expliciet te rommelen met de Belofte voorwerp. We kunnen elke functie aannemen en deze laten terugkeren a Belofte object alleen door het toevoegen van de async sleutelwoord naar de voorkant ervan. 

Laten we doorgaan en onze omzetten GetValues en asyncOperation functies:

async-functie getValues ​​() return [1,2,3,4];  async-functie asyncOperation (waarde) retourwaarde + 1; 

Gemakkelijk! Laten we nu eens kijken naar het beste deel van alles: het wachten trefwoord. Binnen uw async-functie kunt u elke keer dat u een bewerking uitvoert die een belofte retourneert, de wachten zoekwoord ervoor en het stopt met het uitvoeren van de rest van de functie totdat de geretourneerde belofte is opgelost of afgewezen. Op dat moment, de wachten op veelbelovendeoperatie () zal evalueren naar de opgeloste of afgewezen waarde. Bijvoorbeeld:

function promisingOperation () return new Promise (functie (resolve, reject) setTimeout (function () if (Math.round (Math.random ())) resolve ('Success!'); else reject ('Failure!' );, 1000); async function foo () var message = awitten promisingOperation (); console.log (bericht);

Wanneer je belt foo, het zal ofwel wachten tot promisingOperation lost het op en dan logt het uit het "Succes!" bericht, of promisingOperation zal afwijzen, in welk geval de afwijzing doorgevoerd wordt en foo zal afwijzen met "Falen!". Sinds foo retourneert niets, het lost op onbepaald ervan uitgaande dat promisingOperation is succesvol.

Er is nog maar één vraag: hoe lossen we storingen op? Het antwoord op die vraag is eenvoudig: we hoeven het alleen in a te plaatsen proberen te vangen blok. Als een van de asynchrone bewerkingen wordt geweigerd, kunnen we dit doen vangst dat en behandel het:

async-functie foo () try var message = wachten op veelbelovendeoperatie (); console.log (bericht);  catch (e) console.log ('We faalden:', e); 

Nu we alle basisprincipes hebben aangeraakt, gaan we eerst onze vorige belofte-voorbeelden bekijken en converteren ze om asynchroonfuncties te gebruiken.

Voorbeelden

Het eerste voorbeeld hierboven gemaakt GetValues en gebruikte het. We zijn al opnieuw gemaakt GetValues dus we moeten gewoon de code opnieuw maken om deze te gebruiken. Er is een mogelijke waarschuwing voor async-functies die hier wordt weergegeven: de code is verplicht om in een functie te zijn. Het vorige voorbeeld was wereldwijd (voor zover bekend), maar we moeten onze async-code in een async-functie plaatsen om deze te laten werken:

async-functie () console.log (wacht op getValues ​​());  (); // De extra "()" voert de functie onmiddellijk uit

Zelfs met het omwikkelen van de code in een functie, beweer ik nog steeds dat het makkelijker te lezen is en heeft het minder bytes (als je de reactie verwijdert). Ons volgende voorbeeld, als je het goed onthoudt, doet alles parallel. Deze is een beetje lastig, omdat we een innerlijke functie hebben die een belofte moet teruggeven. Als we de wachten sleutelwoord binnen de interne functie, die functie moet ook worden voorafgegaan door async.

async-functie foo () try var values ​​= awVan getValues ​​(); var newValues ​​= values.map (async-functie (waarde) var newValue = wachten op asyncOperation (waarde); console.log (newValue); return newValue;); return wachten * nieuwe waarden;  catch (err) console.log ('We hadden een', err); 

Je hebt misschien de asterisk van de vorige gemerkt wachten trefwoord. Dit lijkt nog een beetje ter discussie, maar het lijkt erop wachten* zal de expressie in feite automatisch omsluiten naar zijn recht erin Promise.all.  Maar op dit moment ondersteunt de tool waar we later naar zullen kijken niet wachten*, dus het moet worden geconverteerd naar wacht op Promise.all (nieuwe waarden); zoals we in het volgende voorbeeld doen.

Het volgende voorbeeld zal de asyncOperation roept parallel, maar brengt het allemaal weer samen en voert de uitvoer sequentieel uit.

async-functie foo () try var values ​​= awVan getValues ​​(); var newValues ​​= wacht op Promise.all (values.map (asyncOperation)); newValues.forEach (functie (waarde) console.log (waarde);); return newValues;  catch (err) console.log ('We hadden een', err); 

Ik hou daarvan. Dat is extreem schoon. Als we de wachten en async sleutelwoorden, verwijderde de Promise.all wrapper en gemaakt GetValues en asyncOperation synchroon, dan zou deze code nog steeds exact hetzelfde werken, behalve dat deze synchroon zou zijn. Dat is in wezen wat we willen bereiken.

Ons laatste voorbeeld zal natuurlijk alles sequentieel hebben. Er worden geen asynchrone bewerkingen uitgevoerd totdat de vorige voltooid is.

async-functie foo () try var values ​​= awVan getValues ​​(); return wachten op values.reduce (async-functie (waarden, waarde) values ​​= await values; value = await asyncOperation (value); console.log (value); values.push (value); return values;, []);  catch (err) console.log ('We hadden een', err); 

Nogmaals, we maken een innerlijke functie async. Er is een interessante gril onthuld in deze code. Ik passeerde [] in als de "memo" waarde voor verminderen, maar toen gebruikte ik wachten ben ermee bezig. De waarde rechts van wachten hoeft geen belofte te zijn. Het kan elke waarde aannemen, en als het geen belofte is, zal hij er niet op wachten; het zal gewoon synchroon lopen. Natuurlijk werken we na de eerste uitvoering van de callback zelfs met een belofte.

Dit voorbeeld is vrijwel hetzelfde als het eerste voorbeeld, behalve dat we gebruiken verminderen in plaats van kaart zodat we kunnen wachten de vorige operatie, en dan omdat we gebruiken verminderen om een ​​array te bouwen (niet iets dat je normaal zou doen, vooral als je een array van dezelfde grootte als de originele array bouwt), moeten we de array bouwen binnen de callback naar verminderen.

Async-functies vandaag gebruiken

Nu dat je een glimp hebt opgevangen van de eenvoud en awesomeness van async-functies, zou je kunnen huilen zoals ik deed toen ik ze voor de eerste keer zag. Ik huilde niet uit blijdschap (hoewel ik dat bijna deed); Nee, ik huilde omdat ES7 hier niet zal zijn voordat ik sterf! Zo ben ik tenminste voelde. Toen kwam ik achter op Traceur.

Traceur is geschreven en wordt beheerd door Google. Het is een transponder die ES6-code converteert naar ES5. Dat helpt niet! Nou, dat zou niet, behalve dat ze ook ondersteuning voor asynchroonfuncties hebben geïmplementeerd. Het is nog steeds een experimentele functie, wat betekent dat je de compiler expliciet moet vertellen dat je die functie gebruikt, en dat je zeker je code grondig wilt testen om er zeker van te zijn dat er geen problemen zijn met de compilatie.

Als je een compiler zoals Traceur gebruikt, betekent dit dat je wat licht opgeblazen, lelijke code naar de client stuurt, wat niet is wat je wilt, maar als je bronkaarten gebruikt, elimineert dit in wezen de nadelen van ontwikkeling. U zult schone ES6 / 7-code lezen, schrijven en debuggen, in plaats van een ingewikkelde puinhoop met code te moeten lezen, schrijven en debuggen die de beperkingen van de taal moet omzeilen. 

Natuurlijk is de code nog steeds groter dan wanneer je de ES5-code (waarschijnlijk) met de hand hebt geschreven, dus je moet misschien een soort balans vinden tussen onderhoudbare code en uitvoeringscode, maar dat is een balans die je vaak nodig hebt om zelfs te vinden zonder een transponder te gebruiken.

Traceur gebruiken

Traceur is een opdrachtregelprogramma dat via NPM kan worden geïnstalleerd:

npm install -g traceur

Over het algemeen is Traceur vrij eenvoudig te gebruiken, maar sommige opties kunnen verwarrend zijn en vereisen mogelijk wat experimenten. U kunt een lijst met opties bekijken voor meer details. Degene waar we echt in geïnteresseerd zijn, is het --experimenteel keuze.

U moet deze optie gebruiken om de experimentele functies in te schakelen. Dit is de manier waarop we de asynchroonfuncties gebruiken. Zodra u een JavaScript-bestand hebt (main.js in dit geval) met de meegeleverde ES6-code en async-functies, kunt u het hier gewoon mee compileren:

traceur main.js --experimental --out compiled.js

U kunt de code ook gewoon uitvoeren door de --uit compiled.js. Je zult niet veel zien tenzij de code heeft console.log (of andere console-uitgangen), maar u kunt op zijn minst controleren op fouten. U zult het waarschijnlijk in een browser willen gebruiken. Als dat het geval is, zijn er nog een paar stappen die u moet nemen.

  1. Download de traceur-runtime.js script. Er zijn veel manieren om het te krijgen, maar een van de gemakkelijkste is van NPM: npm traceur-runtime installeren. Het bestand is dan beschikbaar als index.js in de map van die module.
  2. Voeg in uw HTML-bestand een toe script tag om het Traceur Runtime-script in te voeren.
  3. Nog een toevoegen script tag onder het Traceur Runtime-script om in te trekken compiled.js.

Hierna moet uw code actief zijn!

Automatisering van Traceur-compilatie

Naast het gebruik van het opdrachtregelprogramma Traceur, kunt u de compilatie ook automatiseren, zodat u niet steeds naar uw console hoeft terug te keren en de compiler niet opnieuw hoeft uit te voeren. Grunt en Gulp, die geautomatiseerde taaklopers zijn, hebben elk hun eigen plug-ins die u kunt gebruiken om de Traceur-compilatie te automatiseren: grunt-traceur en gulp-traceur.

Elk van deze taaklopers kan worden ingesteld om uw bestandssysteem te bekijken en de code opnieuw te compileren op het moment dat u wijzigingen in uw JavaScript-bestanden opslaat. Raadpleeg de documentatie "Aan de slag" om te leren hoe u Grom of Gulp gebruikt.

Conclusie

ES7's async-functies bieden ontwikkelaars een manier om werkelijk uitstappen van callback hell op een manier die belooft nooit kon op hun eigen. Met deze nieuwe functie kunnen we asynchrone code schrijven op een manier die sterk lijkt op onze synchrone code, en ook al wacht ES6 nog steeds op de volledige release, we kunnen vandaag al async-functies gebruiken door middel van transpilatie. Waar wacht je op? Ga uit en maak je code geweldig!