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.