Section 16: Asynchronous JavaScript: Promises, Async/Await, and AJAX Flashcards
Definition
- Synchronous
- Asynchronous
- Synchronous
- Most code is synchronous
- Synchronous code is executed line by line
- Each code wait for previous code to finish
- long-running operations block code from execution (Example: alert() prevents next line of code executing until the user click on OK button - Asynchronous
- Asynchronous code is executed after a task that runs in the background finishes
- non-blocking
- Execution doesn’t wait for an asynchronous task to finish its work
- Example: setTimeout() using timer, setting src attribute of image, loading image event listener, AJAX call,…
Definition
- AJAX call
- API
- Online API
- XML and JSON
- Asynchronous Javascript And XML: Allow us to communicate to remote web servers (thru web API) in an asynchronous way. With ajax call, we can request data from web servers dynamically
- Application Programming Interface: A piece of software that can be used by another piece of software. in order to allow applications to communicate to each other
- There are many type of API: DOM API, Geolocation API, Online API - Online API: applications run on a server that receives data requests and sends back data as response
- Are data format when API send back as response. Today we use JSON more. It’s JavaScript Object Notation (JS Object converted to string)
Definition
- Fetch API
- Promise
- Promise lifecycle
- Fetch API is another way to make asynchronous HTTP request similar like AJAX. It works with Promise. Fetch API return a response object that represents a response to request. This object provides methods to work with response data such as parsing JSON
- Promise is an object that is used as a placeholder for future result of an asynchronous task
- Promise has different states: Pending, Settled (Fulfilled or Rejected)
Definition
Chaining Promise
What happens if we use / don’t use return keyword?
how many promise meth
then/catch/finally are methods of promise. These methods always return a promise although have or doesn’t have return keyword
- If we return a value, that value is a fulfilled value returned from promise
Problem
/*
This country API provides country’s data using country name and country code
https://restcountries.com/v3.1/name/country_name
https://restcountries.com/v3.1/alpha/country_code
Using then/catch or async/await:
1. Create a function to get country data and display on browser
2. Display 1 neighbour country with class name “neighbour”. Display on browser a notice when country is island
3. Simulate Internet lost, log error to console and display error on browser
4. Simulate input wrong country name or wrong country code, log error to console and display error on browser
5. Refractor code, avoid violating DRY principles
DATA: Check with Vietnam, Australia,…
*/
- then/catch method
~~~
const renderCountry = function (data, className = ‘’) {
const html = `<article class="country ${className}">
<img></img>
<div>
<h3>${data.name.common}</h3>
<h4>${data.region}</h4>
<p><span>👫</span>${(
data.population / 1000000
).toFixed(1)} people</p>
<p><span>🗣️</span>${Object.values(data.languages).join(
', '
)}</p>
<p><span>💰</span>${
Object.values(data.currencies)[0].name
}</p>
</div>
</article> `;
countriesContainer.insertAdjacentHTML(‘beforeend’, html);
// countriesContainer.style.opacity = 1;
};
const renderError = function (msg) {
countriesContainer.insertAdjacentText(‘beforeend’, msg);
// countriesContainer.style.opacity = 1;
};
const getJSON = function (url, errorMsg = ‘Something went wrong’) {
return fetch(url).then(response => {
if (!response.ok) throw new Error(${errorMsg} (${response.status})
);
return response.json();
});
};
const getCountryData = function (country) {
// Country 1
getJSON(https://restcountries.com/v3.1/name/${country}
, ‘No country found’)
.then(data => {
// Render Country
renderCountry(data[0]);
// Country 2 if (!data[0].borders) throw new Error('No neighbour'); const neighbour = data[0].borders[0]; return getJSON( `https://restcountries.com/v3.1/alpha/${neighbour}`, 'No country found' ); }) .then(data => renderCountry(data[0], 'neighbour')) .catch(err => { console.error(err); renderError(`Something went wrong 🥲, ${err.message}`); }) .finally(() => (countriesContainer.style.opacity = 1)); };
getCountryData(‘australia’);
btn.addEventListener(‘click’, function () {
getCountryData(‘vietnam’);
});
~~~
- Async/Await
// Async```
/Await
const renderCountry = function (data, className = ‘’) {
const html = `
<article>
<img></img>
<div>
<h3>${data.name.common}</h3>
<h4>${data.region}</h4>
<p><span>👫</span>${(
data.population / 1000000
).toFixed(1)} people</p>
<p><span>🗣️</span>${Object.values(
data.languages
)}</p>
<p><span>💰</span>${
Object.values(data.currencies)[0].name
}</p>
</div>
</article>
`;
countriesContainer.insertAdjacentHTML(‘beforeend’, html);
countriesContainer.style.opacity = 1;
};
const renderError = function (msg) {
countriesContainer.insertAdjacentText(
‘beforeend’,
Something went wrong. ${msg}
);
countriesContainer.style.opacity = 1;
};
const getJSON = async function (url) {
const res = await fetch(url);
if (!res.ok) throw new Error(“Can’t get country data 🙏”);
return res.json();
};
const getCountryData = async function (country) {
try {
const data = await getJSON(
https://restcountries.com/v3.1/name/${country}
);
// Render Country 1
renderCountry(data[0]);
// Get Country 2 data if (!data[0].borders) throw new Error('This country has no neighbour 🥲'); const neighbour = data[0].borders[0]; const dataNeighbour = await getJSON( `https://restcountries.com/v3.1/alpha/${neighbour}` ); // Render country 2 renderCountry(dataNeighbour[0], 'neighbour'); } catch (err) { console.error(err); renderError(err.message); } }; getCountryData('vietnam');
btn.addEventListener(‘click’, function () {});
~~~
Problem
”/*
In this challenge you will build a function ‘whereAmI’ which renders a country ONLY based on GPS coordinates. For that, you will use a second API to geocode coordinates.
Here are your tasks:
PART 1
1. Create a function ‘whereAmI’ which takes as inputs a latitude value (lat) and a longitude value (lng) (these are GPS coordinates, examples are below).
2. Do ‘reverse geocoding’ of the provided coordinates. Reverse geocoding means to convert coordinates to a meaningful location, like a city and country name. Use this API to do reverse geocoding: https://geocode.xyz/api.
The AJAX call will be done to a URL with this format: https://geocode.xyz/52.508,13.381?geoit=json. Use the fetch API and promises to get the data. Do NOT use the getJSON function we created, that is cheating 😉
3. Once you have the data, take a look at it in the console to see all the attributes that you recieved about the provided location. Then, using this data, log a messsage like this to the console: ‘You are in Berlin, Germany’
4. Chain a .catch method to the end of the promise chain and log errors to the console
5. This API allows you to make only 3 requests per second. If you reload fast, you will get this error with code 403. This is an error with the request. Remember, fetch() does NOT reject the promise in this case. So create an error to reject the promise yourself, with a meaningful error message.
PART 2
6. Now it’s time to use the received data to render a country. So take the relevant attribute from the geocoding API result, and plug it into the countries API that we have been using.
7. Render the country and catch any errors, just like we have done in the last lecture (you can even copy this code, no need to type the same code)
TEST COORDINATES 1: 52.508, 13.381 (Latitude, Longitude)
TEST COORDINATES 2: 19.037, 72.873
TEST COORDINATES 2: -33.933, 18.474
GOOD LUCK 😀
*/”
"const whereAmI = function (lat, long) { fetch(`https://geocode.xyz/${lat},${long}?geoit=json`) .then(response => { console.log(response); return response.json(); }) .then(data => { console.log(data); const city = data.city; const country = data.country; if (!country) throw new Error("Please don't send too much request "); console.log(`You are in ${city}, ${country}`); return fetch(`https://restcountries.com/v3.1/name/${country}`); }) .then(res => res.json()) .then(data => renderCountry(data[0])) .catch(err => console.error(`Fuck that, ${err.message}`)); }; whereAmI(52.508, 13.381); whereAmI(19.037, 72.873); whereAmI(-33.933, 18.474);"
Explain
How JS work behind the scence?
How asynchronous work behind the scence?
- This will explain how asynchronous code can be executed in a non-blocking way if there is only one thread of execution in the engine.
All code are executed in Call Stack, however, any asynchronous code (loading image, callback function wait for loading image to finish to be executed, fetching data, callback code to return promise) are executed in Web APIs environment
After finishing loading image, the callback is put into Callback Queue (Imagine Callback Queue is a to-do list of all callback functions that are in line to be executed. If there is a new callback, it will be put after the 1st callback of image load
If there is no execution in Call Stack, the Event Loop move callback function to Call Stack - called Event Loop Tick. Note: Event Loop will prior callbacks in Microtasks Queue rather than Callback Queue.
Event Loop: Decides when each callback is executed (imagine it’s an orchestration)
For the fetch, when data is arrived, fetch is done. However, callback of then method isn’t moved into Callback Queue because callbacks of promises have a special queue - called Microtasks Queue. This has a priority over Callback Queue
Definition
- Syntax for create promise
- What is promisify?
- new Promise(function(resolve, reject) { } )
- Promisifying is the process of converting a callback-based asynchronous function into a function that returns a Promise. It’s a technique commonly used in JavaScript to work with asynchronous code in a more modern and manageable way, especially when dealing with older callback-based APIs.
Problem
- Write a function to print a message after waiting for a specified number of seconds in a sequential manner.
Example, after each 1 second, print string
‘1 second passed’
‘2 seconds passed’
‘3 seconds passed’ - Can you solve it using 3 ways?
Hint (Callback Hell, Asynchronous (Build a promise) and use promise methods, Async/Await
- Build a Promise
~~~
const wait = function (seconds) {
return new Promise(function (resolve) {
setTimeout(resolve, seconds * 1000);
});
};
wait(1)
.then(() => {
console.log(‘1 second passed’);
return wait(1);
})
.then(() => {
console.log(‘2 seconds passed’);
return wait(1);
})
.then(() => {
console.log(‘3 seconds passed’);
return wait(1);
})
.then(() => {
console.log(‘4 seconds passed’);
return wait(1);
})
.then(() => console.log(‘5 seconds passed’));
~~~
- Callback Hell
~~~
setTimeout(() => {
console.log(‘1 second passed’);
setTimeout(() => {
console.log(‘2 seconds passed’);
setTimeout(() => {
console.log(‘3 seconds passed’);
setTimeout(() => {
console.log(‘4 seconds passed’);
setTimeout(() => {
console.log(‘5 seconds passed’);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
// 3. Async/Await
const wait = function (sec) {
return new Promise(function (resolve, reject) {
setTimeout(resolve, sec * 1000);
});
};
const runtAndWait = async function (times, second = 1) {
for (let i = 1; i <= times; i++) {
await wait(second);
console.log(${i} second${i != 1 ? "s" : ""} passed
);
}
};
runtAndWait(10);
~~~
Problem
Simulate a lottery using promise. Fulfilled promise is to win lottery, rejected promise is lose money. There are 50 - 50 chance to win or lose the game
const lotteryPromise = new Promise(function (resolve, reject) { console.log('Lottery draw is happening 🔮...'); setTimeout(function () { if (Math.random() >= 0.5) { resolve('You WIN 🥳'); } else reject(new Error('You lose money 🥹')); }, 3000); }); lotteryPromise.then(res => console.log(res)).catch(err => console.error(err));
Problem
Create immediately fulfilled and rejected promise
Promise.resolve('abc').then(res => console.log(res)); Promise.reject(new Error('def')).catch(err => console.error(err));
Problem
/*
Write a function to:
1/ get current lat, long of your device
2/ Reverse geolocation to get country
3/ Render country to screen
4/ Handle error (no internet, 403, 404)
Part 2: Using promise methods
5/ return a string “You are in HCM, Vietnam”. Log it to console
6/ With return keyword, simulate a wrong input in country name, does it catch error?
7/ Do anything to create these strings sequentially without modifying inside the function. Do this when function is either fulfilled or rejected
String 1: “1. Will get location”
String 2: “2. You are in HCM, Vietnam” or “2. display error message”
String 3: “3. Finished getting location”
Part 3: Instead of promise methods, use async/await
Given this
1. navigator.geolocation.getCurrentPosition( ): get current position
2. https://geocode.xyz/52.508,13.381?geoit=json : reverse geocoding
3. https://restcountries.com/v3.1/name/country_name : get country data
4. renderCountry function
5. renderError function
const renderCountry = function (data, className = ‘’) {
const html = `
<article>
<img></img>
<div>
<h3>${data.name.common}</h3>
<h4>${data.region}</h4>
<p><span>👫</span>${(
data.population / 1000000
).toFixed(1)} people</p>
<p><span>🗣️</span>${Object.values(
data.languages
)}</p>
<p><span>💰</span>${
Object.values(data.currencies)[0].name
}</p>
</div>
</article>
`;
countriesContainer.insertAdjacentHTML(‘beforeend’, html);
countriesContainer.style.opacity = 1;
};
const renderError = function (msg) {
countriesContainer.insertAdjacentText(
‘beforeend’,
Something went wrong. ${msg}
);
countriesContainer.style.opacity = 1;
};
*/
“const getPosition = function () {
return new Promise(function (resolve, reject) {
navigator.geolocation.getCurrentPosition(resolve, reject);
});
};
const whereAmI = async function () {
try {
// Geolocation
const pos = await getPosition();
const { latitude: lat, longitude: long } = pos.coords;
// Reverse geocoding const resGeo = await fetch(`https://geocode.xyz/${lat},${long}?geoit=json`); const dataGeo = await resGeo.json(); console.log(dataGeo); if (!dataGeo.country) throw new Error( `Problem getting location data (${Object.values(dataGeo)[0]})` ); // Country data const res = await fetch( `https://restcountries.com/v3.1/name/${dataGeo.country}` ); console.log(res); if (!res.ok) throw new Error(`Problem getting country (${res.status})`); const data = await res.json(); console.log(data); // Render country renderCountry(data[0]); return `You are in ${dataGeo.city}, ${dataGeo.country}`; } catch (err) { console.error(`${err} 🥹`); renderError(`Something went wrong, ${err.message}`); // 1. // Reject promise returned from async function throw err; } };
// 2
console.log(1: Will get location
);
whereAmI()
.then(city => console.log(2: ${city}
))
.catch(err => console.error(2: ${err.message}
))
.finally(() => console.log(3: Finished getting location
));”
// Async/Await // 2b (async function () { console.log(`1: Will get location`); try { const city = await whereAmI(); console.log(`2: ${city}`); } catch (err) { console.log(`2: ${err.message}`); }
console.log(3: Finished getting location
);
})();
Definition
How many Promise combinator function?
Promise.all
Promise.race
Promise.allSettled
Promise.any
Problem
”/*
PART 1
Write an async function ‘loadNPause’ that recreates Coding Challenge #2, this time using async/await (only the part where the promise is consumed). Compare the two versions, think about the big differences, and see which one you like more.
Don’t forget to test the error handler, and to set the network speed to ‘Fast 3G’ in the dev tools Network tab.
PART 2
1. Create an async function ‘loadAll’ that receives an array of image paths ‘imgArr’;
2. Use .map to loop over the array, to load all the images with the ‘createImage’ function (call the resulting array ‘imgs’)
3. Check out the ‘imgs’ array in the console! Is it like you expected?
4. Use a promise combinator function to actually get the images from the array 😉
5. Add the ‘paralell’ class to all the images (it has some CSS styles).
TEST DATA: [‘img/img-1.jpg’, ‘img/img-2.jpg’, ‘img/img-3.jpg’]. To test, turn off the ‘loadNPause’ function.
*/
// Coding Challenge #2
/*
Build the image loading functionality that I just showed you on the screen.
Tasks are not super-descriptive this time, so that you can figure out some stuff on your own. Pretend you’re working on your own 😉
PART 1
1. Create a function ‘createImage’ which receives imgPath as an input. This function returns a promise which creates a new image (use document.createElement(‘img’)) and sets the .src attribute to the provided image path. When the image is done loading, append it to the DOM element with the ‘images’ class, and resolve the promise. The fulfilled value should be the image element itself. In case there is an error loading the image (‘error’ event), reject the promise.
If this part is too tricky for you, just watch the first part of the solution.
PART 2
2. Comsume the promise using .then and also add an error handler;
3. After the image has loaded, pause execution for 2 seconds using the wait function we created earlier;
4. After the 2 seconds have passed, hide the current image (set display to ‘none’), and load a second image (HINT: Use the image element returned by the createImage promise to hide the current image. You will need a global variable for that 😉);
5. After the second image has loaded, pause execution for 2 seconds again;
6. After the 2 seconds have passed, hide the current image.
TEST DATA: Images in the img folder. Test the error handler by passing a wrong image path. Set the network speed to ‘Fast 3G’ in the dev tools Network tab, otherwise images load too fast.
GOOD LUCK 😀
*/”
const wait = function (seconds) { return new Promise(function (resolve) { setTimeout(resolve, seconds * 1000); }); }; const imgContainer = document.querySelector('.images'); const createImage = function (imgPath) { return new Promise(function (resolve, reject) { const img = document.createElement('img'); img.src = imgPath; img.addEventListener('load', function () { imgContainer.append(img); resolve(img); }); img.addEventListener('error', function () { reject(new Error('Image not found')); }); }); }; let currentImg; // createImage('img/img-1.jpg') // .then(img => { // currentImg = img; // console.log('Image 1 loaded'); // return wait(2); // }) // .then(() => { // currentImg.style.display = 'none'; // return createImage('img/img-2.jpg'); // }) // .then(img => { // currentImg = img; // console.log('Image 2 loaded'); // return wait(2); // }) // .then(() => { // currentImg.style.display = 'none'; // }) // .catch(err => console.error(err)); // PART 1 const loadNPause = async function () { try { // Load image 1 let img = await createImage('img/img-1.jpg'); console.log('Image 1 loaded'); await wait(2); img.style.display = 'none'; // Load image 1 img = await createImage('img/img-2.jpg'); console.log('Image 2 loaded'); await wait(2); img.style.display = 'none'; } catch (err) { console.error(err); } }; // loadNPause(); // PART 2 const loadAll = async function (imgArr) { try { const imgs = imgArr.map(async img => await createImage(img)); const imgsEl = await Promise.all(imgs); console.log(imgsEl); imgsEl.forEach(img => img.classList.add('parallel')); } catch (err) { console.error(err); } }; loadAll(['img/img-1.jpg', 'img/img-2.jpg', 'img/img-3.jpg']);