I have an application where I first make a post request to an endpoint, let say;
ENDPOINT A
I have another endpoint where I make a GET HTTP request, let say;
ENDPOINT B
The issue now is how do I get the current data I posted to ENDPOINT A, from ENDPOINT B without refreshing the page.
Note: Every thing is working fine just that I need to refresh the page to see the current data I posted, which doesn't make it a reactive application.
Here is part of the code
//Get user requests
useEffect(() => {
fetch('http://localhost:3002/request/me', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': JSON.parse(localStorage.getItem("Some"))
}
}).then(function (res) {
// console.log(res)
return res.json()
}).then(function (datas) {
setState({ datas: datas })
console.log(datas)
}).catch(function (e) {
console.log(e)
})
}, [])
//Create request
const onAdd = (data) => {
fetch('http://localhost:3002/request', {
method: 'POST',
headers: {
'Authorization': JSON.parse(localStorage.getItem("Some"))
},
body: data
}).then(function (res) {
// console.log(res)
return res.json()
}).then(function (data) {
setPost({datas: data})
console.log(data)
}).catch(function (e) {
console.log(e)
})
}
You'll probably need to poll the server on an interval. fetch requests you only get back one response, if the data changes later you won't get any update for that update, meaning you'll need to repeat the request manually.
There are different strategies, probably the most simple one is to poll on an interval:
useEffect(() => {
const performRequest = () => fetch('http://localhost:3002/request/me', ...)
.then(...)
const token = setInterval(performRequest, 5000) // Every 5 seconds?
performRequest(); // Initial request
return () => {
// Don't forget to cleanup the interval when this effect is cleaned up.
clearInterval(token)
}
}, []);
But you can do more fancy stuff if you want more control. You could write this logic in a custom hook with the following API:
const { result, refresh } = useRequestMe()
// When needed, call refresh(). e.g. after a succesful POST request.
const onAdd = (data) => {
fetch('http://localhost:3002/request', ...)
.then(function (res) {
// console.log(res)
return res.json()
}).then(function (data) {
setPost({datas: data})
refresh();
console.log(data)
}).catch(function (e) {
console.log(e)
})
}
Related
I'm having these calls to list users with groups from google active directory
let globalGroups = null;
let groupMembers = null;
await GetCustomerId(req, res).then( async () => {
// GetGroups is async function saves groups in `globalGroups` variable
await GetGroups(token).then( () => {
globalGroups.forEach( async (group) => {
// GetGroupMembers is async function saves users in `groupMembers` variable
await GetGroupMembers(group.id, token).then( () => {
groupMembers.forEach( (member) => {
// here I log the `member` and have no issues here
if (usersIds.includes(member.id)) {
let user = users.find( ({ id }) => id === member.id );
user.group_ids.push(group.id);
}
else {
member.group_ids = [];
member.group_ids.push(group.id);
users.push(member);
usersIds.push(member.id);
}
})
})
});
// the issue is here without timeout it returns an empty array because it doesn't wait for the loop to finish
console.log(users);
res.status(200).json({"users": users}).send();
}).catch(function(err) {
console.log(err)
res.status(500).json({"error": err}).send();
});
});
This returns an empty array unless I use timeout to return the response like this
setTimeout( () => {
console.log(users);
res.status(200).json({"users": users, "next_page_link": "notFound"}).send();
}, 1000);
How to make it wait till the whole loop ends to return the response without using timeout?
const GetCustomerId = async (req, res, next) => {
try {
let authorization = req.headers['authorization'].split(' ');
if (authorization[0] !== 'Bearer') {
return res.status(401).send();
} else {
await axios({
url: 'https://admin.googleapis.com/admin/directory/v1/users?domain=&maxResults=1',
method: 'get',
headers: {
'Content-Type': "application/json",
'Authorization': ' Bearer ' + authorization[1]
},
})
.then((response) => {
globalCustomerId = response.data.users[0].customerId
})
.catch(function(err) {
console.log(err);
});
}
} catch (err) {
console.log(err);
}
}
const GetGroups = async (token) => {
try {
await axios({
url: 'https://admin.googleapis.com/admin/directory/v1/groups?customer=' + globalCustomerId,
method: 'get',
headers: {
'Content-Type': "application/json",
'Authorization': ' Bearer ' + token
},
})
.then((response) => {
globalGroups = response.data.groups;
})
.catch(function (err) {
return res.status(500).json({"error": err}).send();
});
} catch (err) {
return res.status(403).json(err).send();
}
}
const GetGroupMembers = async (groupId, token) => {
await axios({
url: "https://admin.googleapis.com/admin/directory/v1/groups/" + groupId + "/members",
method: 'get',
headers: {
'Content-Type': "application/json",
'Authorization': ' Bearer ' + token
},
})
.then((response) => {
groupMembers = null;
groupMembers = response.data.members;
})
.catch(function (err) {
return res.status(500).json({"error": err}).send();
});
}
globalGroups.forEach( async (group) => {
An async method inside .forEach doesn't actually do what you might want it to do.
By essentially doing array.forEach(async method) you're invoking a bunch of async calls, 1 per element in the array. It's not actually processing each call one by one and then finally resolving.
Switch to using a regular for loop with await inside it and it will do what you want.
eg.
for (const group of globalGroups) {
await GetGroupMembers(group.id, token)
groupMembers.forEach.....
}
You could do that to force your code to be more synchronous (or use something like Promise.all to be more efficient while still being synchronous) but another issue with the code is you're stuck in callback hell, which leads to less-readable code.
I'd highly recommend refactoring your Get* methods such that they return the values you need. Then you can do something cleaner and predictable/deterministic like:
const globalCustomerId = await GetCustomerId(req, res);
const globalGroups = await GetGroups(token); //note: Promise.all could help here
for (const group of globalGroups) {
const groupMembers = await GetGroupMembers(group.id, token)
groupMembers.forEach.....
}
console.log(users);
res.status(200).json({"users": users}).send();
You can wrap it in a try/catch to take care of error handling. This leads to much cleaner, more concise, and more predictable order of executions.
I made an api with a database that stores books and I can get the book on the front-end like this
async function getBooks() {
try {
const response = await fetch("https://node-api-with-books.herokuapp.com/books");
return await response.json();
// console.log(books)
} catch (error) {
console.log("Error", error);
}
}
getBooks().then(book => {
console.log(book);
});
But I want to figure at how to add a book to the api
If I am getting this right and assuming that you configured you Api to accept Post requests then all you have to do is just send a post request to the backend API
async function addBook(bookData) {
try {
const response = await fetch("https://node-api-with-books.herokuapp.com/books", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(bookData),
});
return response.json();
} catch (error) {
console.log("Error", error);
}
}
and on the back end, you will parse the request with your middleware and then you can get the data from req.body
resource
I am posting a large amount of data to an API that uses a token that needs to be refreshed every 30 minutes.
How would I pause my script when the token expires, refresh the token and then resume from when we left off?
I have so far:
const post = await mergedItems.forEach((item, index) => {
return new Promise((resolve, reject) => {
const postBody = `{ "ItemShops": { "ItemShop": [{ "itemShopID": ${item.itemShopID}, "qoh": ${item.qoh} }]}}`
setTimeout(() => {
axios({
url: `${lightspeedApi}/Account/${accountID}/Item/${item.id}.json`,
method: 'put',
headers: authHeader,
data: postBody
}).then(response => {
if (response.status == 401) {
refreshToken()
} else {
console.log(response.data) }
}).catch(err => console.error(err))
}, index * 10000)
})
})
}}
This is quite a common problem, and parallels (ha!) that described here. You're starting multiple requests in parallel, and can expect that as soon as one receives a 401 response the others will get the same shortly thereafter. This means they all start to try to refresh the token, also in parallel.
A rough sketch of how to apply the solution follows:
const makeRequest = async (opts) => {
try {
return axios(opts);
catch (err) {
// note axios throws on >=400 response code, not like fetch
if (err.response && err.response.status === 401) {
await refreshToken();
return makeRequest(opts);
else {
raise err;
}
}
}
Now you can expose a promise of the token getting refreshed, and reuse it to avoid making multiple requests while one is already in-flight:
let refreshPromise;
export const refreshToken = () => {
if (!refreshPromise) {
// start a refresh request only if one isn't in flight
refreshPromise = axios(...).then((res) => {
// ... reset token
refreshPromise = null;
});
}
return refreshPromise;
};
The chain I have is retrieving information from a Sql Server and then setting up the data to then be sent to an api via post. The problem is i receive this RequestError message, **"TypeError [ERR_INVALID_HTTP_TOKEN]: Header name must be a valid HTTP token ["key"]"
Objective
-Retrieve Data using id
-Format data
-Send new, separate request to an api with formatted data
-Resolve results returned from api call
router.js
router.get('/request/:id', controller.data_post)
module.exports = router
Controller.js
exports.data_post = function(req, res) {
...
RetrieveData( req.id ) //retrieves data
.then( results => { //format data
var obj = formatData(results);
let body = [];
body.push(obj);
return body //resolve formatted data
} //End Of Promise
})
.then( body => { //Send new, separate request to an api with formatted data
var options = :{
method: 'POST',
uri: 'url',
headers: {
'key':'value',
'Content-Type': 'application/json'
},
body: JSON.stringify(body),
json:true
}
return option
})
.then( results => {
//send results
})
.catch( error => {
//error routine
})
}
RetrieveData.js
function RetrieveData( id ){
const promise = new Promise((resolve, reject) => {
...
resolve(data)
}
return promise;
}
RequestUtility.js
const request = require('request-promise')
function requestutility(options) {
return request(options)
.then( response => {
return response;
})
.catch( error => {
return error;
})
}
Current Error
"name": "RequestError",
message": "TypeError [ERR_INVALID_HTTP_TOKEN]: Header name must be a valid HTTP token ["key"]",
options: Object{},
callback: function RP$callback(err, response, body) {
arguments:TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
caller:TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
}
Couple of problems I see here
You don't need to return Promise.resolve and Promise.reject in the request utility method. Since request promise returns a promise, your promise will be resolved when succesfull and rejected when error. So you can get rid of requestutility alltogether.
You are wrapping results in new Promise which is not required.
resolve( requestutility(option)) doesn't work the way you are expecting it to work as it will resolve to a promise instead of value.
Remove the key from headers.
I have tried to update the code. It should look like
const request = require("request-promise");
RetrieveData(id)
.then(results => {
const obj = formatData(results);
const body = [];
body.push(obj);
return body;
})
.then(body => {
const options = {
"method": "POST",
"uri": "url",
"headers": {
"key": "value",
"Content-Type": "application/json"
},
"body": JSON.stringify(body),
"json": true
};
return request(options);
})
.then(results => {
// send results
})
.catch(error => {
// error routine
});
I am extremely new to Node, I hope someone can help.
I have 2 APIs that are external to my project.
I want to create a single endpoint in my Node Express server that returns the results from both calls.
How would I do this?
So far I figured out how to call an external resource like this,
app.use('/myapi', function(req, res) {
var url = 'https://some-service.com/api1';
res.setHeader('content-type', 'application/json');
req.pipe(request(url)).pipe(res);
});
This will return the results to my application. What I cant figure out is how to make a second call, then combine the results into an object like this,
{ response1, response2 }
Does anyone know how to do this, or point me in the right direction?
Also is there a way to make sure that both calls succeed before sending a response to my application.
Thanks.
You have to use promise a functionality like this, as the external calls can take their own sweet time. You can wait for the calls to get complete and send the final response.
You can update your route to something like below :
app.use('/myapi', function(req, res) {
var url = 'https://some-service.com/api1';
res.setHeader('content-type', 'application/json');
var responseOne = await CallFirstSystem();
var responseTwo = await CallSecondSystem();
var finalResponse = doSomeLogic(responseOne,responseTwo);
res.send(finalResponse);
});
Create separate functions to call and handle the response
function CallFirstSystem() {
return new Promise(async function (resolve, reject) {
try {
var url = "http://firssytem/api";
var updateResult = [];
await axios.get(url, JSON.stringify(requestPayLoad), {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
}).then(function (response) {
updateResult = innerLogicOne(response);
})
.catch(function (error) {
console.log(error);
});
resolve(updateResult);
} catch (err) {
console.log(err)
} finally {}
}).catch((err) => {
console.log(err)
});
}
function CallSecondSystem() {
return new Promise(async function (resolve, reject) {
try {
var url = "http://secondSystem/api";
var updateResult = [];
await axios.get(url, JSON.stringify(requestPayLoad), {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
}).then(function (response) {
updateResult = innerLogicOne(response);
})
.catch(function (error) {
console.log(error);
});
resolve(updateResult);
} catch (err) {
console.log(err)
} finally {}
}).catch((err) => {
console.log(err)
});
}
I am using axios to call API, you are free to use any other library to call the external system API.
Hope this helps.