Search:   Dictionary All Posts
Store Your Knowledge at the Brain Bank!

Promises - fetch() - async/await

By Jamie in Lessons / Programming - JS  1.14.19  (Source)
Summary - Promises help JS appear to run code asynchronously. JS runs synchronously on one thread. A promise runs the code, but says continue running other code and I will let you know when this code returns data. fetch() is just a promise that gets an api. async/await is syntactic sugar for running promises.
Asynchronous JS
When we run a function one at a time in order, that is synchronous code. But what if the code you are running in step 1 takes a long time, that will hold up everything that comes after it. So JS has come up with a way to make our code asynchronous, even though the language actually is synchronous. 

Promises
The first way JS came up with the to deal with his is something called a promise. When you use a promise you are telling your code, go ahead and run this code, but don't wait for me, continue on and when i am ready I will give you my data, either with the full returned data, or an error if something goes wrong.

A Promise is an object that fetches passed in data and either returns a resolve function or a reject function. If the resolve function is called a .then method exists that handles the returned data. In the reject function a .catch method exists which returns the error message.

Resources - Eric Elliot - Have a function that returns a promise - In another function, handle the returned promise with a .then. see here...

.then
Let's say a promise is resolved, its data is sent to the .then. but in the .then you can run another function which returns a new result after using some of the data from the main result. the result of that can be found in another .then statement. for instance...

const getName = new Promise((resolve, reject) => {
    resolve('Jamie')
    reject('No good')
})

getName
    .then(data => {
        console.log(data)
        return 'Hello,' + data
    })
    .then(result => console.log(result))
    .catch(error => console.log(error))

this will console 'Jamie' then it will console 'Hello, Jamie'

.catch()
The code in the .catch will only run if an error happens with the promise or the .then function. The .catch takes in a parameter which is equal to the error message, so most people name it error, or err

The .catch will only catch errors that happens before it, so...

promise
    .then()
    .then()
    .catch()
    .then()

this will only catch errors from the first two .then. if there are no errors, all the .then will run properly. if there is an error in the third .then nothing will be returned and a regular browser error will be displayed in the console.

Promise.all()
say you have multiple promises and you want to run them all in the same chain. in other words, you have 5 promises and each promise needs some data from the previous promise before it can run. In that case you use Promise.all()

const promise1 = new Promise((resolve, reject) => resolve('First'))
const promise2 = new Promise((resolve, reject) => resolve('Second'))
const promise3 = new Promise((resolve, reject) => resolve('Third'))
const promise4 = new Promise((resolve, reject) => resolve('Fourth'))
const promise5 = new Promise((resolve, reject) => resolve('Fifth'))

Promise.all([promise1, promise2, promise3, promise4, promise5])
    .then(result => console.log(result))

In this instance we are sending in an array of promises. This means it will return an array with the returned data for each in its spot in the array.

However, we can instead use a forEach function to individually display the results of each promise...

Promise.all([promise1, promise2, promise3, promise4, promise5])
    .then(result => result.forEach(item => console.log(item)))

The Fetch API is a version of the promise provided in the window object. The API provides a fetch() function, which is a method that gets data from a provided json file, typically from an external source (either a backend db or a publicly provided API). Because it is a window property it is not available in Node. However, there is an npm package 'node-fetch' that can be installed. React has it built in so it can be used out of the box after bootstrapping with create-react-app.

It returns a promise. That is important to understand. You then take in the promise response, which is just the http response, which includes headers and the body. The headers might have information like 200, 400, 404 etc. The body will contain the data returned by the api. In order to convert the body into json, we return response.json() - which is a built in function that does what it sounds like. 

Then we use another .then which takes in the json data which can be used however.

Mozilla resource and details on the Fetch API

fetch('url')
    .then(result => result.json() )
    .then(data => console.log(data))
    .catch(error => console.log(error))

A real example...

fetch('https://jsonplaceholder.typicode.com/users')
    .then(result => result.json())
    .then(data => data.forEach(item => console.log(item.name))
    .catch( err => console.log(err) )

fetch() second argument

say you are uploading data, which means you will need to talk to the server to let it know what kind of request this is (post, put, etc), you will have to provide it with an object of data that it expects. 

It will likely expect the method, which is just a string of which type of request, headers which will be on object with several options, but mainly setting the content-type expected to receive in the body which will be an object of data to be sent with the request. However, that object needs to be a string, so it needs to run through the json.stringify() method first.

Get requests might be sending a simple object with a credentials property, especially for API's that require credentials. You can also upload a file or multiple files, see the docs link above for more details.

fetch('https://jsonplaceholder.typicode.com/users', {
    method: 'post',
    headers: {'Content-Type': 'application/json' },
    body: JSON.stringify({
        name: "Jamie",
        cash: "500"
    })
})
.then(res => res.json())
.then(data => console.log(data)
.catch(err => console.log(data)

async/await - instead of promise

const urls = [
    'https://jsonplaceholder.typicode.com/users',
    'https://jsonplaceholder.typicode.com/posts',
    'https://jsonplaceholder.typicode.com/albums'
]

const getData = async function(){

    try {
        const [ users, posts, albums ] = await Promise.all(urls.map(url => {
            return fetch(url).then(result => result.json())
        }))
        console.log('users', users)
        console.log('posts', posts)
        console.log('albums', albums)
    } catch (err) {
        console.log('error', err)
    }

}

const getData = async function(){
    try{
        const [users, posts, albums] = await Promise.all(urls.map(async function(url) {
            const resp = await fetch(url)
            return resp.json()
        }))
        console.log(users, posts, albums)
    } catch (err) {
        console.log(err)
    }
}

NEW ES9 2018 - await for of x loop

const getData = async function(){
    try {
        const arrayOfPromises = urls.map(url => fetch(url))
        for await (let response of arrayOfPromises) {
                const data = await response.json()
                console.log(data)
        }
    } catch (err) {
        console.log(err)
    }
}