How to resolve the converting circular structure to JSON issue in fetch - javascript

I want to get the list of the repositories by providing a username.
Below is what I have done so far.
router.get('/github/:username', (req, res) => {
try {
const url = `https://api.github.com/users/${req.params.username}/repos?per_page=5&sort=created:asc&client_id=${config.get('githubClientId')}&clientSecret=${config.get('githubSecret')}`;
const headers = {
"Content-Type": "application/x-www-form-urlencoded",
};
console.log(url);
fetch(url, {
method: 'GET',
headers: headers,
}).then(data => {
if (data.status !== 200) {
return res.status(404).send({
msg: 'No GitHub profile found'
});
} else {
return data.json();
}
}).then(result => res.json(result));
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error');
}
})
When I use the dynamically created URL in the browser, I get the response
When I pass valid user-name, I get the repositories in Postman, where I am testing the API
When I pass invalid user-name, I get the following error
(node:18684) UnhandledPromiseRejectionWarning: TypeError: Converting circular structure to JSON
at JSON.stringify (<anonymous>)
at stringify (E:\Connector\node_modules\express\lib\response.js:1123:12)
at ServerResponse.json (E:\Connector\node_modules\express\lib\response.js:260:14)
at fetch.then.then.result (E:\Connector\routes\api\profile.js:396:31)
at process._tickCallback (internal/process/next_tick.js:68:7)
(node:18684) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
Can anybody tell me how to resolve this error? I have looked at many resources but could not find any concrete.

The problem is the return res.status(404).send(…) in the first then callback. The second then callback will then try to res.json(result) that return value.
You should instead write
router.get('/github/:username', (req, res) => {
const url = `https://api.github.com/users/${req.params.username}/repos?per_page=5&sort=created:asc&client_id=${config.get('githubClientId')}&clientSecret=${config.get('githubSecret')}`;
const headers = {
"Content-Type": "application/x-www-form-urlencoded",
};
console.log(url);
fetch(url, {
method: 'GET',
headers: headers,
}).then(data => {
if (data.status !== 200) {
res.status(404).send({
msg: 'No GitHub profile found'
});
} else {
return data.json().then(result => {
res.json(result);
});
}
}).catch(err => {
console.error(err.message);
res.status(500).send('Server Error');
});
})

Related

Http request error using firebase functions/nodejs

Ive been trying unsuccessfully to get an http request to the Genius API server running through my cloud functions node.js instance. I am currently stuck getting this 'Server Error' message and am not sure how else to move around this. I am new to http requests, so was wondering if there were any glaring mistakes im making in this request? Or possible ideas for how to get more useful information from the error console
My console log currently outputs:
(node:43068) UnhandledPromiseRejectionWarning: Error: Server error.
at /Users/xxx/Documents/GitHub/xxx/functions/index.js:58:21
at processTicksAndRejections (internal/process/task_queues.js:97:5)
(node:43068) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1))
const functions = require('firebase-functions');
const express = require('express')
const app = express()
const port = 3035
var http = require('http');
const https = require('https');
const fetch = require('node-fetch');
app.get('/auth', (req, res) => {
const accessToken ='xxx';
const clientId = 'xxx';
const clientSecret = 'xxx';
const uri = `https://api.genius.com/oauth/authorize`;
const options = {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({
"code": "CODE_FROM_REDIRECT",
"client_id": clientId,
"client_secret": clientSecret,
"redirect_uri": "YOUR_REDIRECT_URI",
"response_type": "code",
"grant_type": "authorization_code"
}),
};
return fetch(uri, options).then((res) => {
if (res.ok) {
return res.json();
} else if (res.status == 409) {
throw new Error('IdP configuration already exists. Update it instead.');
} else {
throw new Error('Server error.');
}
});
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
})
In your then handler you are throwing exceptions that are not catched anywhere (at least not in the code you are showing us). Thus, you get an UnhandledPromiseRejectionWarning. And as this is all inside an express routehandler, you are not returning anything to your client calling this route (not even if the authenication call is successful)
app.get('/auth', (reqest, response) => {
const accessToken ='xxx';
const clientId = 'xxx';
const clientSecret = 'xxx';
const uri = `https://api.genius.com/oauth/authorize`;
const options = {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`
},
body: JSON.stringify({
"code": "CODE_FROM_REDIRECT",
"client_id": clientId,
"client_secret": clientSecret,
"redirect_uri": "YOUR_REDIRECT_URI",
"response_type": "code",
"grant_type": "authorization_code"
}),
};
fetch(uri, options).then((res) => {
if (res.ok) {
return res.json();
} else if (res.status == 409) {
throw new Error('IdP configuration already exists. Update it instead.');
} else {
throw new Error('Server error.');
}
})
.then(json => {
response.send(json); // will send status 200 and the json as body
})
.catch(e => {
console.log(e);
response.sendStatus(400); //or whatever status code you want to return
});
})
Often you may need to do some tweaks to the data before it's passed to the consumer, for example:
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(response.statusText)
}
return response.json()
})
.then(data => {
return data.data
})
.catch((error: Error) => {
throw error
})

Why is it not waiting for an awaited Promise.all to resolve?

I previously had working code that inefficiently called awaits on every element of an iterable. I'm in the process of refactoring to use Promise.All. However, my code is not waiting for the Promise.All to resolve before executive further code.
Specficially, the purgeRequestPromises line executes before the initial Promise.All resolves. I'm not sure why that is? retrieveSurrogateKey is an async function, so its return line will be wrapped in a resolved promise.
try {
//retrieve surrogate key associated with each URL/file updated in push to S3
const surrogateKeyPromises = urlArray.map(url => this.retrieveSurrogateKey(url));
const surrogateKeyArray = await Promise.all(surrogateKeyPromises).catch(console.log);
//purge each surrogate key
const purgeRequestPromises = surrogateKeyArray.map(surrogateKey => this.requestPurgeOfSurrogateKey(surrogateKey));
await Promise.all(purgeRequestPromises);
// GET request the URLs to warm cache for our users
const warmCachePromises = urlArray.map(url => this.warmCache(url));
await Promise.all(warmCachePromises)
} catch (error) {
logger.save(`${'(prod)'.padEnd(15)}error in purge cache: ${error}`);
throw error
}
async retrieveSurrogateKey(url) {
try {
axios({
method: 'HEAD',
url: url,
headers: headers,
}).then(response => {
console.log("this is the response status: ", response.status)
if (response.status === 200) {
console.log("this is the surrogate key!! ", response.headers['surrogate-key'])
return response.headers['surrogate-key'];
}
});
} catch (error) {
logger.save(`${'(prod)'.padEnd(15)}error in retrieveSurrogateKey: ${error}`);
throw error
}
}
I know that the purgeRequestPromises executes early, because I get errors complaining that I've set my Surrogate-Key header as undefined in my HEAD request:
async requestPurgeOfSurrogateKey(surrogateKey) {
headers['Surrogate-Key'] = surrogateKey
try {
axios({
method: `POST`,
url: `https://api.fastly.com/service/${fastlyServiceId}/purge/${surrogateKey}`,
path: `/service/${fastlyServiceId}/purge${surrogateKey}`,
headers: headers,
})
.then(response => {
console.log("the status code for purging!! ", response.status)
if (response.status === 200) {
return true
}
});
} catch (error) {
logger.save(`${'(prod)'.padEnd(15)}error in requestPurgeOfSurrogateKey: ${error}`);
throw error;
}
}
retrieveSurrogateKey is synchronously returning undefined: the value in the try block is a promise and no errors are thrown synchronously, so the catch clause is never executed and execution falls out the bottom, returning undefined from the function body.
You could try something like:
function retrieveSurrogateKey(url) { // returns a promise
return axios({
// ^^^^^^
method: 'HEAD',
url: url,
headers: headers,
}).then(response => {
console.log("this is the response status: ", response.status)
if (response.status === 200) {
console.log("this is the surrogate key!! ", response.headers['surrogate-key'])
return response.headers['surrogate-key'];
}
}).catch(error => {
logger.save(`${'(prod)'.padEnd(15)}error in retrieveSurrogateKey: ${error}`);
throw error;
});
}
Note that it is superfluous to declare a function returning a promise as async if it doesn't use await. There is also a secondary problem in this line:
const surrogateKeyArray = await Promise.all(surrogateKeyPromises).catch(console.log);
The catch clause will will fulfill the promise chain unless the error is rethrown. You could (perhaps) leave off the .catch clause or recode it as
.catch( err=> { console.log(err); throw err} );
You don't have to remove async from retrieveSurrogateKey() in order for it to work. In fact it's more readable if you don't. As was already explained, the problem is that the promise returned by retrieveSurrogateKey() does not follow the completion of the promise returned by the call to axios(). You need to await it:
async retrieveSurrogateKey(url) {
try {
const response = await axios({
method: 'HEAD',
url,
headers,
});
console.log('this is the response status: ', response.status);
if (response.status === 200) {
const surrogateKey = response.headers['surrogate-key'];
console.log('this is the surrogate key!! ', surrogateKey);
return surrogateKey;
}
} catch (error) {
logger.save(`${'(prod)'.padEnd(15)}error in retrieveSurrogateKey: ${error}`);
throw error;
}
}
This preserves the same logic you currently have, but you'll notice that when response.status !== 200, you end up with a resolved promise of undefined, rather than a rejected promise. You might want to use validateStatus to assert the exact status of 200. By default axios resolves any response with a status >= 200 and < 300:
async retrieveSurrogateKey(url) {
try {
const response = await axios({
method: 'HEAD',
url,
headers,
validateStatus(status) {
return status === 200;
}
});
const surrogateKey = response.headers['surrogate-key'];
console.log('this is the surrogate key!! ', surrogateKey);
return surrogateKey;
} catch (error) {
logger.save(`${'(prod)'.padEnd(15)}error in retrieveSurrogateKey: ${error}`);
throw error;
}
}
This way, you're always guaranteed a surrogate key, or a rejected promise.

DELETE with Express.JS

I am trying to get my DELETE method to work. When the function is ran it is going straight into the throw Error line. In the console it is printing the following two errors. “404 Not Found” “Error Uncaught (in promise) Error"
Here is my client side code
async function deleteItem(item) {
let requestOptions = {
method: "DELETE",
headers: { "Content-Type": "application/json" },
}
const response = await fetch("/delete/:id", requestOptions);
if (response.status != 204) {
throw Error("Cannot delete your item from list");
}
return item;
}
And server side code
app.delete("/delete/:id"),
async (request, res) => {
try {
await Item.deleteOne({ _id: request.params.id });
res.sendStatus(204);
} catch {
res.sendStatus(404);
console.log('test');
}
};
You need to pass the ID of the "thing" that you want to delete in the client side code.
async function deleteItem(item) {
let requestOptions = {
method: "DELETE",
headers: { "Content-Type": "application/json" },
}
const response = await fetch("/delete/:id", requestOptions); // <----- HERE!
if (response.status != 204) {
throw Error("Cannot delete your item from list");
}
return item;
}
it should be something like (assuming that the item object has the id)
const response = await fetch(`/delete/${ item.id }`, requestOptions);
e.g.: /delete/12423
You aren't catching the promise returned from your fetch() function causing it to throw an error. Also, it looks like you aren't sending the request with a valid id.
You could fix that by doing
fetch(...).then((response) => {
if (response.status != 204) {
throw Error("Cannot delete your item from list");
}
}).catch((err) => {
console.error(err); // handle error
});
or
const response = await fetch(...).catch((err) => {
console.error(err); // handle error
});
// if the promise is rejected and the catch block is executed then 'response' will be undefined
if (!response) return;
if (response.status != 204) {
throw Error("Cannot delete your item from list");
}
edit: or of course you could not catch the promise rejection and just send the request with a valid id

How to wrap JavaScript fetch in a function - unhandled promise rejection

I'm trying to write a wrapper function for the JavaScript fetch command.
I took the example code from this post:
function fetchAPI(url, data, method = 'POST') {
const headers = {
'Authorization': `Token ${getAuthToken()}`,
};
return fetch(url, { headers, 'method': method, 'body': data })
.then(response => {
if (response.ok) {
const contentType = response.headers.get('Content-Type') || '';
if (contentType.includes('application/json')) {
return response.json().catch(error => {
return Promise.reject(new Error('Invalid JSON: ' + error.message));
});
}
if (contentType.includes('text/html')) {
return response.text().then(html => {
return {
'page_type': 'generic',
'html': html
};
}).catch(error => {
return Promise.reject(new Error('HTML error: ' + error.message));
});
}
return Promise.reject(new Error('Invalid content type: ' + contentType));
}
if (response.status === 404) {
return Promise.reject(new Error('Page not found: ' + url));
}
return response.json().then(res => {
// if the response is ok but the server rejected the request, e.g. because of a wrong password, we want to display the reason
// the information is contained in the json()
// there may be more than one error
let errors = [];
Object.keys(res).forEach((key) => {
errors.push(`${key}: ${res[key]}`);
});
return Promise.reject(new Error(errors)
);
});
}).catch(error => {
return Promise.reject(new Error(error.message));
});
};
And I'm calling it like this:
fetchAPI('/api/v1/rest-auth/password/change/', formData).then(response => {
console.log('response ', response);
});
Edit: I have modified the code to display information returned by the server if the request is ok but refused, for example because of an invalid password. You have to interrogate the response json if ok == false.
A valid URL fetch is fine. But if there is an error, I see an Unhandled Rejection (Error): error message.
Why is it that the rejects are unhandled even though they are in catch blocks? What's the secret sauce here?
The way to avoid an unhandled promise rejection, is to handle it:
fetchAPI('/api/v1/rest-auth/password/change/', formData).then(response => {
console.log('response ', response);
}).catch(error => {
// do something meaningful here.
});;

Handling non JSON response with a Body.json() promise

I'm trying to create a scheme to intercept and handle requests from an API middleware, however, for whatever reason I'm unable to properly handle non JSON responses from my API endpoint. The following snippet works just fine for server responses formatted in JSON however say an user has an invalid token, the server returns a simple Unauthorized Access response that I'm unable to handle even though I am supplying an error callback to the json() promise. The Unauthorized Access response message is lost in the following scheme.
const callAPI = () => { fetch('http://127.0.0.1:5000/auth/', {
method: 'GET',
headers: {
'credentials': 'include',
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Basic bXlKaGJHY2lPaUpJVXpJMU5pSXNJbVY0Y0NJNk1UUTVPRE15TVRNeU5pd2lhV0YwSWpveE5EazRNak0wT1RJMmZRLmV5SnBaQ0k2TVgwLllFdWdKNF9YM0NlWlcyR2l0SGtOZGdTNkpsRDhyRE9vZ2lkNGVvaVhiMEU6'
}
});
};
return callAPI().then(res => {
return res.json().then(responseJSON => {
if(responseJSON.status === 200){
return dispatch({
type: type[1],
data: responseJSON,
message: success
});
} else if(responseJSON.status === 401) {
return dispatch({
type: type[2],
message: responseJSON.message
});
}
return Promise.resolve(json);
}, (err) => {
console.log(err.toString(), ' an error occured');
});
}, err => {
console.log('An error occured. Please try again.');
});
Try using text method of Body: res.text().
Try to wrap your response handling code in a try...catch block like this:
return callAPI().then(res => {
try {
return res.json().then(responseJSON => {
[...]
catch(e) {
console.error(e);
}
});
Body.json() throws when the body is actually not JSON. Therefore, you should check if the body contains JSON before you call json() on it. See https://developer.mozilla.org/en-US/docs/Web/API/Response.

Categories