• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Kiedy należy używać obiektów Promise?

Object Storage Arubacloud
0 głosów
467 wizyt
pytanie zadane 24 kwietnia 2020 w JavaScript przez maslokeeper01 Użytkownik (620 p.)

Witam,

Przewertowałem dziesiątki tutoriali i z każdym kolejnym rozumiem coraz mniej. Bardzo proszę o pomoc w zrozumieniu zasady działania obietnic w JavaScripcie - kiedy używać, jak używać, co możemy dzięki nim uzyskać i jakie są korzyści z takiej a nie innej asynchroniczności.

Przykład: piszę proste memory i program ma:

  1. Przypisać 9 kolorów do 18-tu divów reprezentujących karty.
  2. Wyświetlić układ kart w przypisanych do nich kolorach przez dwie sekundy.
  3. Po dwóch sekundach zakryć karty z użyciem klasy 'hidden', która narzuca na kartę czarne tło.

Mój pomysł na te dwa punkty był taki:

const cardsColor = ['red','red', 'yellow','yellow',
 'lime', 'lime', 'indigo', 'indigo',
  'aqua', 'aqua', 'peru', 'peru',
   'green', 'green', 'purple', 'purple',
    'cadetblue', 'cadetblue'];

const cardsColorLength = cardsColor.length;

let allCards = document.querySelectorAll('div');
allCards = [...allCards];
const cardsAmount = allCards.length;

let initializeGame = new Promise((resolve, reject) => {
    let randomDiv = () => Math.floor(Math.random()*cardsAmount);

    cardsColor.forEach(element => {
    let random = randomDiv();

    while (allCards[random].classList.length === 1) random = randomDiv(); //aby nie przypisywało klasy dwa razy do tej samej karty

    allCards[random].classList.add(element);
    resolve('Game generated...');
    });
});

initializeGame
    .then(() => {setTimeout(() => {
        allCards.forEach(element => element.classList.add('hidden'));
        console.log('Great, let\'s play!');
        }, 2000);
    });

(po załadowaniu program inicjuje przypisanie kolorów do kart, po czym zwraca status obietnicy jako spełnionej, co uruchamia procedurę zakrycia kart).

Całość działa bez zarzutu, natomiast mocno zastanawia mnie, czy to właśnie takie użycie obietnicy jest tym właściwym.

Bardzo proszę o wyjaśnienie w miarę zrozumiałym językiem.

Z góry dziękuję.

2 odpowiedzi

+2 głosów
odpowiedź 24 kwietnia 2020 przez ScriptyChris Mędrzec (190,190 p.)
wybrane 25 kwietnia 2020 przez maslokeeper01
 
Najlepsza

Użycie Promise w Twoim kodzie nie ma sensu, ponieważ funkcja initializeGame nie wykonuje kodu asynchronicznego. Cytując dokumentację parametru executor, którym jest callback przekazany do konstruktora promisa:

The executor normally initiates some asynchronous work, and then, once that completes, either calls the resolve function to resolve the promise or else rejects it if an error occurred

Jeśli już (choć w obecnej formie to trochę przerost formy nad treścią), to Promise powinien być użyty przy setTimeout (27 linia), bo dopiero tutaj jest kod wykonywany asynchonicznie.

initializeGame(); // w tym przykładzie ta funkcja nie zwraca Promisa

new Promise(resolve => {
    setTimeout(() => {
        allCards.forEach(element => element.classList.add('hidden'));
        resolve();
    }, 2000);
}).then(() => console.log('Great, let\'s play!'));

Odpowiadając na Twoje pytania:

  1. "kiedy używać?": gdy mamy do czynienia z kodem asynchronicznym (obsługa eventów, timerów, ajaxów, workerów), gdzie skorzystanie z tradycyjnego podejścia z użyciem callbacków pogorszy czytelność i utrzymywalność kodu (o tym później) i gdy pracujemy z kodem ES6+ bez możliwości transpilacji z możliwością transpilacji (czytaj: nie babramy się z jakimś legacy code).
  2. "jak używać?": nie za bardzo rozumiem to pytanie. Jeśli pytasz o składnię, to użyłeś jej prawidłowo (choć to prosty przypadek). :)
  3. "co możemy dzięki nim uzyskać?" / "jakie są korzyści z takiej a nie innej asynchroniczności?":
  • czytelność kodu (płaska struktura then'ów w porównaniu do zagnieżdżonych callbacków - pozbycie się problemu tzw. callback hell), zwłaszcza,że promisy można chainować
  • lepsza kontrola błędów: do obsługi promisa można przekazać drugi callback, który przyjmie parametr błędu, albo można cały chain promisów można "zakończyć" użyciem metody catch i tam obsługiwać błędy lub "przerzucić je" (słówko throw) do następnego catch
  • uniknięcie odwrócenia kontroli nad kodem (IoC): Implementacji kodu w promisie nie interesuje co zrobisz przy obsłudze resolve/reject, bo nawet nie przekazujesz tam swoich callbacków do wykonania, dzięki temu praca z 3rd party API/libkami jest bezpieczniejsza. Promise można potraktować jako taki notyfikator o zakończonej akcji asynchronicznej - ale to od kodu nadrzędnego (wewnątrz then) zależy co się dalej będzie dziać. W przypadku "pełnego" podejścia callbackowego implementacja kodu (np. wnętrze Twojej funkcji initializeGame) dostaje referencje do funkcji, która ma się wykonać po zakończeniu operacji, więc może ją mutować (choćby wywołać w innym kontekście lub zmienić propertisy), albo jeśli nie ma dobrej obsługi błędów, to w ogóle nie odpali Twojego callbacka.
  • praca z promisami jest jeszcze bardziej czytelna, gdy korzysta się ze składni async/await: dzięki temu, czytamy kod jakby wykonywał się synchronicznie, mimo że pod spodem nadal są promisy obsługiwane asynchronicznie
komentarz 24 kwietnia 2020 przez maslokeeper01 Użytkownik (620 p.)

"Jak używać" - przede wszystkim w jaki sposób należy udostępniać callbackowi metody resolve() i reject(). Przez zwykły if? W większości przykładów promise'ów, które do tej pory widziałem, obsługa błędów polega na zainicjowaniu jednej zmiennej Boolean i w zależności od jej wartości true/false zwraca się resolve() lub reject(), co nic nie mówi.

I przede wszystkim - jak działają resolve i reject? Czytałem, że zmieniają status obietnicy i wartość wewnątrz metody resolve przekazują do then, ale to tak ewidentnie nie działa.

komentarz 25 kwietnia 2020 przez ScriptyChris Mędrzec (190,190 p.)

przede wszystkim w jaki sposób należy udostępniać callbackowi metody resolve() i reject()

Czy przez callback masz na myśli wspomniany w dokumentacji executor? Jeśli tak, to... normalnie przekazujesz tam argumenty resolve i reject (chociaż możesz je nazwać jak chcesz - oczywiście wszystko "z głową"). A w jaki sposób je wywołasz, to zależy od implementacji konkretnego przypadku. Może być if..else, może być switch, można nawet przypisać do zewnętrznej zmiennej, lub wrzucić do zewnętrznej tablicy/obiektu i rozwiązać/odrzucić promisa poza executor'em.

Taki abstrakcyjny przykład:

let resolver = () => {};

new Promise(resolve => resolver = resolve)
    .then((value) => console.log('value:', value));

const shouldResolvePromise = confirm('Want to resolve promise?');

if (shouldResolvePromise) {
    resolver('Promise resolved by User. :)');
}

Pamiętaj, że możesz też stworzyć Promisa, który jest już rozwiązany z określoną wartością lub odrzucany z określoną przyczyną. Do tego służą metody Promise.[resolve/reject].

W większości przykładów promise'ów, które do tej pory widziałem, obsługa błędów polega na zainicjowaniu jednej zmiennej Boolean i w zależności od jej wartości true/false zwraca się resolve() lub reject(), co nic nie mówi

To zależy od napisanego kodu, ale nie widzę jaki miał by być problem z ładnie rozpisanym if..else na podstawie flagi. :) Wszystko zależy od przypadku użycia, od tego czy aplikacja ma jakieś generyczne mechanizmy do obsługi błędów itd.

I przede wszystkim - jak działają resolve i reject? Czytałem, że zmieniają status obietnicy i wartość wewnątrz metody resolve przekazują do then, ale to tak ewidentnie nie działa.

Na pytanie "jak konkretnie działają resolve i reject pod spodem" Ci nie odpowiem, bo nie mam takiej wiedzy. Jeśli chcesz, to poszukiwania zacznij od specyfikacji: 

https://tc39.es/ecma262/#sec-promise-constructor

https://tc39.es/ecma262/#sec-createresolvingfunctions

Możesz podać jakiś przykład, gdzie to tak nie działa?

Poniższy przykład pokazuje, że Promise ma 2 stany: najpierw "pending", a potem "resolved" i jego wartością jest to, co przekazałem do resolve.

const promise = new Promise(resolve => setTimeout(() => resolve('some value')));
console.log(promise);
// Promise {<pending>}

setTimeout(() => console.log('Fullfilled promise:', promise));
// Fullfilled promise: Promise {<resolved>: "some value"}

 

+1 głos
odpowiedź 24 kwietnia 2020 przez mb-dir Mądrala (6,710 p.)
Moim zdaniem w tym przypadku używanie obietnic jest czymś zbędnym. Obietnica to obiekt reprezentujący przyszłą wartość akcji asynchronicznej(czyli takiej na której wartość musimy czekać - np odpowiedź od serwera/API - np mamy aplikację, która po kliknięciu w przycisk łączy się z jakimś API i zwraca nam jakieś randomowe zdjęcie - coś takiego to akcja asynchroniczna). W tym przypadku przypisywanie kolorów nie jest akcją asynchroniczną(nie musimy czekać na jej wynik, to nie jest żadne zapytanie do serwera czy API). Obietnic używa się wtedy gdy wiemy że nie otrzymamy od razu wyniku danej operacji, tylko musimy na ten rezultat czekać.
komentarz 24 kwietnia 2020 przez adrian17 Ekspert (344,860 p.)

Popieram, że w tym konkretnym przypadku asynchroniczność nie ma sensu. Ale kolejny krok, "czekanie 2 sekund", można już zrealizować bez callbacków:

showCards();

await wait(2);

hideCards();

 

Podobne pytania

0 głosów
1 odpowiedź 233 wizyt
pytanie zadane 6 czerwca 2020 w JavaScript przez lukas_1994 Nowicjusz (150 p.)
+1 głos
2 odpowiedzi 652 wizyt
pytanie zadane 9 lipca 2020 w JavaScript przez Greeenone Pasjonat (16,100 p.)
0 głosów
1 odpowiedź 205 wizyt
pytanie zadane 25 czerwca 2020 w JavaScript przez mb-dir Mądrala (6,710 p.)

92,576 zapytań

141,426 odpowiedzi

319,652 komentarzy

61,961 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...