# 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
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 resolvedresponse
to a variable
- Add the keyword
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
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
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
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
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
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
solution with promises
refactored with Async/Await
result
- 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
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
solution with promises
refactored with Async/Await
result
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
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
solution with promises
refactored with Async/Await
result
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
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
solution with promises
refactored with Async/Await
result
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 © <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
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
solution with promises
refactored with Async/Await
result
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
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
solution with promises
refactored with Async/Await
result
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
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
READING TIPS
WATCHING TIPS
# 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25