If you're short on time you can scroll to the bottom for the working code!
If you use JavaScript to make requests or fetch responses across the network, you can use .fetch()
. And when working with .fetch()
, you might need to read the body of a request or a response.
After all, that's where all the juicy data is!
I thought I'd be able to read the response body like this:
async function myFetchFunction() {
const response = await fetch("http://api.example.com/user/12345");
console.log(response.body.name); // undefined
}
But no! I got that dreaded undefined
. Or worse, an error "Cannot read properties of undefined (reading 'body')"
.
But that shouldn't be the case. I know the response sends { id: 12345, name: "Codemzy" }
back. So where is that body going?
Now, I usually use Axios as my JavaScript HTTP client, and I use Express in Node.js to accept HTTP requests, so I have to admit I may be a little late to the party on .fetch()
.
However, I recently created a function in Cloudflare to collect CSP violation reports, which needed to collect POST requests. And incoming requests to Cloudflare workers have a Request
object as the request
property.
And .fetch()
is natively available inside Cloudflare workers so I wanted to avoid installing external libraries and use .fetch()
to send requests in my function too.
But I ran into a few problems, and late last night I realised - I needed to learn .fetch()
!!
Here's what I discovered...
Reading from the response.body
doesn't work
In Axios, I would get the response body with response.data
.
axios.get('/user/12345')
.then(function (response) {
console.log(response.data.name); // "Codemzy"
});
In Express.js I can get the request body with request.body
.
And this is when I discovered that response.body
doesn't work in .fetch()
.
But when I read the Fetch API docs, I was kinda confused, because it looks like there's a response.body
, but when I tried to get the data it was undefined
!
async function myFetchFunction() {
const response = await fetch("http://api.example.com/user/12345");
console.log(response.body.name); // undefined
}
After some further digging (or in this case console.log()
-ing, my favourite type of bug fixing!), and reading the docs with a little more attention, I discovered that response.body
is a readable stream.
async function myFetchFunction() {
const response = await fetch("http://api.example.com/user/12345");
console.log(response.body); // ReadableStream
}
And what that means for you and me, is that we can't read the data from it. Not yet, anyway.
Convert the response with .json()
To read the response body, we need to convert it from a readable stream.
In this case, I need a javascript object, since the response I'm expecting is a JSON* object, and I want to parse it from JSON to a JavaScript object. So I'll use the .json()
method available in the response.
And while we are parsing the body, we are not touching the body
. As in, we don't call .json()
on the body.
This won't work:
let data = response.body.json();
We actually need to call the .json()
method on the response object to get the body.
let data = response.json();
This bit confuzzled me a little.
I thought, since we are parsing the response, everything would now be parsed in this data object - e.g. data.headers
, data.body
, etc. But it's not. Headers are still on the original response object (response.headers
) - it's just the body in data
(now as an object instead of a readable stream).
So now data
is the body. You could even call it body
if you want.
let body = response.json();
Just don't get it mixed up with response.body
- the readable stream!
*If your response isn't JSON, you can use other methods to read the response including .blob()
, .formData()
and .text()
.
Final code
async function myFetchFunction() {
const response = await fetch("http://api.example.com/user/12345");
let body = response.json(); // parse the response body
console.log(body.name); // "Codemzy"
}