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:


async function doThings() {
    return "ok";
}

doThings()
    .then(res => {
        console.log(res);
    })


//to samo co
async function doThings() {
    return Promise.resolve("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 data;
}

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(...)

Dodatkowe materiały

Bardzo fajny film na powyższe tematy znajdziesz pod adresem: https://www.youtube.com/watch?v=vn3tm0quoqE

Trening czyni mistrza

Jeżeli chcesz sobie potrenować zdobytą wiedzę z tego działu, to zadania znajdują się w repozytorium pod adresem: https://github.com/kartofelek007/zadania-ajax

W repozytorium jest branch "solutions". Tam znajdziesz przykładowe rozwiązania.

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem. Aha - i ta strona korzysta z ciasteczek.

Menu