8bitzz / blogs

0 stars 0 forks source link

Asynchronous JavaScript #15

Open 8bitzz opened 3 years ago

8bitzz commented 3 years ago

Synchronous & Asynchronous

Synchronous vs Asynchronous

Async callbacks

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 the `XMLHttpRequest` 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
```javascript
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
```javascript
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
```javascript
// 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 when `all 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.
```javascript
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.
```javascript
async function hello() {
  return greeting = await Promise.resolve("Hello");
};

hello().then(alert);
```
#### Example

```javascript
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
```javascript
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));
```