ES6 - A beginners guide - Promises and Fetch
Tags: javascript, webdev, beginners, codenewbie
Published: 2021-12-15 08:00:48

This time I am going to cover ES6's introduction of Promise and Fetch as native JavaScript functionality in the browser. A lot of dev's will use 3rd party libraries such as Axios, SuperAgent, or jQuery although it might not always be necessary to do so and it may just add bloat to your projects. We'll start by looking at ES6 Promises, before heading on over to details about Fetch
## Promises
### What is a Promise?
Much like in the real world, a promise is the result of saying we will do something and give something back. Let's say we wanted to run this piece of code:
``` javascript
const url = "http://www.json.com";
const data = makeRequest(url);
console.log(data);
```
In the above, our console.log will result in showing undefined because we will simply be executing line 3 immediately after line 2, regardless of how quick that makeRequest function runs, it will never be quicker than the execution of the following line. JavaScript Promises give us a method of using 3 different states whilst waiting for something to complete, such as an AJAX request. The three states were can use are:
* `unresolved` - This is out "waiting" phase, if we were to check in on the value of a Promise periodically using a setTimeout() or similar we would see this until the promise either completed or failed
* `resolved` - This is our "finished" state, we have finished getting the data, the promise is fulfilled and something is ready to be returned.
* `rejected` - This is our "error" state, something went wrong, this would be used to trigger some form of error handling.
Off the back of these states we have two possible callbacks that we can use:
* `then` - This can be used after a `resolved` state is triggered, it tells our code what to do next
* `catch` - Much like with a try/catch loop, this is where we perform our error handling
### How about an example?
The following examples, can easily be plugged straight into the Dev Tools of your browser and run from the Console screen. Let's get started:
``` javascript
promise = new Promise()
```
Uh oh! we got an error, but why? well, if you run the above code you should see an error similar to `Uncaught TypeError: Promise resolver undefined is not a function`. This error response is telling us that the browser knows what a Promise is, but we haven't told it what to do in order to resolve the promise. It's actually really simple to fix. Let's fix it now:
``` javascript
promise = new Promise(()=>{})
```
Now we have created a promise, if you run the above code you'll see that it gives a response similar to this:
`Promise {<pending>}[[Prototype]]: Promise[[PromiseState]]: "pending"[[PromiseResult]]: undefined`. So now we have created a promise, it doesn't do much right now though. When we define a promise we need to handle how/when its resolved and rejected, luckily the `Promise` had two built in arguments that we can use, these are `resolve` and `reject`. Let's have a look at that:
``` javascript
promiseResolve = new Promise((resolve, reject) => {
  resolve()
});
```
In the code above you'll see we create a new `Promise`, we include our two arguments in our inner function. We then call `resolve()` inside our function in order to complete the execution. If you run the code above the browser will output something like: `Promise {<fulfilled>: undefined}`. Likewise we can do the same with `reject()`:
``` javascript
promiseReject = new Promise((resolve, reject) => {
  reject()
});
```
Wait! we got a warning, we have the following returned `Promise {<rejected>: undefined}` this is expected, however we also got `Uncaught (in promise) undefined` because we didn't handle the rejection properly. Let's look at our callbacks, they'll help us handle both state calls above.
### Using callbacks
ES6 Promises give us two built in callback methods as mentioned above they are `.then()` and `.catch()`. We can use `.then()` when we resolve a promise to instruct our code on the next action, and the parameter in the function will automatically take the value that was returned in our promise. Let's look at an example:
``` javascript
promiseResolve = new Promise((resolve, reject) => {
  resolve('Promise resolved');
});

promiseResolve
  .then((resolvedValue) => console.log(resolvedValue))
  .then(() => console.log('Still resolved'))
```
Notice how in our first `.then()` we have a parameter for our function, we then use that parameter in the return of the arrow function, however rather than giving us an error about the variable being undefined the above code will actually give use the following output:
```
Promise resolved
Still resolved
Promise {<fulfilled>: undefined}
```
So as we can see `resolvedValue` actually gives us the value we passed back in the resolve. We're going to revisit this later in the article when we look at using `fetch` to pull remote data. Now on to error handling, let's jump straight into an example:
``` javascript
promiseReject = new Promise((resolve, reject) => {
  reject('Promise rejected')
});
    
promiseReject
  .then(() => console.log('Promise resolved'))
  .then(() => console.log('Still resolved'))
  .catch((err) => console.log(err))
```
As with the above, we can now see that our catch is including a parameter and our console.log message contains `Promise rejected` but we do not output `Promise resolved` or `Still resolved` and this is because we fired the `reject()` line in out promise.
### Using asyncronous callbacks
We can use asyncronous callback in our ES6 Promises, this can help to simulate what would happen when making an AJAX call or similar to pull data. In the example below we will wait for 5 seconds before resolving our promise.
``` javascript
promiseAsync = new Promise((resolve, reject) => {
  console.log('Starting Promise...')
  setTimeout(() => {resolve('Promise resolved')}, 5000)
});
    
promiseAsync
  .then((response) => console.log(response))
  .catch(() => console.log('Promise rejected'))
```
We can use a library like jQuery to make a request and using a promise we can then take an action when it completes, take a look below, we will add a log to say we have started, then we'll fetch a JSON sample of blog posts in a promise, and then log that response
``` javascript
promiseAsync = new Promise((resolve, reject) => {
  console.log('Starting promise')
  $.ajax({
    url: 'https://jsonplaceholder.typicode.com/posts/',
    type: 'GET',
    success: function (data) {
      resolve(data)
    },
    error: function (error) {
      reject(error)
    },
  })
})

promiseAsync
  .then((response) => console.log(response))
  .catch((error) => console.log('Promise rejected', error))
```
Running the code above gives us `Starting promise` and then `Array(100)` in the dev tools. I ran this on JSFiddle so that I could ensure jQuery was included.
## So what about Fetch?
Fetch is a new feature included with ES6, it provides us to combine a network request with a promise in a super simple form! It does have its limitations though, and i'll go into them in a bit, but first...you know what's coming...an example!
``` javascript
const url = "https://jsonplaceholder.typicode.com/posts/";
fetch(url)
```
The code above will simply give us a pending Promise in the browser, that's no use to us in this state. With a Promise, as above, we would have to supply a function to handle the resolve/reject conditions, fetch does this for us though. All we need to do is supply callbacks
``` javascript
const url = "https://jsonplaceholder.typicode.com/posts/";
fetch(url)
  .then(data => console.log(data))
```
Well, we're getting there, we now get the following output when we use this in the Console window:
```
Promise {<pending>}
Response {type: 'cors', url: 'https://jsonplaceholder.typicode.com/posts/', redirected: false, status: 200, ok: true, …}
```
This doesn't give us the actual response data, just some metadata. If we want to extract the actual information from jsonplaceholder we are going to pass this through a different function first, let's take a look:
``` javascript
const url = "https://jsonplaceholder.typicode.com/posts/";
fetch(url)
  .then(response => response.json())
  .then(data => console.log(data))
```
Above, we are first using `.json()` in order to take the response stream and render the response body text as JSON ([MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/Response/json)), we then call `.then()` again, this time taking in the return value from `response.json()` and then passing that to `console.log`. This gives us the following output:
```
Promise {<pending>}
(100) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
```
## But Stefan, you mentioned some downfalls of Fetch??
That's right, I did. I mentioned that we can use `.then()` and `.catch()` with Fetch, however the `.catch()` handler isn't always triggered when you expect it. For example, if you go to a page/endpoint that does not exist and gives you an HTTP404 response you won't actually hit the `.catch()` code
``` javascript
const badUrl = "https://jsonplaceholder.typicode.com/posts1321654646186/";
fetch(badUrl)
  .then(response => console.log('SUCCESS: ',response))
  .catch(error => console.log('ERROR', error))
```
The code above will give ue the following output:
```
Promise {<pending>}
GET https://jsonplaceholder.typicode.com/posts1321654646186/ 404 (Not Found)
SUCCESS: Response {type: 'cors', url: 'https://jsonplaceholder.typicode.com/posts1321654646186/', redirected: false, status: 404, ok: false, …}
```
We can see we got a 404 response, but the output is from our `.then()` callback. Fetch is designed in such a way that you would only hit the `.catch()` callback is there was a network level error (such as a failed DNS lookup). The following example would actually go to the `.catch()` callback:
``` javascript
const badUrlHost = "https://jsonplaceholder.typicode12345.com/posts/";
fetch(badUrlHost)
    .then(response => console.log('SUCCESS: ', response))
    .catch(error => console.log('ERROR', error))
```
This time our console gives us:
```
Promise {<pending>}
GET https://jsonplaceholder.typicode12345.com/posts/ net::ERR_TUNNEL_CONNECTION_FAILED
ERROR TypeError: Failed to fetch at <anonymous>:2:1
```
This is ok, but we still want to handle HTTP4XX or HTTP5XX errors gracefully
### There is a way around this
There are generally a couple of suggested "workarounds" for working with these kinds of requests. If you NEED to use the Fetch API, then the following kind of code construction would be better for you:
``` javascript
const badUrl = "https://jsonplaceholder.typicode.com/posts1321654646186/";
fetch(badUrl)
  .then(response => {
    if(!response.ok){
        throw new Error("I'm an error");
    } else{
        return response.json()
    }
  })
  .then(data => console.log('Response Data', data))
  .catch(error => console.log('ERROR', error))
```
In the code above we are now throwing an exception because the response metadatas property for `ok` was false. 
```
Promise {<pending>}
GET https://jsonplaceholder.typicode.com/posts1321654646186/ 404 (Not Found)
ERROR Error: I'm an error
```
From the metadata we could use either `.ok` or `.status` if we wanted to handle errors differently for HTTP4xx errors as opposed to HTTP5XX errors (for example), or we could use both for generic error handling, such as below:
``` javascript
const badUrl = "https://jsonplaceholder.typicode.com/posts1321654646186/";
fetch(badUrl)
  .then(response => {
    if(!response.ok){
        throw new Error(`${response.status} - ${response.statusText}`);
    } else{
        return response.json()
    }
  })
  .then(data => console.log('Response Data', data))
  .catch(error => console.log('ERROR', error))
```
Now we see the following error output:
```
Promise {<pending>}
GET https://jsonplaceholder.typicode.com/posts1321654646186/ 404 (Not Found)
ERROR Error: 404 - Not Found
```

I mentioned that there were a couple of suggestions for workarounds, alternatives to the above would be using 3rd Party Libraries/Tools such as:
- [jQuery](https://jquery.com)
- [Axios](https://axios-http.com/docs/intro) - This is my personal favourite and I use it in my own projects
- [SuperAgent](https://github.com/visionmedia/superagent)

You could also just use XMLHttpRequest which has long been baked into browsers as default functionality, information on that can be found on [MDN here](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)