# Async and await

  • Async/Await is just a syntax sugar built on top of promises
  • Async/Await makes code look and behave a bit more like synchronous code
  • It makes your code more readable and easier to understand and maintenance-friendly

IMPORTANT

Async/Await does not entirely replace promises, it's only a nicer way to get rid of the chained .then() handlers

# Use promises with Async/Await

  • Assume we have three promises and two regular (synchronous) function that have to be executed one after one, then the code lookt like this:
function makeRequest() {
    return promise1()
    .then((response1) => promise2())
    .then((response2) => regularFunction1())
    .then((data1) => promise3())
    .then((response3) => regularFunction2())
}
makeRequest();
Copied!
1
2
3
4
5
6
7
8
  • Converting this to async/await:
    • Add the keyword async in front of the function that contains one or more asynchronous function
    • Add the keyword await in front of the asynchronous function (not before regular functions) and add the resolved response to a variable
async function makeRequest() {
    const response1 = await promise1();
    const response2 = await promise2();
    const data1 =  regularFunction1();
    const response3 = await promise3();
    regularFunction2();
}
makeRequest();
Copied!
1
2
3
4
5
6
7
8

# User fetch() with Async/Await

A fetch() function can be written with Async/Await as stand-alone version:


 



 












async function fetchFunction() {
    const response = await fetch(url);
    if (!response.ok) {
        throw new Error(`An error has occurred: ${response.status}  ${response.statusText}`); // check for errors
    }
    return response.json();
}

async function otherFunction() {
    try {
        const data = await fetchFunction();
        // process the data inside this function
        // ...
    } catch (error) {
        console.error(error);
    }
}
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • Or as a function where you do something with the data inside te function itself:


 



 







async function fetchFunction() {
    try {
        const response = await fetch(url);
        if (!response.ok) {
            throw new Error(`An error has occurred: ${response.status}  ${response.statusText}`); // check for errors
        }
        const data = await response.json();
        // process the data inside this function
        // ...
    } catch (error) {
        console.error(error);
    }
}
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13

# Error handling

  • We can catch errors using try...catch just like in synchronous code

 





 
 
 



async function makeRequest() {
    try {
        let resovelved1 = await callPromise1();
        let resovelved2 = await callPromise2();
        let data1 =  regFunction1();
        let resovelved3 = await callPromise3();
        regFunction2();
    } catch (error) {
        console.error(error);
    }
}
makeRequest();
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
  • Or use the catch() methode on the function that calls the async function







 
 
 

async function makeRequest() {
    let resovelved1 = await callPromise1();
    let resovelved2 = await callPromise2();
    let data1 =  regFunction1();
    let resovelved3 = await callPromise3();
    regFunction2();
}
makeRequest().catch (error) {
    console.error(error);
};
Copied!
1
2
3
4
5
6
7
8
9
10

# Examples

  • Let's refactor some examples from the previous chapters to Async/Await

# Asynchronous calculator

  • Open es6/asyncAwait/calculator.html and es6/asyncAwait/calculator.js
  • Compare the refactored code with the code from es6/promises/calculator.js
  • There are multiple then() handlers in this solution, but only line 16 and line 24 refers to a promise
  • The rest of them contains just normal (regular) functions















 







 














let x = 5;

const delay = (ms = 1000) => {
    return new Promise((resolve, reject) => {
        if (isNaN(ms)) {
            reject(new Error('Delay only except milliseconds'));
        } else {
            setTimeout(() => resolve(`waited for ${ms} ms`), ms);
        }
    });
};

console.log(`x = ${x}`);
// step 1
console.warn('Give me two seconds, first empty my coffee and then I will multiply x by 2');
delay(2000)
    .then((data) => {
        console.log(`%c ${data}`, 'color: blue');
        x *= 2;
        console.log(`x * 2 = ${x}`);
    })
    // step 2
    .then(() => console.warn("Give me a sec, I'm coming down and add 6 to x"))
    .then(() => delay()) // 1000 is the default value
    .then((data) => {
        console.log(`%c ${data}`, 'color: blue');
        x += 6;
        console.log(`x + 6 = ${x}`);
    })
    .then(() => {
        // step 3
        x -= 1;
        console.log(`x - 1 = ${x}`);
        // step 4 (output x to DOM)
        document.querySelector('pre').innerText = `x = ${x}`;
    })
    .catch((error) => console.error(error));
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# Karaoke

  • Open es6/asyncAwait/karaoke.html and es6/asyncAwait/karaoke.js
  • Compare the refactored code with the code from es6/promises/karaoke.js












 


 



 



 



 



 



 



const lyrics = document.getElementById('lyrics');
const heroes = {
    line1: '<p><span>1</span> I, I will be king</p>',
    line2: '<p><span>2</span> And you, you will be queen</p>',
    line3: '<p><span>3</span> Though nothing will drive them away</p>',
    line4: '<p><span>4</span> We can beat them, just for one day</p>',
    line5: '<p><span>5</span> We can be heroes, just for one day</p>',
};

const delay = (ms = 2000) => new Promise((resolve) => setTimeout(resolve, ms));

lyrics.innerHTML += '<p>--- START --------------------------------------------</p>';
delay()
    .then(() => {
        lyrics.innerHTML += heroes.line1;
        return delay();
    })
    .then(() => {
        lyrics.innerHTML += heroes.line2;
        return delay();
    })
    .then(() => {
        lyrics.innerHTML += heroes.line3;
        return delay();
    })
    .then(() => {
        lyrics.innerHTML += heroes.line4;
        return delay();
    })
    .then(() => {
        lyrics.innerHTML += heroes.line5;
        return delay();
    })
    .then(() => {
        lyrics.innerHTML += '<p>--- END ----------------------------------------------</p>';
        return delay();
    })
    .then(() => console.log('lyrics written to page'));
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# Random words

  • Open es6/asyncAwait/random_words.html and es6/asyncAwait/random_words.js
  • Compare the refactored code with the code from es6/promises/random_words.js











 


 
 






 




const number = document.getElementById('number');
const dl = document.querySelector('dl');
const wordUrl = 'https://random-words-api.vercel.app/word';

number.addEventListener('change', () => {
    console.clear();
    dl.innerHTML = '';
    const words = [];
    for (let i = 1; i <= number.value; i++) {
        // push the JSON response from the fetch API to the words[] array
        // remember that the JSON response is also a promise!
        words.push(fetch(wordUrl).then((response) => response.json()));
    }

    Promise.all(words)
        .then((responses) => {
            // loop over all JSON responses once they have all been resolved
            responses.forEach((response, index) => {
                console.log(`response ${index}`, response);
                dl.innerHTML += `<dt>${response[0].word}</dt><dd>${response[0].definition}</dd>`;
            });
        })
        .catch((error) => console.error(error));
});

number.dispatchEvent(new Event('change'));
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# Fetch images

  • Open es6/asyncAwait/images.html and es6/asyncAwait/images.js
  • Compare the refactored code with the code from es6/fetch/images.js




 
 







 














 



 






const url = 'https://my-json-server.typicode.com/pverhaert/itf-api/picsum';
const spinner = document.querySelector('.spinner');

function fetchImages() {
    fetch(url)
        .then((response) => {
            // check for errors
            if (!response.ok) {
                throw new Error(`An error has occurred: ${response.status}  ${response.statusText}`); // check for errors
            }
            // return JSON data
            return response.json();
        })
        .then((images) => {
            // add a card for every image in the array images[]
            images.forEach((image) => {
                document.getElementById('imgContainer').innerHTML += `
                    <div class="card">
                      <img src="${image.url}/600/400" class="section media" alt="Image by ${image.author}">
                      <div class="section">
                        Image &copy; <a href="https://unsplash.com/photos/${image.meta.unsplash}" target="_blank">${image.author}</a>
                        <br>
                        Category: ${image.category}
                      </div>
                    </div>
                `;
            });
        })
        .then(() => {
            // hide the spinner
            spinner.classList.add('hidden');
        })
        .catch((error) => {
            spinner.classList.add('hidden');
            console.log(error);
        });
}
fetchImages();
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# Fetch movie info

  • Open es6/asyncAwait/omdb.html and es6/asyncAwait/omdb.js
  • Compare the refactored code with the code from es6/fetch/omdb.js






 
 





 
























 


 

















const omdbapi = 'https://www.omdbapi.com/';
const spinner = document.querySelector('.spinner');
const form = document.querySelector('form');

function fetchMovie(url) {
    console.log(url);
    fetch(url)
    .then((response) => {
        if (!response.ok) {
            throw new Error(`An error has occurred: ${response.status}  ${response.statusText}`); // check for errors
        }
        return response.json();
    })
    .then((movie) => {
        // info if movie not found
        let info = `
                  <div class="card warning full-width">
                    <p>No info found for <b>${title.value}</b></p>
                  </div>`;
        // overwrite info if the movie is found
        if (movie.Response === 'True') {
            const poster = movie.Poster && movie.Poster.length > 10 ? `<img src="${movie.Poster}">` : '';
            info = `
                    <div class="row">
                      <div class="col-sm-6">${poster}</div>
                      <div class="col-sm-6">
                        <h2>${movie.Title}</h2>
                        <p><b>Genre: </b>${movie.Genre}<br>
                        <b>Released: </b>${movie.Released}<br>
                        <b>Actors: </b>${movie.Actors}<br>
                        <b>Director: </b>${movie.Director}<br></p>
                        <hr>
                        <p class="text-justify">${movie.Plot}</p>
                      </div>
                    </div>`;
        }
        document.getElementById('movieContainer').innerHTML = info;
    })
    .then(() => {
        spinner.classList.add('hidden');
    })
    .catch((error) => {
        spinner.classList.add('hidden');
        console.log(error);
    });
}

form.addEventListener('submit', (e) => {
    e.preventDefault();
    // get all field data from the form
    const data = new FormData(form);
    // convert data to a query string
    const queryString = new URLSearchParams(data).toString();
    // console.log(queryString);
    // fetch data from OMDb
    fetchMovie(`${omdbapi}?${queryString}`);
});
form.dispatchEvent(new Event('submit'));
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

# Belgian railway schedules (advanced)

  • Open es6/asyncAwait/irail.html and es6/asyncAwait/irail.js
  • Compare the refactored code with the code from es6/fetch/irail.js




































 
 



 


















 





 





























 
 
 
























 































const buttons = document.getElementById('buttons');
const preloader = document.getElementById('preloader');
const stationsUrl = 'https://api.irail.be/stations/?format=json&lang=en';
let closestStations = [];
// default value for myLocation = Geel
const myLocation = {
    lat: 51.162074,
    lng: 4.99088,
};

// get current position
if (navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(
        (position) => {
            // update myLocation
            myLocation.lat = position.coords.latitude;
            myLocation.lng = position.coords.longitude;
            // fetch all Belgian railway stations
            fetchStations();
        },
        (error) => {
            alert('Unable to retrieve your location');
        },
        {
            enableHighAccuracy: true,
            timeout: 3000,
        }
    );
} else {
    alert('Geolocation is not supported by this browser');
}

function fetchStations() {
    // fetch all Belgian railway stations
    preloader.style.visibility = 'visible';
    preloader.querySelector('p').textContent = 'Loading stations ...';
    fetch(stationsUrl)
        .then((response) =>
            // return JSON part of the response
            response.json()
        )
        .then((stations) => {
            console.log('unordered list of railway stations', stations);
            preloader.querySelector('p').textContent = 'Find closest station ...';
            // modify the properties of every station
            return stations.station.map((st) => {
                // convert lat and lng form string to number
                const lat = +st.locationY;
                const lng = +st.locationX;
                // calculate the distance between your location and the station location as the crow flies (in km)
                const distance = calcCrow(lat, lng, myLocation.lat, myLocation.lng);
                return {
                    lat,
                    lng,
                    distance,
                    id: st.id,
                    name: st.name,
                };
            });
        })
        .then((stations) => {
            // order railway stations by distance from your location
            const ordered = stations.sort((a, b) => a.distance - b.distance);
            console.log('ordered list of railway stations', ordered);
            return ordered;
        })
        .then((orderedStations) => {
            // get the five closest railway stations
            closestStations = orderedStations.slice(0, 5);
            console.table(closestStations);
            // add a button for every station
            closestStations.forEach((station) => {
                buttons.innerHTML += `<button data-id="${station.id}">${station.name}</button>`;
            });
            // add event listener to every button
            buttons.querySelectorAll('button').forEach((button) => {
                button.addEventListener('click', function (e) {
                    const id = this.dataset.id;
                    document.getElementById('station').textContent = this.textContent;
                    // get timetable from the clicked station
                    fetchTimetable(id, this.textContent);
                });
            });
            // trigger click event on first button
            buttons.querySelector('button:first-child').dispatchEvent(new Event('click'));
            preloader.style.visibility = 'hidden';
        })
        .catch((error) => {
            console.log(error);
        });
}

function fetchTimetable(id, station) {
    preloader.style.visibility = 'visible';
    preloader.querySelector('p').textContent = `Loading timetable for ${station}...`;
    const liveboardUrl = `https://api.irail.be/liveboard/?id=${id}&format=json&lang=en`;
    fetch(liveboardUrl)
        .then((response) => response.json())
        .then((data) => {
            // we only need the departure[] containing the timetable
            const table = data.departures.departure;
            console.log('timetable', table);
            // return the transformed timetable
            return table.map((tbl) => {
                // the API sends the time in sec, we need ms
                const departure = new Date(+tbl.time * 1000).toTimeString().substring(0, 5);
                let delay = '';
                let color = 'border-green';
                // if delay is not '0', draw a red borde
                if (tbl.delay !== '0') {
                    // convert the delay from sec to min
                    delay = `+${tbl.delay / 60}`;
                    color = 'border-red';
                }
                return {
                    to: tbl.station,
                    platform: tbl.platform,
                    delay,
                    color,
                    departure,
                };
            });
        })
        .then((table) => {
            // build the timetable
            let rows = '';
            table.forEach((train) => {
                rows += `
                    <div class="${train.color}">
                      <span>${train.departure}</span>
                      <span>${train.delay}</span>
                      <span>${train.platform}</span>
                      <span>${train.to}</span>
                   </div> 
                `;
            });
            document.getElementById('timetable').innerHTML = rows;
            preloader.style.visibility = 'hidden';
        })
        .catch((error) => alert(error));
}

//This function takes in latitude and longitude of two location and returns the distance between them as the crow flies (in km)
function calcCrow(lat1, lon1, lat2, lon2) {
    const R = 6371; // km
    const dLat = ((lat2 - lat1) * Math.PI) / 180;
    const dLon = ((lon2 - lon1) * Math.PI) / 180;
    const latitude1 = (lat1 * Math.PI) / 180;
    const latitude2 = (lat2 * Math.PI) / 180;

    const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.sin(dLon / 2) * Math.sin(dLon / 2) * Math.cos(latitude1) * Math.cos(latitude2);
    const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    return Math.round(R * c * 100) / 100;
}
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

# Weather app

const key = 'MBEqSqMMid8s9gsk2cO6lAaYABDX05aM';

const getCity = async (city) => {
  const base = 'http://dataservice.accuweather.com/locations/v1/cities/search';
  const query = `?apikey=${key}&q=${city}`;

  const response = await fetch (base + query);
  const data = await response.json();

  return data[0];
}

getCity('Stockholm')
  .then(data => console.log(data))
  .catch(err => console.log(err));

  const getWeather = async (id) => {
    const base = 'http://dataservice.accuweather.com/currentconditions/v1/';
    const query = `${id}?apikey=${key}`;
    
    const response = await fetch(base + query);
    const data = await response.json();

    return data[0];
  };
Copied!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Last Updated: 4/12/2021, 7:55:49 AM