Pe web, multe lucruri tind să consume mult timp – dacă interogați un API, poate dura ceva timp pentru a primi un răspuns. Prin urmare, programarea asincronă este o abilitate esențială pentru dezvoltatori.

Când lucrăm cu operații asincrone în JavaScript, auzim adesea termenul Promise. Dar poate fi dificil să înțelegem cum funcționează și cum să le folosim.

Spre deosebire de multe tutoriale tradiționale de codificare, în acest tutorial vom învăța făcând. Vom finaliza patru sarcini până la sfârșitul articolului:

  • Sarcina 1: elementele de bază ale promisiunilor explicate folosind ziua mea de naștere
  • Sarcina 2: Construiește un joc de ghicire
  • Sarcina 3: Obțineți informații despre țară dintr-un API
  • Sarcina 4: Aduceți țările vecine ale unei țări

Dacă doriți să continuați, asigurați-vă că descărcați resursele aici: https://bit.ly/3m4bjWI

Sarcina 1: Bazele promisiunilor explicate folosind ziua mea de naștere

Text alternativ

Prietenul meu Kayo promite că îmi va face un tort de ziua mea peste două săptămâni.

Dacă totul merge bine și Kayo nu se îmbolnăvește, vom avea un anumit număr de prăjituri. (Torturile sunt de numărat în acest tutorial 😆). În caz contrar, dacă Kayo se îmbolnăvește, nu vom avea prăjituri.

Oricum ar fi, vom face încă o petrecere.

Pentru această primă sarcină, vom traduce această poveste în cod. În primul rând, să creăm o funcție care returnează un Promise:

const onMyBirthday = (isKayoSick) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (!isKayoSick) {
        resolve(2);
      } else {
        reject(new Error("I am sad"));
      }
    }, 2000);
  });
};

În JavaScript, putem crea un nou Promise cu new Promise(), care ia o funcție ca argument: (resolve, reject) => {}.

În această funcție, resolve și reject sunt funcții de apel invers care sunt furnizate în mod implicit în JavaScript.

Să aruncăm o privire mai atentă la codul de mai sus.

Când rulăm onMyBirthday funcție, după 2000ms:

  • Dacă Kayo nu este bolnav, atunci fugim resolve cu 2 ca argument
  • Dacă Kayo este bolnav, atunci alergăm reject cu new Error("I am sad") ca argument. Chiar dacă poți transmite orice reject ca argument, se recomandă trecerea acestuia Error obiect.

Acum, pentru că onMyBirthday() returnează un Promise, avem acces la then, catch, și finally metode.

Și avem, de asemenea, acces la argumentele care au fost transmise resolve și reject mai devreme în interior then și catch.

Să aruncăm o privire mai atentă asupra codului.

Dacă Kayo nu este bolnav:

onMyBirthday(false)
  .then((result) => {
    console.log(`I have ${result} cakes`); // In the console: I have 2 cakes  
  })
  .catch((error) => {
    console.log(error); // Does not run
  })
  .finally(() => {
    console.log("Party"); // Shows in the console no matter what: Party
  });

Dacă Kayo este bolnav:

onMyBirthday(true)
  .then((result) => {
    console.log(`I have ${result} cakes`); // does not run 
  })
  .catch((error) => {
    console.log(error); // in console: Error: I am sad
  })
  .finally(() => {
    console.log("Party"); // Shows in the console no matter what: Party
  });

Bine, așa că, până acum, sper că veți avea ideea de bază Promise. Să trecem la sarcina 2.

Sarcina 2: Construiește un joc de ghicire

Cerintele:

  • Povestea utilizatorului: un utilizator poate introduce un număr
  • Povestea utilizatorului: sistemul alege un număr aleatoriu de la 1 la 6
  • Povestea utilizatorului: dacă numărul utilizatorului este egal cu un număr aleatoriu, acordați utilizatorului 2 puncte
  • Povestea utilizatorului: dacă numărul utilizatorului este diferit de numărul aleator cu 1,
    acordă utilizatorului 1 punct. În caz contrar, acordați utilizatorului 0 puncte
  • Povestea utilizatorului: utilizatorul poate juca jocul atât timp cât dorește

Pentru primele 4 povești ale utilizatorilor, să creăm un enterNumber funcția și returnarea a Promise:

const enterNumber = () => {
  return new Promise((resolve, reject) => {
    // Let's start from here
  });
};

Primul lucru pe care trebuie să-l facem este să cerem un număr de la utilizator și să alegem un număr aleatoriu între 1 și 6:

const enterNumber = () => {
  return new Promise((resolve, reject) => {
    const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // Ask the user to enter a number
    const randomNumber = Math.floor(Math.random() * 6 + 1); // Pick a random number between 1 and 6
  });
};

Acum, userNumber poate introduce o valoare, care nu este un număr. Dacă da, să apelăm la reject funcție cu o eroare:

const enterNumber = () => {
  return new Promise((resolve, reject) => {
    const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // Ask user to enter a number
    const randomNumber = Math.floor(Math.random() * 6 + 1); // Pick a random number between 1 and 6

    if (isNaN(userNumber)) {
      reject(new Error("Wrong Input Type")); // If the user enters a value that is not a number, run reject with an error
    }
  });
};

Următorul lucru pe care vrem să îl facem este să verificăm dacă userNumber este egal cu randomNumber, dacă da, vrem să acordăm utilizatorului 2 puncte și putem rula resolve funcție de trecere a unui obiect { points: 2, randomNumber }. Observați aici că și noi dorim să știm randomNumber când Promisiunea este rezolvată

Dacă userNumber este diferit de randomNumber pe rând, apoi acordăm utilizatorului 1 punct. În caz contrar, acordăm utilizatorului 0 puncte:

return new Promise((resolve, reject) => {
  const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // Ask the user to enter a number
  const randomNumber = Math.floor(Math.random() * 6 + 1); // Pick a random number between 1 and 6

  if (isNaN(userNumber)) {
    reject(new Error("Wrong Input Type")); // If the user enters a value that is not a number, run reject with an error
  }

  if (userNumber === randomNumber) {
    // If the user's number matches the random number, return 2 points
    resolve({
      points: 2,
      randomNumber,
    });
  } else if (
    userNumber === randomNumber - 1 ||
    userNumber === randomNumber + 1
  ) {
    // If the user's number is different than the random number by 1, return 1 point
    resolve({
      points: 1,
      randomNumber,
    });
  } else {
    // Else return 0 points
    resolve({
      points: 0,
      randomNumber,
    });
  }
});

Bine, să creăm și o altă funcție pentru a întreba dacă utilizatorul dorește să continue jocul:

const continueGame = () => {
  return new Promise((resolve) => {
    if (window.confirm("Do you want to continue?")) { // Ask if the user want to continue the game with a confirm modal
      resolve(true);
    } else {
      resolve(false);
    }
  });
};

Observați aici că creăm un Promise, dar nu folosește reject sună din nou. Este bine.

Acum să creăm o funcție pentru a face față presupunerii:

const handleGuess = () => {
  enterNumber() // This returns a Promise
    .then((result) => {
      alert(`Dice: ${result.randomNumber}: you got ${result.points} points`); // When resolve is run, we get the points and the random number 
      
      // Let's ask the user if they want to continue the game
      continueGame().then((result) => {
        if (result) {
          handleGuess(); // If yes, we run handleGuess again
        } else {
          alert("Game ends"); // If no, we show an alert
        }
      });
    })
    .catch((error) => alert(error));
};

handleGuess(); // Run handleGuess function

Aici când sunăm handleGuess, enterNumber() acum returnează un Promise:

  • Dacă Promise este rezolvat, numim then și afișați un mesaj de alertă. De asemenea, întrebăm dacă utilizatorul dorește să continue.
  • Dacă Promise este respins, afișăm un mesaj de alertă cu eroarea.

După cum puteți vedea, codul este destul de dificil de citit.

Să refactorizăm handleGuess funcționează puțin folosind async/await sintaxă:

const handleGuess = async () => {
  try {
    const result = await enterNumber(); // Instead of the then method, we can get the result directly by just putting await before the promise

    alert(`Dice: ${result.randomNumber}: you got ${result.points} points`);

    const isContinuing = await continueGame();

    if (isContinuing) {
      handleGuess();
    } else {
      alert("Game ends");
    }
  } catch (error) { // Instead of catch method, we can use the try, catch syntax
    alert(error);
  }
};

Puteți vedea că am creat un async funcționează prin punere async înaintea parantezelor. Apoi în async funcţie:

  • In loc de then metoda, putem obține rezultatele direct doar prin punerea await înainte de promisiune
  • In loc de catch metoda, putem folosi try, catch sintaxă

Iată tot codul pentru această sarcină din nou pentru referință:

const enterNumber = () => {
  return new Promise((resolve, reject) => {
    const userNumber = Number(window.prompt("Enter a number (1 - 6):")); // Ask the user to enter a number
    const randomNumber = Math.floor(Math.random() * 6 + 1); // Pick a random number between 1 and 6

    if (isNaN(userNumber)) {
      reject(new Error("Wrong Input Type")); // If the user enters a value that is not a number, run reject with an error
    }

    if (userNumber === randomNumber) { // If the user's number matches the random number, return 2 points
      resolve({
        points: 2,
        randomNumber,
      });
    } else if (
      userNumber === randomNumber - 1 ||
      userNumber === randomNumber + 1
    ) { // If the user's number is different than the random number by 1, return 1 point
      resolve({
        points: 1,
        randomNumber,
      });
    } else { // Else return 0 points
      resolve({
        points: 0,
        randomNumber,
      });
    }
  });
};

const continueGame = () => {
  return new Promise((resolve) => {
    if (window.confirm("Do you want to continue?")) { // Ask if the user want to continue the game with a confirm modal
      resolve(true);
    } else {
      resolve(false);
    }
  });
};

const handleGuess = async () => {
  try {
    const result = await enterNumber(); // Instead of the then method, we can get the result directly by just putting await before the promise

    alert(`Dice: ${result.randomNumber}: you got ${result.points} points`);

    const isContinuing = await continueGame();

    if (isContinuing) {
      handleGuess();
    } else {
      alert("Game ends");
    }
  } catch (error) { // Instead of catch method, we can use the try, catch syntax
    alert(error);
  }
};

handleGuess(); // Run handleGuess function

Bine, am terminat cu a doua sarcină. Să trecem la al treilea.

Sarcina 3: Obțineți informații despre țară din un API

Vei vedea Promises folosit foarte mult la preluarea datelor dintr-un API.

Dacă deschizi https://restcountries.eu/rest/v2/alpha/col într-un browser nou, veți vedea datele țării în format JSON.

Prin utilizarea Fetch API, putem prelua datele prin:

const fetchData = async () => {
  const res = await fetch("https://restcountries.eu/rest/v2/alpha/col"); // fetch() returns a promise, so we need to wait for it

  const country = await res.json(); // res is now only an HTTP response, so we need to call res.json()

  console.log(country); // Columbia's data will be logged to the dev console
};

fetchData();

Acum, că avem datele de țară pe care le dorim, să trecem la ultima sarcină.

Sarcina 4: Aduceți țările vecine ale unei țări

Dacă deschideți sarcina 4, veți vedea că avem un fetchCountry funcție, care preia datele de la punctul final: https://restcountries.eu/rest/v2/alpha/${alpha3Code} Unde alpha3code este codul țării.

De asemenea, vedeți că va prinde oricare error asta s-ar putea întâmpla la obținerea datelor.

// Task 4: get the neigher countries of Columbia

const fetchCountry = async (alpha3Code) => {
  try {
    const res = await fetch(
      `https://restcountries.eu/rest/v2/alpha/${alpha3Code}`
    );

    const data = await res.json();

    return data;
  } catch (error) {
    console.log(error);
  }
};

Să creăm un fetchCountryAndNeighbors funcționează și extrage informațiile Columbia trecând col dupa cum alpha3code.

const fetchCountryAndNeighbors = async () => {
  const columbia = await fetchCountry("col");

  console.log(columbia);
};

fetchCountryAndNeighbors();

Acum, dacă vă uitați în consolă, puteți vedea un obiect care arată astfel:

Text alternativ

În obiect, există un border proprietate care este o listă de alpha3codes pentru țările vecine din Columbia.

Acum, dacă încercăm să obținem țările vecine prin:

  const neighbors = 
    columbia.borders.map((border) => fetchCountry(border));

Apoi, neighbors va fi o serie de Promise obiecte.

Când lucrăm cu o serie de promisiuni, trebuie să le folosim Promise.all:

const fetchCountryAndNeigbors = async () => {
  const columbia = await fetchCountry("col");

  const neighbors = await Promise.all(
    columbia.borders.map((border) => fetchCountry(border))
  );

  console.log(neighbors);
};

fetchCountryAndNeigbors();

În console, ar trebui să putem vedea lista obiectelor de țară.

Iată tot codul pentru sarcina 4 din nou pentru referință:

const fetchCountry = async (alpha3Code) => {
  try {
    const res = await fetch(
      `https://restcountries.eu/rest/v2/alpha/${alpha3Code}`
    );

    const data = await res.json();

    return data;
  } catch (error) {
    console.log(error);
  }
};

const fetchCountryAndNeigbors = async () => {
  const columbia = await fetchCountry("col");

  const neighbors = await Promise.all(
    columbia.borders.map((border) => fetchCountry(border))
  );

  console.log(neighbors);
};

fetchCountryAndNeigbors();

Concluzie

Text alternativ

După finalizarea acestor 4 sarcini, puteți vedea asta Promise este util atunci când vine vorba de acțiuni asincrone sau lucruri care nu se întâmplă în același timp.

Puteți vedea acest lucru în practică într-unul dintre tutorialele mele, unde construim o aplicație de la zero cu React și Next.js:

__________ 🐣 Despre mine __________