Un Grund pe funcțiile Async ES7

Dacă ați urmat lumea JavaScript, probabil ați auzit de promisiuni. Există câteva tutoriale online, dacă doriți să aflați despre promisiuni, dar nu le voi explica aici; acest articol presupune că aveți deja o cunoaștere activă a promisiunilor. 

Promisiunile sunt oferite drept viitorul programării asincrone în JavaScript. Promisiunile sunt într-adevăr minunate și ajută la rezolvarea multor probleme care apar cu programarea asincronă, dar această afirmație este doar oarecum corectă. În realitate, promisiunile sunt fundație a viitorului programării asincrone în JavaScript. În mod ideal, promisiunile vor fi ascunse în spatele scenei și vom putea scrie codul asincron ca și cum ar fi sincron.

În ECMAScript 7, acest lucru va deveni mai mult decât un vis fantezist: va deveni realitate și vă voi arăta realitatea - numite funcții de asincracție - chiar acum. De ce vorbim acum despre asta? La urma urmei, ES6 nu a fost complet finalizat, deci cine știe cât va mai fi înainte de a vedea ES7. Adevărul este că puteți folosi această tehnologie chiar acum, iar la sfârșitul acestui post, vă voi arăta cum.

Starea actuală a afacerilor

Înainte de a începe să demonstrez cum să folosesc funcțiile de asincracție, vreau să trec câteva exemple cu promisiuni (folosind promisiunile ES6). Mai târziu, voi converti aceste exemple pentru a utiliza funcții de asincracție, astfel încât să puteți vedea ce diferență mare face.

Exemple

Pentru primul exemplu, vom face ceva foarte simplu: apelarea unei funcții asincrone și înregistrarea valorii returnate.

funcția getValues ​​() return Promise.resolve ([1,2,3,4]);  getValues ​​(), apoi (funcția (valori) console.log (values););

Acum că am definit exemplul de bază, să sărim într-un pic mai complicat. Voi folosi și modifica exemple dintr-o postare de pe blogul meu care trece prin anumite modele pentru a folosi promisiunile în diferite scenarii. Fiecare dintre exemple extrage în mod asincron o serie de valori, efectuează o operație asincronă care transformă fiecare valoare în matrice, înregistrează fiecare valoare nouă și, în cele din urmă, returnează matricea umplută cu noile valori.

În primul rând, vom examina un exemplu care va executa mai multe operații asincrone în paralel și apoi va răspunde la ele imediat după terminarea fiecărei etape, indiferent de ordinea în care se termină. getValues funcția este aceeași din exemplul anterior. asyncOperation funcția va fi, de asemenea, refolosită în exemplele viitoare.

funcția asyncOperation (valoare) return Promise.resolve (valoare + 1);  functie (valoare) return asyncOperation (valoare) .then (function (newValue) console.log (newValue); ())))); return Promise.all (operațiuni);) captura (funcția (err) console.log ('Am avut o eroare; 

Putem face exact același lucru, dar asigurați-vă că logarea se întâmplă în ordinea elementelor din matrice. Cu alte cuvinte, următorul exemplu va face munca asincronă în paralel, dar lucrarea sincronă va fi secvențială:

(functii (newValues) newValues.forEach (function (newValue)) functionare (newValues) console.log (newValue);); retur noiValue;);)) captură (funcție (err) console.log ('Am avut o eroare; 

Exemplul nostru final va demonstra un model în care așteptăm ca o operație asincronă anterioară să se termine înainte de a începe următoarea operație. În acest exemplu nu se desfășoară nimic în paralel; totul este secvențial.

funcția foo () var newValues ​​= []; returneaza getValues ​​(), apoi (functie (valori) return values.reduce (function (previousOperation, value) returneaza previousOperation.then (function (return asyncOperation (value) (funcția () return newValues;);)) captura (funcția (err)) console.log ("Am avut un", err);); 

Chiar și cu capacitatea promisiunilor de a reduce cuiburile de inviere, nu prea ajută prea mult. Rularea unui număr necunoscut de apeluri secvențiale asincrone va fi dezordonat indiferent de ceea ce faceți. Este deosebit de îngrozitor să vezi toate cele imbricate întoarcere Cuvinte cheie. Dacă am trecut newValues array prin promisiunile din reduceapel invers în loc de a face global pentru întreaga foo , ar trebui să ajustăm codul pentru a avea și mai multe returnări imbricate, cum ar fi:

(funcția (valori) retur valorile.reduce (funcția (operația precedentă, valoarea) returnă previousOperation.then (funcția (newValues) return asyncOperation (value) .then (function (newValue ) console.log (newValue); newValues.push (newValue); return (newValues));), Promise.resolve ([])); "Am avut un", err);); 

Nu ești de acord că trebuie să rezolvăm asta? Să ne uităm la soluție.

Async Funcții pentru salvare

Chiar și cu promisiuni, programarea asincronă nu este chiar simplă și nu merge bine întotdeauna de la A la Z. Programarea sincronă este mult mai simplă și este scrisă și citită mult mai natural. Specificațiile Async Functions văd un mijloc (folosind generatoarele ES6 din spatele scenei) de a scrie codul ca și cum ar fi sincron.

Cum le folosim?

Primul lucru pe care trebuie să-l facem este să ne prefixăm funcțiile cu async cuvinte cheie. Fără acest cuvânt cheie în loc, nu putem folosi cele mai importante aștepta cuvinte cheie din interiorul acelei funcții, pe care o voi explica puțin. 

async cuvântul cheie nu ne permite doar să ne folosim aștepta, se asigură de asemenea că funcția va reveni a Promisiune obiect. În cadrul unei funcții de asincronizare, oricând întoarcere o valoare, funcția va fi de fapt returnați a Promisiune care este rezolvată cu acea valoare. Modul de respingere este de a arunca o eroare, caz în care valoarea de respingere va fi obiectul de eroare. Iată un exemplu simplu:

funcția async foo () if (Math.round (Math.random ())) returnează 'Success!'; altfel aruncați "Eșec!";  // echivalează cu ... funcția foo () if (Math.round (Math.random ())) returnați Promise.resolve ('Success!'); altul se întoarce Promise.reject ('Failure!'); 

Nici măcar nu am ajuns la partea cea mai bună și deja am făcut codul nostru mai mult ca un cod sincron, pentru că am putut să ne oprim în mod explicit în legătură cu Promisiune obiect. Putem prelua orice funcție și o putem face să revină Promisiune obiect doar prin adăugarea async cuvânt cheie în partea din față a acestuia. 

Să mergem mai departe și să ne convertim getValues și asyncOperation funcţii:

funcția async getValues ​​() return [1,2,3,4];  async function asyncOperation (valoare) return value + 1; 

Uşor! Acum, să aruncăm o privire la cea mai bună parte din toate: aștepta cuvinte cheie. În cadrul funcției de asincracție, de fiecare dată când efectuați o operațiune care întoarce o promisiune, puteți arunca aștepta cuvântul cheie din fața acestuia și va înceta să execute restul funcției până când promisiunea returnată nu a fost rezolvată sau respinsă. În acest moment, așteaptă promițătorOperație () va evalua la valoarea rezolvată sau respinsă. De exemplu:

functie promitatoareOperare () retur noi Promise (functie (rezolva, respinge) setTimeout (function () if (Math.round (Math.random ()) rezolva ('Success! ); Async function foo () var mesaj = await promițătorOperație (); console.log (mesaj);

Când sunați foo, fie va astepta pana promisingOperation rezolvă și apoi va deconecta "Succes!" mesaj, sau promisingOperation va respinge, caz în care respingerea va fi trecută și foo va respinge cu "Failure!". De cand foo nu întoarce nimic, se va rezolva cu nedefinit presupunând promisingOperation este de succes.

Există o singură întrebare: Cum rezolvăm eșecurile? Răspunsul la această întrebare este simplu: tot ce trebuie să facem este să îl înfășurăm într-un încearcă să prinzi bloc. Dacă una dintre operațiile asincrone este respinsă, putem captură și să o descurcați:

funcția async foo () try var message = await promisingOperație (); console.log (mesaj);  captură (e) console.log ("Am eșuat:", e); 

Acum, că am dat peste toate elementele de bază, să trecem prin exemplele noastre anterioare de promisiune și să le convertim pentru a folosi funcții de asincronizare.

Exemple

Primul exemplu de mai sus a fost creat getValues și a folosit-o. Am fost deja re-creat getValues așa că trebuie să redeschidem codul pentru utilizarea acestuia. Există o posibilitate de avertizare pentru a asocia funcțiile care apar aici: Codul este necesar pentru a fi într-o funcție. Exemplul anterior a fost în sfera globală (în măsura în care cineva putea spune), dar trebuie să înfășurăm codul asincron într-o funcție de asincronă pentru ca acesta să funcționeze:

funcția async () console.log (await getValues ​​());  (); // Extra "()" rulează imediat funcția

Chiar și cu înfășurarea codului într-o funcție, încă pretind că este mai ușor de citit și are mai puține octeți (dacă eliminați comentariul). Următorul exemplu, dacă vă amintiți corect, face totul în paralel. Acesta este un pic cam complicat, pentru că avem o funcție interioară care are nevoie să redea promisiunea. Dacă folosim aștepta cuvânt cheie din interiorul funcției interioare, această funcție trebuie să fie prefixată cu async.

funcția async foo () încercați var values ​​= await getValues ​​(); var newValues ​​= values.map (funcția async (valoare) var newValue = await asyncOperation (valoare); console.log (newValue); retur nouValue;); retur așteptați * newValues;  captură (err) console.log ("Am avut un", err); 

S-ar putea să fi observat asteriscul atașat ultimului aștepta cuvinte cheie. Acest lucru pare să fie în continuare pentru dezbatere un pic, dar se pare că * astept se va auto-înfășura expresia în dreptul ei în Promise.all.  În momentul de față, totuși, instrumentul pe care îl vom examina mai târziu nu acceptă * astept, așa că trebuie convertită așteaptă Promise.all (newValues); așa cum facem în exemplul următor.

Următorul exemplu va declanșa asyncOperation solicită în paralel, dar apoi le va aduce înapoi împreună și va face ieșirea succesiv.

funcția async foo () încercați var values ​​= await getValues ​​(); var newValues ​​= asteapta Promise.all (values.map (asyncOperation)); newValues.forEach (funcție (valoare) console.log (valoare);); returnează noile valori;  captură (err) console.log ("Am avut un", err); 

Iubesc aia. Acest lucru este extrem de curat. Dacă am scos-o aștepta și async cuvinte cheie, a eliminat Promise.all împachetate și făcute getValues și asyncOperation sincron, atunci acest cod va funcționa în același mod exact cu excepția faptului că ar fi sincron. Aceasta este, în esență, ceea ce urmărim să realizăm.

Exemplul nostru final va fi, desigur, ca totul să se desfășoare secvențial. Nu se efectuează nici o operație asincronă până la terminarea celei anterioare.

funcția async foo () încercați var values ​​= await getValues ​​(); retur await values.reduce (funcția async (valori, valoare) values ​​= await values; value = await asyncOperation (valoare); console.log (value); values.push (value);  captură (err) console.log ("Am avut un", err); 

Încă o dată, facem o funcție interioară async. Există un interes interesat dezvăluit în acest cod. Am trecut [] în valoare de "memo" reduce, dar apoi am folosit aștepta pe el. Valoarea din dreapta paginii aștepta nu trebuie să fie o promisiune. Poate lua orice valoare și dacă nu este o promisiune, nu va aștepta; se va executa sincron. Desigur, însă, după prima executare a apelului telefonic, vom lucra cu promisiune.

Acest exemplu este destul de mult ca primul exemplu, cu excepția faptului că îl folosim reduce in loc de Hartă ca să putem aștepta operațiunea anterioară și apoi pentru că folosim reduce pentru a construi o matrice (nu ceva ce ai face în mod normal, mai ales dacă construiești o matrice de aceeași dimensiune ca matricea originală), trebuie să construim matricea în cadrul callback-ului reduce.

Utilizarea funcțiilor Async azi

Acum că ați văzut simplitatea și minunata funcții de asincracție, ați putea fi plâns ca și când le-am văzut prima oară. Nu plângeam de bucurie (deși aproape că am făcut-o); nu, plângeam pentru că ES7 nu va mai fi aici până nu mor! Cel puțin așa sunt eu simțit. Apoi am aflat despre Traceur.

Traceur este scris și menținut de Google. Este un transpilator care convertește codul ES6 la ES5. Asta nu ajută! Ei bine, nu ar fi, cu excepția faptului că au implementat, de asemenea, suport pentru funcțiile de asincronizare. Este încă o caracteristică experimentală, ceea ce înseamnă că va trebui să îi spuneți în mod explicit compilatorului că utilizați acea facilitate și că veți dori cu exactitate să vă testați codul în întregime pentru a vă asigura că nu există probleme cu compilația.

Folosind un compilator ca Traceur înseamnă că vei avea un cod ușor umflat și urât trimis clientului, ceea ce nu este ceea ce vrei, dar dacă folosești hărți sursă, acest lucru elimină esențial majoritatea dezavantajelor legate de dezvoltare. Veți fi citit, scris și depanat codul ES6 / 7 curat, mai degrabă decât să citiți, să scrieți și să depanați o greșeală complicată de cod care trebuie să lucreze în jurul limitelor limbii. 

Bineînțeles, dimensiunea codului va fi în continuare mai mare decât dacă ați fi scris manual codul ES5 (cel mai probabil), deci este posibil să fie nevoie să găsiți un echilibru între codul de întreținut și codul performant, dar acesta este un echilibru de care aveți nevoie adesea pentru a găsi chiar și fără a utiliza un transpilator.

Folosind Traceur

Traceur este un utilitar de linie de comandă care poate fi instalat prin intermediul NPM:

npm instalează -g traceur

În general, Traceur este destul de simplu de folosit, dar unele dintre opțiuni pot fi confuze și pot necesita unele experimentări. Puteți vedea o listă a opțiunilor pentru mai multe detalii. Cel care ne interesează cu adevărat este --experimental opțiune.

Trebuie să utilizați această opțiune pentru a activa funcțiile experimentale, care este modul în care obținem funcțiile async pentru a funcționa. După ce aveți un fișier JavaScript (main.js în acest caz) cu codul ES6 și funcțiile de asincracție incluse, îl puteți compila doar cu acesta:

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

De asemenea, puteți rula codul prin omiterea acestuia --out compiled.js. Nu veți vedea prea mult dacă codul nu are console.log declarații (sau alte ieșiri din consola), dar cel puțin puteți verifica dacă există erori. Cu toate acestea, probabil că doriți să îl rulați într-un browser. În acest caz, mai sunt câțiva pași pe care trebuie să îi luați.

  1. Descărcați traceur-runtime.js script-ul. Există multe modalități de a obține, dar una dintre cele mai ușoare este de la NPM: npm instalează traceur-runtime. Fișierul va fi apoi disponibil ca index.js în dosarul modulului respectiv.
  2. În fișierul HTML, adăugați o scenariu tag pentru a trage în scriptul Traceur Runtime.
  3. Adaugă altul scenariu eticheta de sub scriptul Traceur Runtime pentru a trage compiled.js.

După aceasta, codul dvs. ar trebui să funcționeze!

Automatizarea compilației Traceur

Dincolo de utilizarea instrumentului pentru linia de comandă Traceur, puteți de asemenea să automatizați compilația, astfel încât să nu mai trebuiți să reveniți la consola dvs. și să reluați compilarea. Grunt și Gulp, care sunt runners de automatizare, fiecare are propriile pluginuri pe care le puteți utiliza pentru a automatiza compilația Traceur: grunt-traceur și respectiv gulp-traceur.

Fiecare dintre acești alergători de sarcini poate fi configurat pentru a viziona sistemul de fișiere și pentru a re-compila codul imediat ce salvați orice modificare a fișierelor JavaScript. Pentru a afla cum să utilizați Grunt sau Gulp, verificați documentația "Noțiuni de bază".

Concluzie

Funcțiile async ale ES7 oferă dezvoltatorilor o modalitate de a de fapt ieșiți din iadul cu apel invers într-un mod în care promisiunile nu puteau fi pe cont propriu. Această caracteristică nouă ne permite să scriem cod asincron într-un mod care este extrem de similar cu codul nostru sincron și, deși ES6 încă așteaptă eliberarea completă, putem folosi astăzi funcții de asincronizare prin transpilație. Ce mai astepti? Du-te și fă codul tău minunat!

Cod