Async / await
Async/await to nowy sposób na zapis asynchronicznego kodu. Dzięki tym słowom praca z obietnicami zaczyna przypominać synchroniczny kod.
Async
Słowo async postawione przed dowolną funkcją tworzy z niej funkcję asynchroniczną, która zwraca obietnicę. Dzięki temu możemy później na nią reagować poznanymi w poprzednim rozdziale funkcjami:
function doThings() {
return Promise.resolve("ok");
}
doThings()
.then(res => {
console.log(res);
})
//to samo co
async function doThings() {
return "ok";
}
doThings()
.then(res => {
console.log(res); //"ok"
});
Await
Słowo kluczowe await sprawia, że JavaScript poczeka na wykonanie asynchronicznego kodu. Dzięki temu zapis bardzo przypomina synchroniczny kod:
function loadUserData() {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve("A"); }, 1000)
});
}
function loadBooks() {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve("B"); }, 1000)
});
}
function loadPets() {
return new Promise((resolve, reject) => {
setTimeout(() => { resolve("C"); }, 1000)
});
}
async function render() {
const user = await loadUserData();
const books = await loadBooks();
const pets = await loadPets();
return {user, books, pets}
}
Słowa await możemy używać tylko wewnątrz funkcji poprzedzonej słowem async:
function renderPage() {
const city = await render(); // SyntaxError: await is a reserved word
}
async function renderPage() {
const city = await render(); //ok
}
Niektórzy programiści wiedząc, że będą używać sporo asynchronicznych operacji wewnątrz swojego kodu, cały kod okrywają IIFE z async lub zdarzeniem DOMContentLoaded:
(async () => {
...
})();
document.addEventListener("DOMContentLoaded", async () => {
...
});
(async () => {
function loadUserData() { ... }
function loadBooks() { ... }
function loadPets() { ... }
async function render() {
const user = await loadUserData();
const books = await loadBooks();
const pets = await loadPets();
return data;
}
const r = await render();
})();
Obsługa błędów
W przypadku obsługi błędów za pomocą async/await, możemy posłużyć się konstrukcją try/catch
:
function loadImage(src) {
const img = new Image();
img.src = src;
return new Promise((resolve, reject) => {
img.onload = function() {
resolve(img);
}
img.onerror = function() {
reject("błąd wczytywania");
}
if (img.complete) resolve(img);
});
}
(async () => {
try {
const imgA = await loadImage("przykladowyA.jpg");
const imgB = await loadImage("przykladowyB.jpg");
console.log(imgA);
console.log(imgB);
} catch(error) {
console.error(error);
} finally {
console.log("Kończymy wczytywanie");
}
})();
Możemy też mieszać składnię async/await z then/catch:
function loadImage(src) {
const img = new Image();
img.src = src;
return new Promise((resolve, reject) => {
img.onload = function() {
resolve(img);
}
img.onerror = function() {
reject("błąd wczytywania");
}
if (img.complete) resolve(img);
});
}
async function loadData() {
try {
const imgA = await loadImage("przykladowyA.jpg");
const imgB = await loadImage("przykladowyB.jpg");
console.log(imgA);
console.log(imgB);
} catch(error) {
//throw new Error(`error`);
//lub
return Promise.reject(`error`);
} finally {
console.log("Kończymy wczytywanie");
}
}
loadData()
.then(response => {
console.log(response);
})
.catch(error => {
console.log("Wystąpił błąd połączenia: ", error);
});
(async () => {
function loadImage(src) {
const img = new Image();
img.src = src;
return new Promise((resolve, reject) => {
img.onload = function() {
resolve(img);
}
img.onerror = function() {
reject("błąd wczytywania");
}
if (img.complete) resolve(img);
});
}
async function loadData() {
try {
const imgA = await loadImage("przykladowyA.jpg");
const imgB = await loadImage("przykladowyB.jpg");
console.log(imgA);
console.log(imgB);
} catch (error) {
return Promise.reject(error)
}
}
try {
const data = await loadData();
console.log(data);
} catch(error) {
console.error(error);
} finally {
console.log("We do cleanup here");
}
})();
Równoczesne operacje
Instrukcja await oznacza to, że kolejna operacja rozpocznie się dopiero, gdy poprzednia się zakończy.
W wielu momentach bardziej optymalnie będzie, gdy nasze operacje wywołamy równocześnie i poczekamy na ich zakończenie:
async function renderPage() {
const country = getCountry();
const weather = getWeather(country.lat, country.lng);
const countryData = await country;
const weatherData = await weather;
updatePage(countryData, weatherData);
}
W przypadku zapisu za pomocą Promise kod mógłby wyglądać tak:
function renderPage() {
const country = getCountry();
const weather = getWeather(country.lat, country.lng);
return Promise.all([country, weather])
}
renderPage().then(...)