Asynchronous JavaScript
Opened this issue · 0 comments
8bitzz commented
Synchronous & Asynchronous
Synchronous vs Asynchronous
- Synchronous: Refers to real-time communication where each party response to a message instantly.
- Asynchronous: Refers to the method of exchanging messages between parties where each party receive and process the message when it's convenient to do so, rather than doing so immediately.
Example of Asynchronous
- Fetching a file from the network
- Accessing a database and returning data from it
- Accessing a video stream from a web cam
- Broadcasting the display to a VR headset
Why async ?
let response = fetch('myImage.png'); // fetch is asynchronous
let blob = response.blob();
// display your image blob in the UI somehow
- We don't know how long it takes to download the image so the
second line
will throw an error since the response is not yet available - The
second line
will need to wait until the response is returned
How to implement async ?
- Async callbacks
- Promises
Async callbacks
- Async callbacks are functions that are specified as arguments --> The callback function is not executed immediately
- The containing function is responsible for executing the callback function when the time comes.
function loadAsset(url, type, callback) {
let xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = type;
xhr.onload = function() {
callback(xhr.response);
};
xhr.send();
}
function displayImage(blob) {
let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
loadAsset('coffee.jpg', 'blob', displayImage);
- The containing function is
loadAsset()
. It uses theXMLHttpRequest
to fetch the resource at the given URL, then pass the response to the callback to do something with - The callback function is
displayImage()
. It is waiting on the XHR call to finish downloading the resource (using the onload event handler), then creates an image to display the URL in, appending it to the document's body
Problem with callback
chooseToppings(function(toppings) {
placeOrder(toppings, function(order) {
collectOrder(order, function(pizza) {
eatPizza(pizza);
}, failureCallback);
}, failureCallback);
}, failureCallback);
- This is messy and hard to read (often referred to as "callback hell")
- It requires the failureCallback() to be called multiple times (once for each nested function), with other issues besides.
- Use Promises will be much nicer
Improvements with promises
What are promises?
- An object that represents an intermediate state of an operation — in effect, a result of some kind will be returned at some point in the future.
- There is no guarantee of exactly when the operation will complete and the result will be returned.
- But there is a guarantee that when the result is available, the code will be executed in order to do something else with a successful result, OR if the promise fails, the code will be executed to gracefully handle a failure case.
Recap
- When
a promise is created
, it is neither in a success or failure state. It is said to be pending. - When
a promise returns
, it is said to be resolved. - A
successfully resolved promise
is said to be fulfilled. It returns a value, which can be accessed by chaining a.then()
block onto the end of the promise chain. The callback function inside the .then() block will contain the promise's return value. - An
unsuccessful resolved promise
is said to be rejected. It returns a reason - an error message stating why the promise was rejected. This reason can be accessed by chaining a.catch()
block onto the end of the promise chain.
Example with fetch Promises
- We can chain multiple async operations together using multiple
.then()
operations. - All errors are handled by a single
.catch()
block at the end of the block
fetch('coffee.jpg')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return response.blob();
}
})
.then(myBlob => {
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
- Fetch promises do not fail on 404 or 500 errors — only on something catastrophic like a network failure. Instead, they succeed, but with the response.ok property set to
false
. To produce an error on a 404, for example, we need to check the value of response.ok, and if false, throw an error, only returning the blob if it is true. - Each call to
.then()
creates a new promise. The value returned by a fulfilled promise becomes the parameter passed to the next .then() block's callback function.
Multiple promises fulfilling
// Define function to fetch a file and return it in a usable form
function fetchAndDecode(url, type) {
return fetch(url)
.then(response => {
if(!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
if(type === 'blob') {
return response.blob();
} else if(type === 'text') {
return response.text();
}
}
})
.catch(e => {
console.log(`There has been a problem with your fetch operation for resource "${url}": ` + e.message);
});
}
// Call the fetchAndDecode() method to fetch the images and the text, and store their promises in variables
let coffee = fetchAndDecode('coffee.jpg', 'blob');
let tea = fetchAndDecode('tea.jpg', 'blob');
let description = fetchAndDecode('description.txt', 'text');
// Use Promise.all() to run code only when all three function calls have resolved
Promise.all([coffee, tea, description])
.then(values => {
console.log(values);
// Store each value returned from the promises in separate variables; create object URLs from the blobs
let objectURL1 = URL.createObjectURL(values[0]);
let objectURL2 = URL.createObjectURL(values[1]);
let descText = values[2];
// Display the images in <img> elements
let image1 = document.createElement('img');
let image2 = document.createElement('img');
image1.src = objectURL1;
image2.src = objectURL2;
document.body.appendChild(image1);
document.body.appendChild(image2);
// Display the text in a paragraph
let para = document.createElement('p');
para.textContent = descText;
document.body.appendChild(para);
});
- The
.then()
callback function will only run whenall three promises resolve
; when that happens, it will be passed an array containing the results from the individual promises (i.e. the decoded response bodies), kind of like [coffee-results, tea-results, description-results].
Rewriting promise code with async/await
- The
async
keyword can be put in front of a function declaration to turn it into an async function --> Invoking the function will return a promise.
let hello = async function() { return "Hello" };
hello().then((value) => console.log(value))
- The
await
keyword can be put in front of any async promise-based function to pause the code on that line until the promise fulfills, then return the resulting value.
async function hello() {
return greeting = await Promise.resolve("Hello");
};
hello().then(alert);
Example
async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
let myBlob = await response.blob();
let objectURL = URL.createObjectURL(myBlob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}
myFetch()
.catch(e => {
console.log('There has been a problem with your fetch operation: ' + e.message);
});
OR
async function myFetch() {
let response = await fetch('coffee.jpg');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.blob();
}
myFetch().then((blob) => {
let objectURL = URL.createObjectURL(blob);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
}).catch(e => console.log(e));