Ce este o promisiune?

O promisiune JavaScript este un obiect care reprezintă finalizarea sau eșecul unei sarcini asincrone și valoarea rezultată a acesteia.¹

Sfarsit.

Glumesc desigur. Deci, ce înseamnă această definiție?

În primul rând, multe lucruri din JavaScript sunt obiecte. Puteți crea un obiect în câteva moduri diferite. Cel mai comun mod este cu sintaxa literală a obiectului:

const myCar = {
   color: 'blue',
   type: 'sedan',
   doors: '4',
};

Ați putea crea și un class și instanțiați-l cu new cuvânt cheie.

class Car {
   constructor(color, type, doors) {
      this.color = color;
      this.type = type;
      this.doors = doors
   }
}

const myCar = new Car('blue', 'sedan', '4');

console.log(myCar);

Cum se scrie o promisiune JavaScript

O promisiune este pur și simplu un obiect pe care îl creăm ca exemplul ulterior. O instanțiem cu new cuvânt cheie. În loc de cei trei parametri pe care i-am trecut pentru a ne face mașina (culoare, tip și uși), trecem într-o funcție care ia două argumente: resolve și reject.

În cele din urmă, promisiunile ne spun ceva despre finalizarea funcției asincrone din care am returnat-o – dacă a funcționat sau nu. Spunem că funcția a avut succes spunând promisiunea rezolvat, și nereușită spunând promisiunea respins.

const myPromise = new Promise(function(resolve, reject) {});

console.log(myPromise);

1611680766 528 Cum se scrie o promisiune JavaScript
Observați că promisiunea este „în așteptare”.
const myPromise = new Promise(function(resolve, reject) {
   resolve(10);
});
1611680766 718 Cum se scrie o promisiune JavaScript
Observați că am rezolvat promisiunea cu valoarea 10.

Vezi, nu prea înfricoșător – doar un obiect pe care l-am creat. Și, dacă îl extindem puțin:

1611680766 319 Cum se scrie o promisiune JavaScript
Observați că avem câteva metode la care avem acces și anume „atunci” și „captură”

În plus, putem trece orice ne-ar plăcea în rezolvare și respingere. De exemplu, am putea trece un obiect în loc de un șir:

return new Promise((resolve, reject) => {
   if(somethingSuccesfulHappened) {
      const successObject = {
         msg: 'Success',
         data,//...some data we got back
      }
      resolve(successObject); 
   } else {
      const errorObject = {
         msg: 'An error occured',
         error, //...some error we got back
      }
      reject(errorObject);
   }
});

Sau, așa cum am văzut mai devreme, nu trebuie să trecem nimic:

return new Promise((resolve, reject) => {
   if(somethingSuccesfulHappend) {
      resolve()
   } else {
      reject();
   }
});

Dar partea „asincronă” a definiției?

JavaScript are un singur thread. Aceasta înseamnă că poate rula un singur lucru la un moment dat. Dacă vă puteți imagina un drum, vă puteți gândi la JavaScript ca la o autostradă cu o singură bandă. Anumite coduri (cod asincron) pot aluneca pe umăr pentru a permite altor coduri să-l treacă. Când se face acel cod asincron, acesta revine la carosabil.

Ca o notă laterală, putem returna o promisiune de la orice funcţie. Nu trebuie să fie asincron. Acestea fiind spuse, promisiunile sunt în mod normal returnate în cazurile în care funcția din care revin este asincronă. De exemplu, un API care are metode de salvare a datelor pe un server ar fi un candidat excelent pentru a returna o promisiune!

De luat masa:

Promisiunile ne oferă o modalitate de a aștepta finalizarea codului nostru asincron, captarea unor valori din acesta și transmiterea acestor valori către alte părți ale programului nostru.

Am un articol aici care se aruncă mai adânc în aceste concepte: Aruncat pentru o buclă: Înțelegerea buclelor și a expirărilor în JavaScript.

Cum folosim o promisiune?

Folosirea unei promisiuni se mai numește consumatoare o promisiune. În exemplul nostru de mai sus, funcția noastră returnează un obiect promis. Acest lucru ne permite să folosim înlănțuirea metodelor cu funcția noastră.

Iată un exemplu de înlănțuire a metodelor, pariez că ați văzut:

const a="Some awesome string";
const b = a.toUpperCase().replace('ST', '').toLowerCase();

console.log(b); // some awesome ring

Acum, amintește-ți (pretinde) promisiunea noastră:

const somethingWasSuccesful = true;

function someAsynFunction() {
   return new Promise((resolve, reject){
      if (somethingWasSuccesful) {
         resolve();     
      } else {
         reject()
      }
   });
}

Și, consumându-ne promisiunea folosind înlănțuirea metodelor:

someAsyncFunction
   .then(runAFunctionIfItResolved(withTheResolvedValue))
   .catch(orARunAfunctionIfItRejected(withTheRejectedValue));

Un exemplu (mai mult) real.

Imaginați-vă că aveți o funcție care îi atrage pe utilizatori dintr-o bază de date. Am scris un exemplu de funcție pe Codepen care simulează un API pe care l-ați putea folosi. Oferă două opțiuni pentru accesarea rezultatelor. În primul rând, puteți oferi o funcție de apel invers, de unde puteți accesa utilizatorul sau orice eroare. Sau două, funcția returnează o promisiune ca modalitate de a accesa utilizatorul sau eroarea.

În mod tradițional, am accesa rezultatele codului asincron prin utilizarea apelurilor de apel.

rr someDatabaseThing(maybeAnID, function(err, result)) {
   //...Once we get back the thing from the database...
   if(err) {
      doSomethingWithTheError(error)
   }   else {
      doSomethingWithResults(results);
   }
}

Utilizarea callback-urilor este O.K până când devin excesiv de imbricate. Cu alte cuvinte, trebuie să rulați cod mai asincron cu fiecare rezultat nou. Acest model de apeluri de apel în cadrul apelurilor de apel poate duce la ceva cunoscut sub numele de „iad de apel de apel”.

1611680766 366 Cum se scrie o promisiune JavaScript
Începuturile iadului de apel invers

Promisiunile ne oferă un mod mai elegant și mai ușor de citit de a vedea fluxul programului nostru.

doSomething()
   .then(doSomethingElse) // and if you wouldn't mind
   .catch(anyErrorsPlease);

Scriind propria noastră promisiune: Goldilocks, cei Trei Urși și un Supercomputer

Imaginați-vă că ați găsit un castron cu supă. Ați dori să știți temperatura supei respective înainte de a o consuma. Nu mai ai termometre, dar din fericire ai acces la un supercomputer care îți spune temperatura bolului cu supă. Din păcate, acest supercomputer poate dura până la 10 secunde pentru a obține rezultatele.

1611680766 377 Cum se scrie o promisiune JavaScript

Iată câteva lucruri de observat.

  1. Inițiem o variabilă globală numită result.
  2. Simulăm durata întârzierii rețelei cu Math.random() și setTimeout().
  3. Simulăm o temperatură cu Math.random().
  4. Menținem valorile de întârziere și temperatură limitate într-un interval adăugând câteva „matematici” suplimentare. Gama pentru temp este de la 1 la 300; gama pentru delay este de la 1000 ms la 10000 ms (de la 1 la 10 secunde).
  5. Înregistrăm întârzierea și temperatura, astfel încât să avem o idee despre cât va dura această funcție și rezultatele pe care le așteptăm să vedem când se termină.

Rulați funcția și înregistrați rezultatele.

getTemperature(); 
console.log(results); // undefined

Temperatura este nedefinită. Ce s-a întâmplat?

Funcția va dura o anumită perioadă de timp pentru a rula. Variabila nu este setată până nu se termină întârzierea. Deci, în timp ce rulăm funcția, setTimeout este asincron. Partea codului din setTimeout se mută din firul principal într-o zonă de așteptare.

Am un articol aici care se aruncă mai adânc în acest proces: Aruncat pentru o buclă: Înțelegerea buclelor și a expirărilor în JavaScript.

Întrucât partea funcției noastre care stabilește variabila result se mută într-o zonă de deținere până când aceasta este terminată, analizorul nostru este liber să se deplaseze pe linia următoare. În cazul nostru, este al nostru console.log(). In acest punct, result este încă nedefinit de când setTimeout nu s-a terminat.

Deci, ce altceva am putea încerca? Am putea fugi getTemperature() și apoi așteptați 11 secunde (deoarece întârzierea noastră maximă este de zece secunde) și atunci consola.log rezultatele.

getTemperature();
   setTimeout(() => {
      console.log(result); 
   }, 11000);
   
// Too Hot | Delay: 3323 | Temperature: 209 deg

Acest lucru funcționează, dar problema cu această tehnică este, deși în exemplul nostru cunoaștem întârzierea maximă a rețelei, într-un exemplu din viața reală poate dura uneori mai mult de zece secunde. Și, chiar dacă am putea garanta o întârziere maximă de zece secunde, dacă rezultatul este gata mai devreme, pierdem timpul.

Promisiuni de salvare

O să ne refactorizăm getTemperature() funcția de a returna o promisiune. Și în loc să stabilim rezultatul, vom respinge promisiunea, cu excepția cazului în care rezultatul este „Just Right”, caz în care vom rezolva promisiunea. În ambele cazuri, vom transmite unele valori atât pentru rezolvare, cât și pentru respingere.

1611680766 310 Cum se scrie o promisiune JavaScript

Acum putem folosi rezultatele promisiunii noastre pe care le întoarcem (știu și ca consumatoare promisiunea).

getTemperature()
   .then(result => console.log(result))
   .catch(error => console.log(error));
   
// Reject: Too Cold | Delay: 7880 | Temperature: 43 deg

.then va fi sunat atunci când promisiunea noastră se rezolvă și va returna orice informație vom transmite resolve.

.catch va fi sunat atunci când promisiunea noastră respinge și va returna orice informație vom transmite reject.

Cel mai probabil, vei consuma promisiuni mai mult decât le vei crea. În ambele cazuri, ele ne ajută să facem codul nostru mai elegant, mai ușor de citit și mai eficient.

rezumat

  1. Promisiunile sunt obiecte care conțin informații despre finalizarea unui cod asincron și a valorilor rezultate pe care dorim să le transmitem.
  2. Pentru a ne returna o promisiune pe care o folosim return new Promise((resolve, reject)=> {})
  3. Pentru a consuma o promisiune pe care o folosim .then pentru a obține informațiile dintr-o promisiune care a fost rezolvată și .catch pentru a obține informațiile dintr-o promisiune respinsă.
  4. Probabil că vei folosi (consuma) promisiunile mai mult decât vei scrie.

Referințe

1.) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise