Angular - asynchroniczne wykonywanie kodu za pomocą typu Promise

Stronę tą wyświetlono już: 14 razy

Wstęp

Konieczne jest na wstępie opowiedzenie co nieco o tym, czym jest wykonanie kodu asynchronicznie a czym wykonanie kodu synchronicznie. Otóż wyobraź sobie, że jest sobie taki kawałek kodu:

Listing 1
  1. a();
  2. b();

gdzie wszystkie funkcje wykonywane są synchronicznie. Oznacza to, że funkcja b zostanie wykonana dopiero gdy funkcja a zakończy swoje działanie. Co się jednak stanie, jeżeli czas realizacji działania funkcji a będzie wymagał np. 10s? W takim przypadku cała aplikacja zostanie "zamrożona". Użytkownik może sobie pomyśleć, że się zawiesiła i ją zamknąć. Może też być nieco podirytowany zaistniałą sytuacją. Gdyby tylko istniało jakieś sprytne, żeby nie powiedzieć przebiegłe rozwiązanie tego problemu. Rozwiązaniem jest użycie wywołania asynchronicznego, czyli takiego, które wykona kod funkcji a w oddzielnym wątku. Jednym z sposobów realizacji takich asynchronicznych działań jest użycie zmiennych typu Promise (ang. obietnica).

Czym jednak jest ta obietnica i dlaczego to się tak nazywa. Otóż czasami a nawet i często okazuje się, że realizacja kodu funkcji nie będzie możliwa co spowodowane może być szeregiem różnych przyczyn np. napotkanie błędnych danych, przerwanie połączenia itd. itp. W takim razie nie ma żadnej gwarancji, że kod się wykona, ale jest obietnica próby wykonania zadania jakim jest wykonanie kodu.

Podstawowa konstrukcja użycia typu Promise

Czas, aby złożyć pierwszą obietnicę! I jej nie dotrzymać albo dotrzymać (któż to wie). Oto bowiem jak wygląda asynchroniczne wywołanie kodu za pomocą Promise:

Listing 2
  1. recalc(values: number[]): Promise<string> {
  2. return new Promise<string>(
  3. (resolve, reject) => {
  4. let sum = 0;
  5. let isDivideByZero: boolean = false;
  6. for (const value of values) {
  7. if (value === 0) {
  8. console.log('is equal zero');
  9. isDivideByZero = true;
  10. break;
  11. }
  12. console.log('10 devided by ' + value + ' is equal: ' + (10 / value));
  13. sum += 10 / value;
  14. }
  15. if (isDivideByZero) {
  16. reject('ERROR: I made a promise and I faild I just can\'t divide by zero my old jedi friend');
  17. } else {
  18. resolve('Sum is ready my old jedi friend: ' + sum);
  19. }
  20. });
  21. }
  22. ngOnInit() {
  23. // dane do przeliczenia
  24. const values: number[] = [1, 2, 3, 4, 5, 6, 7, 9, 0, 100, 200, 300];
  25. // czas odpalić obietnicę!
  26. this.recalc(values).then((success) => { // jak zostanie zrealizowana
  27. console.log(success);
  28. })
  29. .catch((error) => { // jak zostanie złamana
  30. console.log(error);
  31. })
  32. .finally(() => { // jak zostanie zakończona (nie zależnie czy złamana, czy nie!)
  33. console.log('End of promise my old jedi friend');
  34. });
  35. }

Oczywiście, powyższy kod spowoduje, że po chwili lub co najwyżej dwóch wyświetlone w konsoli przeglądarki zostaną następujące rewelacyjne informacje:

10 devided by 1 is equal: 10
10 devided by 2 is equal: 5
10 devided by 3 is equal: 3.3333333333333335
10 devided by 4 is equal: 2.5
10 devided by 5 is equal: 2
10 devided by 6 is equal: 1.6666666666666667
10 devided by 7 is equal: 1.4285714285714286
10 devided by 9 is equal: 1.1111111111111112
is equal zero
ERROR: I made a promise and I faild I just can't divide by zero my old jedi friend
End of promise my old jedi friend

Jak widać złożyłem obietnicę i nawet na początku dobrze mi szło jej realizowanie. Niestety jedna z wartości liczbowych była równa 0 a wszyscy wiedzą, by nie dzielić przez zero. Z tego też względu musiałem przyznać się do porażki i odrzucić za pomocą metody reject realizację obietnicy. Gdyby tylko to nieszczęsne zero nie istniało w tablicy, wszystko mogło się potoczyć zupełnie inaczej!

Oczywiście powyższy przykład miałby więcej sensu, gdyby dane do przeliczenia miały znaczną liczbę rekordów, taką która zajęła by procesorowi dobrych kilka sekund.

Łączenie obietnic!

Obietnice można spełniać ale można również je łączyć. Coby zbyt długo nie przynudzać spójrzmy łaskawym okiem na poniższy kawałek kodu:

Listing 3
  1. recalc(values: number[]): Promise<number> {
  2. return new Promise<number>(
  3. (resolve, reject) => {
  4. let sum = 0;
  5. let isDivideByZero = false;
  6. for (const value of values) {
  7. if (value === 0) {
  8. console.log('is equal zero');
  9. isDivideByZero = true;
  10. break;
  11. }
  12. console.log('10 devided by ' + value + ' is equal: ' + (10 / value));
  13. sum += 10 / value;
  14. }
  15. if (isDivideByZero) {
  16. reject('ERROR: I made a promise and I faild I just can\'t divide by zero my old jedi friend');
  17. } else {
  18. resolve(sum);
  19. }
  20. });
  21. }
  22. randomCalc(value: number): Promise<number> {
  23. return new Promise<number>(
  24. (resolve, reject) => {
  25. const rand = value * Math.random();
  26. setTimeout(() => {
  27. if (rand < 100) {
  28. resolve(rand);
  29. } else {
  30. reject('ERROR: value is less then 100');
  31. }
  32. }, 5000);
  33. }
  34. )
  35. }
  36. ngOnInit() {
  37. const values: number[] = [1, 2, 3, 4, 5, 6, 7, 9, 10, 100, 200, 300];
  38. this.recalc(values).then((success) => {
  39. console.log('I made a promise and I\'m not fail: ' + success);
  40. return this.randomCalc(success);
  41. })
  42. .then(value => console.log('And anothere one is made: ' + value))
  43. .catch((error) => {
  44. console.log('I made a promise and fail: ' + error);
  45. })
  46. .finally(() => {
  47. console.log('Promise is done my old jedi friend');
  48. });
  49. }

Wynik działania powyższego kodu jest następujący:

10 devided by 1 is equal: 10
10 devided by 2 is equal: 5
10 devided by 3 is equal: 3.3333333333333335
10 devided by 4 is equal: 2.5
10 devided by 5 is equal: 2
10 devided by 6 is equal: 1.6666666666666667
10 devided by 7 is equal: 1.4285714285714286
10 devided by 9 is equal: 1.1111111111111112
10 devided by 10 is equal: 1
10 devided by 100 is equal: 0.1
10 devided by 200 is equal: 0.05
10 devided by 300 is equal: 0.03333333333333333
I made a promise and I'm not fail: 28.2230158730158754
And anothere one is made: 20.89478466266473
Promise is done my old jedi friend

Jak widać, udało mi się tym razem spełnić nie jedną ale dwie obietnice!

Komentarze