Async/Await to dwa nowe słowa kluczowe dodane do języka JavaScript w wersji ES8 (inna nazwa to ES2017), które ułatwiają pisanie funkcji, które operują na obietnicach (ang. promises). Czyli służą do tworzenia funkcji asynchronicznych.
Jeśli nie jesteś zaznajomiony z obietnicami, możesz zobaczyć pierwszą część o programowaniu asynchronicznym w JavaScript.
Słowo kluczowe async, służy do oznaczania funkcji jako asynchronicznych, które będą operować na obietnicach.
Obietnice natomiast dzięki słowu await staja się synchroniczne (a dokładniej ich wygląd w kodzie wygląda na
sekwencyjny i synchroniczny). Można obietnicę przypisywać do zmiennych, wewnątrz których znajdą się wartości
obietnic, dodając słowo kluczowe await przed obietnicą.
Poniżej przykładowy kod:
async function total(username) {
var res = await fetch("/users/" + username);
var user = await res.json();
return user.total;
}Użyto dwóch słów kluczowych await, ponieważ funkcja fetch, zwraca obietnicę zasobu, który udostępnia funkcje
text() oraz json(), które po wywołaniu zwracają następną obietnicę.
Wynikiem funkcji async jest funkcja, która zwraca obietnicę, więc można jej użyć w ten sposób:
total('jan').then(function(total) {
console.log('jan ma ' + total);
});Czyli dokładnie tak jakby nasza funkcja była zapisana w taki, mniej czytelny sposób:
function total(username) {
return fetch("/users/" + username)
.then((res) => res.json())
.then((user) => user.total);
}Można oczywiście użyć, tej funkcji, razem ze słowem await, w innej funkcji async.
Tak naprawdę funkcja async, nie musi mieć w sobie słowa kluczowego await, a i tak będzie zwracała obietnicę, np:
async function getUsername(user) {
return user.username;
}
var person = {
username: 'Jan'
};
getUsername(person).then(function(username) {
console.log(username);
});Czyli jakby była zapisana w taki sposób:
function getUsername(user) {
return Promise.resolve(user.username);
}Zadziała także pusta funkcja. Po prostu obietnicą, będzie wartość undefined, ponieważ w języku JavaScript, każda
funkcja, która nie zwraca jawnie wartości, będzie zwracała wartość undefined.
Jako bardziej rozbudowany przykład, przypomnijmy sobie kod z poprzedniej części o obietnicach:
getUsers().then(function(users) {
return Promise.all(users.map(function(user) {
return Promise.all([user, getProducts(user.id)]);
}));
}).then(function(data) {
return Promise.all(data.map(function([user, products]) {
var total_promise = products.reduce(function(promise, product) {
return promise.then(function(total) {
return Promise.all([getPrice(product.id),
getQuantity(user.id, product.id)])
.then(([price, count]) => total + (count * price));
});
}, Promise.resolve(0));
return Promise.all([user, total_promise]);
}));
}).then(function(data) {
data.forEach(function([user, total]) {
console.log(user.name + ' ' + total);
});
}).catch(function() {
console.log('Błąd w którejś obietnicy, nigdy się nie wywoła');
});Mamy tu tablice użytkowników, a każdy z nich ma tablicę produktów. Z kolei każdy produkt, ma cenę oraz liczność.
Każda z tych wartości jest pobierana poprzez funkcję, która zwraca obietnicę. Powyższy kod, jako funkcja async,
wyglądałby tak:
async function displayUsersTotal() {
try {
var users = await getUsers();
for (let user of users) {
user.products = await getProducts(user.id);
for (let product of user.products) {
product.price = await getPrice(product.id)
product.count = await getQuantity(user.id, product.id);
}
user.total = user.products.reduce(function(acc, product) {
return acc + (product.price * product.count);
}, 0);
console.log(user.name + ' ' + user.total);
}
} catch (e) {
console.log('Błąd w którejś obietnicy, nigdy się nie wywoła');
}
}Jak widzicie kod jest o wiele krótszy i o wiele bardziej czytelny. W powyższym kodzie, oprócz async/await, użyłem
słowa kluczowego let oraz pętli for..of. Let działa tak jak var, tylko że zasięg zmiennej znajduję się wewnątrz
bloku, w którym została zdefiniowana, czyli poza instrukcja for, będzie niezdefiniowana oraz każda iteracja
pętli będzie miała swoją zmienną. Natomiast pętla for..of to nowy dodatek do języka JavaScript, dzięki któremu
iterując po tablicy, iterujemy po jej wartościach, a nie tak jak w przypadku for..in po kluczach/indeksach.
Dla porównania kod z funkcjami zwrotnymi, z pierwszej części:
getUsers(function(users) {
users.forEach(function(user) {
getProducts(user.id, function(products) {
var total = 0;
products.forEach(function(product, i) {
getPrice(product.id, function(price) {
product.price = price;
getQuantity(user.id, product.id, function(quantity) {
total += product.price * quantity;
if (products.length - 1 == i) {
console.log(user.name + ' ' + total);
}
});
});
});
});
});
});Jeśli funkcja, która wywoływana jest ze słowem kluczowym await, się nie powiedzie (wywoła się funkcja reject
obietnicy), wyrzucony zostanie zwykły wyjątek. Dlatego wystarczy jedna instrukcja try..catch, aby je
przechwycić. Tak jak w przypadku samych obietnic wystarczy jedno miejsce obsługi błędów. Wszystkie wyjątki
wyrzucane wewnątrz funkcji async są konwertowane na odrzucaną obietnicę.
Przykład:
function rejected() {
return new Promise((_, reject) => reject('rejected'));
}
async function foo() {
try {
await rejected();
} catch (e) {
console.log('error');
throw e;
}
}
foo().then(() => console.log('nigdy się nie wyświetli')).catch((e) => console.log(e));Powyższy kod wyświetli ’error’ i ’rejected’.
Słowo kluczowe await, może być wywoływane, tylko i wyłącznie wewnątrz funkcja async. Ale istnieje
propozycja aby dodać możliwość użycia go bez async. Z chwilą
pisania tego artykułu, wiem tylko o jednej możliwości użycia await poza funkcja async. Można go użyć w konsoli
devtools przeglądarki Google Chrome/Chromium. Dlatego aby skorzystać z await „luzem”, trzeba utworzyć IIFE
(ang. Immediately Invoked Function Expression), czyli funkcję anonimową, którą od razu wywołujemy:
(async function() {
var username = 'jan';
var res = await fetch('/users/' + username);
var user = await res.json();
console.log(user.fullName);
})();Można też użyć funkcji strzałkowej:
(async () => {
var username = 'jan';
var res = await fetch('/users/' + username);
var user = await res.json();
console.log(user.fullName);
})();Async/Await obsługuje większość nowoczesnych przeglądarek (oprócz oczywiście IE). Ich listę możesz zobaczyć na can I use.
Komentarze
Hasło, które podasz umożliwi ponowne zalogowanie się i np. usunięcie komentarza, jest dobrowolne. Email jest szyfrowany i używany do wysyłania powiadomień o odpowiedziach do komentarzy oraz do pobierania awatara dzięki usłudze Gravatar.com.