Async / await

Async/await to nowy sposób na zapis asynchronicznego kodu. Jest to nakładka na promisy, która ułatwia pisanie nam kodu.

W poprzednim rozdziale za pomocą promisów uporządkowaliśmy nasz kod w taki sposób:


function getCountry() { ... }
function getWeather() { ... }
function updatePage() { ... }

function renderPage() {
    getCountry()
        .then(data => getWeather(data.lat, data.lng))
        .then(dataAndWeather => console.log(data, dataAndWeather))
}

Dzięki async/await powyższy kod możemy jeszcze bardziej uporządkować, dzięki czemu będzie wyglądał bardziej synchronicznie:


async function renderPage() {
    const country = await getCountry();
    const weather = await getWeather(country.lat, country.lng);

    console.log(country, weather);
}

Async

Słowo async postawione przed funkcją sprawia, że dana funkcja zawsze zwraca obietnicę. Nie potrzebujemy tutaj dodawać żadnego dodatkowego kodu:


async function getData() {
    const data = await readData();
    return data;
}

getData()
    .then(res => console.log(res))

Await

Słowo kluczowe await, sprawia, że Javascript poczeka na wykonanie danej obietnicy do jej zakończenia i zwrócenia przez nią wyniku.


function getData() {
    return new Promise(...)
}

async function renderPage() {
    const city = await = getData();
    console.log(city);
}

Ważne by pamiętać, że słowa await możemy używać tylko wewnątrz funkcji async:


function renderPage() {
    const city = await getCountry(); // SyntaxError: await is a reserved word
}

async function renderPage() {
    const city = await getCountry(); //ok
}

Nasz niedziałający kod możemy więc poprawić w następujący sposób:


async function renderPage() {
    const country = await getCountry();
    const weather = await getWeather(country.lat, country.lng);

    updatePage(c, weather);
}

renderPage();

Czasami zdarza się, że będziemy chcieli wykonać jakąś asynchroniczną operację nie będąc wewnątrz funkcji. Pozostaje wtedy użyć IIFE:


const response = await fetch("..."); //błąd bo nie jesteśmy w funkcji

(async () => {
    const response = await fetch("..."); //pl
})();

Obsługa błędów

Jeżeli Promise zakończy się normalnie, zostanie zwrócony wynik. Jeżeli jednak nie zakończy się powodzeniem, zostanie wykonany Reject.

Aby obsłużyć błędy w przypadku async/await, możemy posłużyć się konstrukcją try/catch:


async function renderPage() {
    try {
        const country = await getCountry();
        const weather = await getCountry(country.lat, country.lng);
        console.log(country, weather);
    } catch (err) {
        console.log(err)
    }
}

Jeżeli dana funkcja nie posługuje się powyższą konstrukcją, wtedy możemy catch podłączyć przy jej wywoływaniu:


async function renderPage() {
    const country = await getCountry();
    const weather = await getCountry(country.lat, country.lng);
    return {country, weather}
}

renderPage()
    .then(data => console.log(data))
    .catch(err => console.log(err));

Pamiętajmy też, że jeżeli odpalamy funkcję async z funkcji nie będącej synchroniczną, to powinniśmy używać then:


async function renderPage() {
    const country = await getCountry();
    const weather = await getCountry(country.lat, country.lng);
    return {country, weather}
}

function init() {
    renderPage().then(result => {
        ...
    })
}

Równoczesne operacje

Popatrzmy na powyższe przykłady. Skoro instrukcja await oznacza, że Javascript poczeka na zakończenie danej operacji, oznacza to, że kolejne połączenie rozpocznie się dopiero, gdy poprzednie się zakończy.

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);
}

Przykłady użycia

Poniżej przyjrzymy się kilku przykładom, a przy okazji ciut lepiej zapoznamy się z async/await.

Gdy wejdziemy na stronę http://jsonplaceholder.com, zobaczymy tam przykładowy kod do pobierania danych. Nieco go poniżej zmodyfikowałem by był przyjemniejszy w użyciu:


function loadData() {
    return fetch('https://jsonplaceholder.typicode.com/todos/1')
        .then(response => response.json())
        .then(json => console.log(json))
}

loadData()
    .then(data => console.log(data))

Gdybyśmy chcieli go zapisać za pomocą nowej składni, wyglądało by to jak poniżej:


async function loadData() {
    const response  = await fetch('https://jsonplaceholder.typicode.com/todos/1');
    const json = await response.json();
    return json;
}

loadData()
    .then(data => console.log(data))

Inny przykład pokazuje wczytywanie grafik:


function loadImage(url) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.src = url;
        img.onload = function() {
            resolve(img);
        }
        img.onerror = function() {
            reject(new Error(img));
        }
    });
}

function loadImages() {
    Promise.all([
        loadImage("image1.jpg"),
        loadImage("image2.jpg")
    ])
    .then(imgData => {
        console.log(imgData[0], imgData[1]);
    }).catch(error => {
        console.log(error);
    })
}

loadImages();

Tutaj także możemy poprawić funkcję loadImages:


async function loadImages() {
    try {
        const img1 = await loadImage("image1.jpg"),
        const img2 = await loadImage("image2.jpg"),
    } catch (err) {
        console.log(err)
    }
}