Preface
Goal: A class example to fetch JSON using ES async/await.
We are going to run this ecmascript
in nodejs in CLI mode.
Source Examples
You can obtain source examples here:
What Standard 🤔?
Standard being used in this article.
- Fat Arrow: ES 2015 (ES6)
- Classes: ES 2015 (ES6)
- Const: ES 2015 (ES6)
- Template Strings: ES 2015 (ES6)
- Async Function: ES 2017 (ES8)
- Fetch: WHATWG
Fetch
is not an ecmacscript
standard.
This is why in NodeJS
you need an additional node-fetch
package.
JSON Provider
JSON is usually utilized to access API. The API utilization increasing with JAM stack. To start learning we need an online API example in JSON format.
Fake REST API
For tutorial purpose, We are going to use jsonplaceholder
:
A fake online REST API for testing and prototyping
The first example copied from that site above is below code:
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => response.json())
.then((json) => console.log(json))
Example Fetch API Usage
There are about six example from above site:
- Getting a resource
- Listing all resources
- Creating a resource
- Updating a resource with PUT
- Updating a resource with PATCH
- Deleting a resource
Since we are going to use JSON from jsonplaceholder
,
we are also going to use these pattern.
1: Simple Promise
Preparing: Install
Since fetch
is not available in nodejs
,
we require to install node-fetch
package in local folder.
❯ npm i -s -D node-fetch
+ node-fetch@2.6.1
updated 1 package and audited 99 packages in 1.978s
Getting a Resource
Consider rewrite the provided example as ES6 below:
// Getting a resource
const fetch = require("node-fetch")
const baseURL = 'https://jsonplaceholder.typicode.com'
const id = 1
fetch(`${baseURL}/posts/${id}`)
fetch(baseURL+'/posts/1')
.then(response => response.json())
.then(json => console.log(json))
We are going to get the result, already in JSON format as below:
$ node 01-01-read-id.js
{
userId: 1,
id: 1,
title: 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit',
body: 'quia et suscipit\n' +
'suscipit recusandae consequuntur expedita et cum\n' +
'reprehenderit molestiae ut ut quas totam\n' +
'nostrum rerum est autem sunt rem eveniet architecto'
}
This used javascript promise
.
It means it run in synchronous manner.
Fetch Options
Options can be attached to a fetch call. For example this update below:
// Updating a resource with PUT
const fetch = require("node-fetch")
const headers = { 'Content-type': 'application/json; charset=UTF-8' }
const baseURL = 'https://jsonplaceholder.typicode.com'
const id = 1
fetch(`${baseURL}/posts/${id}`, {
method: 'PUT',
body: JSON.stringify({
id: 1,
title: 'foo',
body: 'bar',
userId: 1,
}),
headers: headers
})
.then((response) => response.json())
.then((json) => console.log(json))
This has these three options:
- Method: might be
GET
,POST
,PUT
,PATCH
, orDELETE
. - Body: The data.
- Headers: Using JSON.
There are more options that you can read:
And The Rest …
I do not think I have to pour them all in a blog,
since it is already explained in original jsonplaceholder
site anyway.
However you can have a look at all the codes in my repository.
- Promised: Getting a resource
- Promised: Listing all resources
- Promised: Creating a resource
- Promised: Updating a resource with PUT
- Promised: Updating a resource with PATCH
- Promised: Deleting a resource
2: Asynchronus Await
Reading Reference
There is a better explanation about await
here:
I do not have to repeat myself.
Fetch Using Async/Await
Consider rewrite the provided the code above in asynchronous manner. This time using knowledge from article above.
// Getting a resource
const fetch = require("node-fetch")
const baseURL = 'https://jsonplaceholder.typicode.com'
const id = 1
const request = async () => {
const response = await fetch(`${baseURL}/posts/${id}`)
const json = await response.json()
return json
}
request()
.then(data => console.log(data))
.catch(reason => console.log(reason.message))
This code looks fine right?
Try … Catch
What’s the catch?
We cannot just put the code above in try/catch
clause,
without proper understanding of asynchronous
process.
Instead of returning value, we can use function straight away, and let the function handle the response.
// Getting a resource
const fetch = require("node-fetch")
const baseURL = 'https://jsonplaceholder.typicode.com'
const id = 1
const show = json => console.log(json)
const request = async () => {
try {
const response = await fetch(`${baseURL}/posts/${id}`)
const json = await response.json()
show(json)
} catch(err) {
console.log(err.message)
}
}
request()
Consider turn of the wifi
, and experience the error message
:
$ node 02-01-read-id.js
request to https://jsonplaceholder.typicode.com/posts/1 failed, reason: getaddrinfo EAI_AGAIN jsonplaceholder.typicode.com
Apply This Pattern
With the same pattern we can rewrite all the codes. For example this update below:
// Updating a resource with PUT
const fetch = require("node-fetch")
const headers = { 'Content-type': 'application/json; charset=UTF-8' }
const baseURL = 'https://jsonplaceholder.typicode.com'
const id = 1
const show = json => console.log(json)
const options = {
method: 'PUT',
body: JSON.stringify({
id: 1,
title: 'foo',
body: 'bar',
userId: 1,
}),
headers: headers
}
const request = async () => {
try {
const response = await fetch(`${baseURL}/posts/${id}`, options)
const json = await response.json()
show(json)
} catch(err) {
console.log(err.message)
}
}
request()
And The Rest …
I do not think I have to pour them all in a blog, since you can have a look at all the codes in my repository anyway. Each script can be shown as below, along with the result.
$ node 02-03-create.js
{ title: 'foo', body: 'bar', userId: 1, id: 101 }
$ node 02-04-update.js
{ id: 1, title: 'foo', body: 'bar', userId: 1 }
$ node 02-05-patch.js
{
userId: 1,
id: 1,
title: 'foo',
body: 'quia et suscipit\n' +
'suscipit recusandae consequuntur expedita et cum\n' +
'reprehenderit molestiae ut ut quas totam\n' +
'nostrum rerum est autem sunt rem eveniet architecto'
}
$ node 02-06-delete.js
The last one is delete
.
This will show nothing.
3: Class Wrapper
So many file, so little differences.
The six source code above, have a lot in common. We can manage all this code into one class.
Constructor
We can start with constructor
.
And place common variable over there.
const fetch = require("node-fetch")
class PlaceHolder {
constructor() {
this.baseURL = 'https://jsonplaceholder.typicode.com'
this.headers = { 'Content-type': 'application/json; charset=UTF-8' }
}
…
}
Private Method
This is the core of our class.
We can have async function
in a class.
// Private Method (using # at tc39)
async _request(url, options = null) {
try {
const response = options === null ?
await fetch(url) : await fetch(url, options)
const json = await response.json()
return json
} catch(err) {
console.log(err.message)
}
}
This way, we can call the _request
,
or each public method later, in a simple way.
read(id) {
return this._request(`${this.baseURL}/posts/${id}`)
}
And then we call the method later with:
placeholder.read(1)
.then(json => console.log(json))
Most of the time, I do not know what later implementation, will be working on the data. I’d better keep these methods in returned fashioned, and decide what to do with it later.
Public Method
We have six public method. All five are here.
// Public Method
readAll() {
return this._request(`${this.baseURL}/posts`)
}
read(id) {
return this._request(`${this.baseURL}/posts/${id}`)
}
create(body) {
const options = {
method: 'POST',
body: JSON.stringify(body),
headers: this.headers
}
return this._request(`${this.baseURL}/posts`, options)
}
update(id, body) {
const options = {
method: 'PUT',
body: JSON.stringify(body),
headers: this.headers
}
return this._request(`${this.baseURL}/posts/${id}`, options)
}
patch(id, body) {
const options = {
method: 'PATCH',
body: JSON.stringify(body),
headers: this.headers
}
return this._request(`${this.baseURL}/posts/${id}`, options)
}
Delete Method
The choice is yours.
The last is the delete method
.
Depends on your requirement, this can run a callback, or just do nothing.
This example show different pattern for delete
function.
Thus, using its own method,
instead of using already made this._request
method.
async delete(id) {
try {
const url = `${this.baseURL}/posts/${id}`
const response = await fetch(url, { method: 'DELETE' })
const json = await response.json()
} catch(err) {
console.log(err.message)
}
}
The code management is depend on your requirement. Mine might different with yours.
Example Usage
First we must make a new class instance.
const placeholder = new PlaceHolder()
Then we can call each different method,
that might have different data
.
- Usage: Getting a resource
placeholder.readAll()
.then(json => console.log(json))
- Usage: Listing all resources
placeholder.read(1)
.then(json => console.log(json))
- Usage: Creating a resource
const body = { title: 'foo', body: 'bar', userId: 1 }
placeholder.create(body)
.then(json => console.log(json))
- Usage: Updating a resource with PUT
const body = { id: 1, title: 'foo', body: 'bar', userId: 1 }
placeholder.update(1, body)
.then(json => console.log(json))
- Usage: Updating a resource with PATCH
const body = { id: 1, title: 'foo' }
placeholder.patch(1, body)
.then(json => console.log(json))
- Usage: Deleting a resource
placeholder.delete(1)
This last one won’t return anything.
Alternative Usage
You can also write down in asynchronous fashioned:
const request = async () => { return await placeholder.read(1) }
request().then(json => console.log(json))
But I still do not know, if there is any practical reason to do this.
4: Callback in Class
Sometimes I failed 😁.
I have managed other way to show all the class,
without using .then(…)
over and over again.
Callback Method
You can skip this entire useless part.
Turning above function into call back is not a hard thing to do.
class PlaceHolder {
constructor(callback=null) {
this.baseURL = 'https://jsonplaceholder.typicode.com'
this.headers = { 'Content-type': 'application/json; charset=UTF-8' }
this.callback = callback
}
// Private Method (using # at tc39)
_doCallback(json, response) {
if (this.callback) this.callback(json, response)
}
async _request(url, options = null) {
try {
const response = options === null ?
await fetch(url) : await fetch(url, options)
const json = await response.json()
this._doCallback(json, response)
} catch(err) {
console.log(err.message)
}
}
…
}
Example Usage
Now we can instantiate the class as below:
const placeholder = new PlaceHolder( (json, response) => {
if (response.status == '200') {
console.log(json)
} else {
console.log(`status [${response.status}]`)
}
});
const body = { id: 1, title: 'foo' }
placeholder.patch(1, body)
With the result as below:
$ node 03-02-class.js
{
userId: 1,
id: 1,
title: 'foo',
body: 'quia et suscipit\n' +
'suscipit recusandae consequuntur expedita et cum\n' +
'reprehenderit molestiae ut ut quas totam\n' +
'nostrum rerum est autem sunt rem eveniet architecto'
}
Error Response
Consider test our callback.
placeholder.read(1000)
This will show
$ node 03-02-class.js
status [404]
What Failure?
The method must return data asynchronously to be used later.
Yes, this code looks fine, at the first sight. But this concept doesn’t have any practical application in real project. I still keep the code for personal reason, but I simply drop the whole idea.
Finally
That is all with fetch
.
5: Axios Alternative
There is however other library.
A very popular one, the axios
library.
We can rewrite the above script as below:
// Getting a resource
const axios = require('axios').default;
const baseURL = 'https://jsonplaceholder.typicode.com'
const id = 1
const url = `${baseURL}/posts/${id}`
axios.get(url)
.then( response => console.log(response.data) )
.catch( error => console.log(error) )
Or if you wish for a more complete options
example:
// Updating a resource with PUT
const axios = require('axios').default;
const headers = { 'Content-type': 'application/json; charset=UTF-8' }
const baseURL = 'https://jsonplaceholder.typicode.com'
const id = 1
const url = `${baseURL}/posts/${id}`
const data = { id: 1, title: 'foo', body: 'bar', userId: 1 }
axios.put(url, data)
.then( response => console.log(response.data) )
.catch( error => console.log(error) )
With about the same result.
And The Rest …
There are already so many axios
tutorial.
So I do not think I have to pour them all in a blog either.
- Axios: Getting a resource
- Axios: Listing all resources
- Axios: Creating a resource
- Axios: Updating a resource with PUT
- Axios: Updating a resource with PATCH
- Axios: Deleting a resource
Which one?
Native asynchronous fetch() or Axios.
I must say: I do not know.
Conclusion
Fetching JSON is not as hard as thought. It is become easier now since you have already had a working example.
What do you think?