Moduł11 - Zajęcia 22 - Składnia async/await
S1A1: Funkcje asynchroniczne
Praca z backendem może być utrudniona, ponieważ po jednej operacji asynchronicznej trzeba wysłać kolejne żądanie do serwera, korzystając z otrzymanych danych, i tak dalej kilka razy. Na przykład na stronie profilu użytkownik chce zobaczyć listę znajomych. Pierwszą rzeczą do zrobienia jest potwierdzenie jego praw dostępu do tej strony w backendzie. Aby to zrobić, musisz wysłać żądanie do my-api.com/me. Jeśli backend umożliwia dostęp, w odpowiedzi otrzymamy unikalny token dostępu do chronionych zasobów.
const fetchFriends = () => {
return fetch("my-api.com/me").then(token => {
console.log(token);
});
};
Następnie musisz poprosić o profil użytkownika z my-api.com/profile, ale profil nie jest kompletny, zawiera tylko krytyczne informacje - ID użytkownika, bez listy znajomych.
const fetchFriends = () => {
return fetch("my-api.com/me")
.then(token => {
return fetch(`my-api.com/profile?token=${token}`);
})
.then(user => {
console.log(user.id);
});
};
I dopiero potem możesz poprosić o listę znajomych od my-api.com/users/:userId/friends.
const fetchFriends = () => {
return fetch("my-api.com/me")
.then(token => {
return fetch(`my-api.com/profile?token=${token}`);
})
.then(user => {
return fetch(`my-api.com/users/${user.id}/friends`);
});
};
fetchFriends()
.then(friends => console.log(friends))
.catch(error => console.error(error));
Sam widzisz, nie jest to najbardziej czytelny kod, chociaż operacje są stosunkowo proste. Dzięki temu, że przekazujemy funkcje obsługi do metody then(), otrzymujemy drzewa zagnieżdżone co nazywamy callback hell
Skłdania funkcji asynchronicznych pomagają ominąć ten problem. Działają przy tym doskonale w połączeniu z metodami then() i catch(), ponieważ gwarantują zwrócenie obietnicy.
const fetchFriends = async () => {
const token = await fetch("my-api.com/me");
const user = await fetch(`my-api.com/profile?token=${token}`);
const friends = await fetch(`my-api.com/users/${user.id}/friends`);
return friends;
};
fetchFriends()
.then(friends => console.log(friends))
.catch(error => console.error(error));
S1A2: Składnia async/await
Funkcje asynchroniczne (async/await) to wygodny sposób na pisanie kodu asynchronicznego, który wygląda na pierwszy rzut oka jak synchroniczny. Składnia async/await opiera się "pod spodem" na obietnicach, więc nie blokuje głównego wątku programu.
Aby zadeklarować asynchroniczną funkcję strzałkową, dodaj słowo kluczowe async przed listą parametrów. Wewnątrz możesz wtedy użyć operatora await i umieścić coś po jego prawej stronie, co zwróci obietnicę. Metoda response.json() również zwraca obietnicę, więc umieszczamy tam słowo kluczowe await.
const fetchUsers = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
return users;
};
fetchUsers().then(users => console.log(users));
Kiedy interpreter napotka await, zawiesza wykonanie tej funkcji (nie całego skryptu) i czeka na wykonanie obietnicy po prawej stronie await. Gdy tylko obietnica stanie się settled, wykonanie funkcji jest wznawiane i w wierszu poniżej dostępny jest dla nas wynik operacji asynchronicznej.
- Operator await może być użyty tylko w treści funkcji asynchronicznej (async)
- Operator await zawiesza funkcję do czasu spełnienia obietnicy (fulfilled lub rejected).
- Jeśli obietnica została spełniona (fulfilled), operator await zwróci jej wartość.
- Jeśli obietnica została odrzucona (rejected), operator await wyrzuci błąd.
- Funkcja asynchroniczna zawsze zwraca obietnicę, więc każda zwracana wartość będzie jej wartością.
- Jeśli nie określisz zwracanej wartości, zostanie zwrócona obietnica o wartości undefined.
Każda funkcja może być asynchroniczna, nie ważne czy jest to metoda obiektu, klasy, callback, deklaracja czy funkcja wbudowana. Wszystkie będą mogły używać operatora await i na pewno zwrócą obietnicę, ponieważ będą to funkcje asynchroniczne.
// Function declaration
async function foo() {
// ...
}
// Functional expression
const foo = async function () {
// ...
};
// Arrow function
const foo = async () => {
// ...
};
// Object method
const user = {
async foo() {
// ...
},
};
// Class method
class User {
async foo() {
// ...
}
}
S1A3: Obsługa błędów
Jeśli wynik funkcji asynchronicznej (obietnica) nie jest używany w kodzie zewnętrznym, błędy są obsługiwane w ciele funkcji za pomocą konstrukcji try...catch. Wartość parametru error w bloku catch to błąd, który wygeneruje await, jeśli obietnica zostanie odrzucona.
Przykład -------------------------------
const form_s1a5 = document.querySelector('form#form-s1a5');
const postsList_s1a5 = document.querySelector('ul#user-list-s1a5');
function getOptionsToUrl_s1a5(id) {
const postToDelete = {
id,
};
const options = {
method: 'DELETE',
body: JSON.stringify(postToDelete),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
};
return options;
}
form_s1a5.addEventListener('submit', event => {
event.preventDefault();
const {
elements: { input_id_s1a5 },
} = event.currentTarget;
console.log(input_id_s1a5.value);
const options = getOptionsToUrl_s1a5(input_id_s1a5.value);
fetch(
`https://jsonplaceholder.typicode.com/posts/${input_id_s1a5.value}`,
options
)
.then(response => response.json())
.then(post => {
console.log(post);
renderPosts_s1a5(post);
})
.catch(error => Notify.failure(`${error}`, optionsNotify));
});
function renderPosts_s1a5(postDelete) {
const { id, title, body } = postDelete;
const markup = `<li >
<p><b>Method POST:</b></p>
<p><b>Post id</b>: ${id}</p>
<p><b>Post title</b>: ${title}</p>
<p><b>Post body</b>: ${body}</p>
</li>`;
postsList_s1a5.innerHTML = markup;
}
PrzykładEND -------------------------------