I am using nodeJS and mongoDB.
I coded a function to get the biggest index inside my database to be able to use it when I post a new element in the database.
I created a function to insert a element in my database :
function postComparison(){
console.log("Titre : " + document.getElementById("Titre").value);
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
var raw = JSON.stringify({
"_id": getLastIndice()+1, // HERE
"id_User": "2",
"PseudoUser": "bob",
"Titre" : document.getElementById("Titre").value
});
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
};
fetch("http://localhost:3000/comparaison", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));}
</script>
I am calling the getLastIndice() function to increment the index from the last index I have in my database.
The function getLastIndice() call the router :
function getLastIndice(){
console.log("on appel la fonction get LastIndice");
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
fetch("http://localhost:3000/comparaison/getLastIndice", requestOptions)
.then(response => response.text())
//.then(result => document.getElementById("textRepBDD").innerHTML = result)
.catch(error => console.log('error', error));
console.log('appel de la fonction qui recupere le dernier indice avec comme réponse : ' + response );
return parseInt(response);
}
</script>
And inside the router I have :
comparaisonRouter.route('/getLastIndice')
.get( (req, res, next) => {
var MongoClient = require('mongodb').MongoClient;
var url = "mongodb://localhost:27017/";
MongoClient.connect(url, function(err, db) {
if (err) throw err;
var dbo = db.db("siteComparaisonMax");
//Sort the result by name:
var sort = { _id: -1 }; // - 1 Pour trier dans un ordre decroissant.
dbo.collection("comparaisons").find().sort(sort).limit(1).toArray(function(err, result) {
if (err) throw err;
//console.log(JSON.parse(result._id));
console.log(result[0]._id);
res.json(result[0]._id);
db.close();
});
});
})
The function get last index is working but when I'm calling the postComparaison() function it just calling the getLastIndice() and that's all.
I think that I'm missing a next() somewhere.
To return a value from a promise you use it's .then(). The .then() method is not there for you to log the result. It is there for postComparaison() to call to get the result:
function getLastIndice(){
console.log("on appel la fonction get LastIndice");
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
// IMPORTANT! The `return` below in front of the `fetch()` is
// what will return the value to the caller:
return fetch("http://localhost:3000/comparaison/getLastIndice", requestOptions)
.then(response => response.text())
.then(result => {
console.log('code INSIDE THIS THEN executes AFTER we get response : ' + result );
return parseInt(result);
})
.catch(error => console.log('error', error));
console.log('code OUTSIDE THE THEN ABOVE executes BEFORE we get response');
}
This is very important. Code at the bottom of the function executes FIRST. Code inside the then executes LAST.
To clarify let's remove all details from the function:
function getLastIndice() {
// THIS HAPPENS FIRST
console.log('first');
return fetch()
.then(response => response.text())
.then(result => {
// THIS HAPPENS THIRD
console.log('third');
return parseInt(result);
});
// THIS HAPPENS SECOND
console.log('second');
}
The above code will log:
first
second
third
But please note:
function getLastIndice() {
return fetch() // THIS RETURN IS IMPORTANT ! !
.then(r => r.json())
.then(r => parseInt(r));
}
So basically a clean version of getLastIndice() can be written simply as:
function getLastIndice() {
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
return fetch("http://localhost:3000/comparaison/getLastIndice", requestOptions)
.then(x => x.text())
.then(y => parseInt(y));
}
That's all the function needs to be. We don't really need (or want) to catch the error here because we want the caller to be able to detect the error so we simply remove the .catch().
Now that the function works properly here's how you use it in postComparison():
function postComparison() {
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
// Remember, .then() is how getLastIndice() RETURNS its value:
getLastIndice().then(indice => {
var raw = JSON.stringify({
"_id": indice+1, // WE GOT THIS FROM .then of getLastIndice()
"id_User": "2",
"PseudoUser": "bob",
"Titre" : document.getElementById("Titre").value
});
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
};
fetch("http://localhost:3000/comparaison", requestOptions)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
}
}
Alternatively you can return the variable raw and process the rest inside another .then(). That's the whole reason Promises were invented - to flatten callback nesting by chaining .then()s:
function postComparison() {
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
// Remember, .then() is how getLastIndice() RETURNS its value:
getLastIndice()
.then(indice => {
var raw = JSON.stringify({
"_id": indice+1,
"id_User": "2",
"PseudoUser": "bob",
"Titre" : document.getElementById("Titre").value
});
return raw; // return this to the `then` below:
})
.then(requestBody =>
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: requestBody, // requestBody comes from raw above
redirect: 'follow'
};
return fetch("http://localhost:3000/comparaison", requestOptions);
})
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
// ^^ NOTE: all the then() are on the same level!
}
If you want postComparison() to return the final result just do the same thing we did with getLastIndice() above: add the return keyword in front of getLastIndice():
function postComparison() {
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
return getLastIndice() // THIS RETURN IS IMPORTANT !
.then(indice => {
var raw = JSON.stringify({
"_id": indice+1,
"id_User": "2",
"PseudoUser": "bob",
"Titre" : document.getElementById("Titre").value
});
return raw;
})
.then(requestBody =>
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: requestBody, // requestBody comes from raw above
redirect: 'follow'
};
return fetch("http://localhost:3000/comparaison", requestOptions);
})
.then(response => response.text())
.then(result => {
console.log(result);
return result; // return the result into the chain of then()
// this return is ALSO IMPORTANT
})
.catch(error => console.log('error', error));
}
An Analogy:
Using Promises you ALWAYS have to remember that the code inside then() happens after the end of the function, not before! It actually makes sense if you think about it.
Say for example you are ordering a Pizza. What usually happens is something like this:
You call Restaurant() and order a pizza
pizzaDeliveryGuy(() => will send you the pizza)
You put down the phone
You wait for the pizzaDeliveryGuy()
Note that in the events above the pizzaDeliveryGuy() is the .then() and the Restaurant() makes a Prmoise to send you a pizza (using pizzaDeliveryGuy()). Even though the Restaurant() promised you to deliver the pizza BEFORE you put down the phone the send you the pizza event happens AFTER you put down the phone.
Your confusion is exactly this: not understanding that a promise happens in the future. The pizza delivery guy will send you the pizza after you make the order even though the restaurant promised you the pizza before you put down the phone. Similarly, Promises will execute the .then() callback after the end of the function, not before.
Modern code with async / await:
ECMAScript version 6 introduced two keywords that helps simplify handling asynchrnous code: async and await. They build upon Promise and is basically a special syntax for inlining promises.
Basically if you write something like this:
async function x () {
let y = await z();
console.log(y);
}
Javascript will compile it to something like this:
function x () {
z().then(y => console.log(y));
}
Using async/await we can simplify your code. First we can rewrite getLastIndice() like this:
async function getLastIndice() {
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
let response = await fetch("http://localhost:3000/comparaison/getLastIndice", requestOptions);
let txt = await response.text();
return parseInt(txt);
}
Because we are using await everything in the function above happens in sequence from top to bottom:
async function getLastIndice() {
// THIS HAPPENS FIRST
var requestOptions = {
method: 'GET',
redirect: 'follow'
};
// THIS HAPPENS SECOND
let response = await fetch(URL, requestOptions);
// THIS HAPPENS THIRD
let txt = await response.text();
// THIS HAPPENS LAST
return parseInt(txt);
}
Now we can rewrite postComparison() like this:
async function postComparison() {
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/json");
try {
let indice = await getLastIndice();
var raw = JSON.stringify({
"_id": indice+1,
"id_User": "2",
"PseudoUser": "bob",
"Titre" : document.getElementById("Titre").value
});
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw
redirect: 'follow'
};
let response = await fetch("http://localhost:3000/comparaison", requestOptions);
let result = await response.text())
console.log(result);
return result;
}
catch(error) {
console.log('error', error);
}
}
As you can see, async/await is much easier to read for simple sequential code like this. Just don't forget to await the results.
Still, even if you use async/await I think it's critical to understand how Promises work and how .then() work because async/await was built on top of promises. There are some situations where you want two or more things to run in parallel instead of sequentially and that is when you need to go back to promises and callbacks.
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 found the aproach how to put the async function into a router endpoint and tried to implement it but not suceed. E.g. this link zellwk.com
The server sends only empty string instead of a huge string that is on snippet.host.
async function downloadData(url) {
const options = {
'method': 'GET', 'url': url, 'headers': {}, formData: {}
};
let result = '';
await request(options, function (error, response) {
if (error) throw new Error(error);
console.log(response.body)
result = response.body
});
return {error: 0, text: result}
}
app.get('/token/:token', jsonParser, async function (req, res) {
console.log(req.params.token) //Don't mind this token
try {
let message = await downloadData('https://snippet.host/xgsh/raw')
res.send(message)
} catch (e) {
console.log(e);
res.sendStatus(500);
}
});
How to make it wait for the download?
EDIT
I implemented node-fetch and got this:
{"error":0,"text":{"size":0}}
What did I wrong?
async function downloadData(url) {
const result = await fetch(url)
return {error: 0, text: result}
}
app.get('/token/:token', jsonParser, async function (req, res) {
console.log(req.params.token)
try {
let message = await downloadData('https://snippet.host/xgsh/raw')
res.send(message)
} catch (e) {
console.log(e);
res.sendStatus(500);
}
});
in case of fetch call the response body need to be parsed first which you are missing.
async function downloadData(url) {
const res = await fetch(url)
if (res && res.status != 200)
throw new Error("status code other than 200 received")
let json = await res.json()
return {error: 0, text: json}
}
app.get('/token/:token', async function (req, res) {
console.log(req.params.token)
try {
let message = await downloadData('https://snippet.host/xgsh/raw')
// let json = await message.json()
res.send(message)
} catch (e) {
console.log(e);
res.sendStatus(500);
}
});
i tried calling the api but every time it is returning 404 . so json cant be called in that case. but if 200 is returned then you call json and return that accordingly.
You can only usefully await a promise.
request doesn't return a promise, it takes a callback function instead.
This is one of the reasons that request was deprecated two years ago.
Replace request with something which supports promises such as node-fetch, the native fetch that was added to Node.js recently, or axios.
In case you must use request (eventhough it's deprecated); or in case you find a similar situation with a old function that doesn't return a promise; you can turn your request into a function that returns a promise.
Either write your own wrapper, in the form of
function requestPromise(options) {
return new Promise(
(resolve,reject) => request(options,
(err,response) => if (err) reject(err) else resolve(response)));
}
or, given that request's callback is in the form of function(err,response), directly use util.promisify
async function xxxx(...) {
try {
...
let response = await util.promisify(request)(options);
...
}
Because request isn't actually an async function that returns a Promise, you can create your own async version of request.
function asyncRequest(options) {
return new Promise((resolve, reject) => {
request(options, (err, response) => {
if (err) {
reject(err)
} else {
resolve(response)
}
})
})
}
Then in your project rewrite your downloadData function to be:
async function downloadData(url) {
const options = {
method: "GET",
url: url,
headers: {},
formData: {},
}
const result = await asyncRequest(options)
return { error: 0, text: result.body }
}
I am tracing my way through an application i am building. I have numbered steps to figure out what is getting called and what is not.
I call Axios and try to call .then() to get the data from the promise that is returned, but it is never executed. the application just keeps on going:
module.exports = async function request(requestOpts = {}) {
console.log("5: in request... sending request")
const response = await sendRequest({
method: requestOpts.method,
url: requestOpts.url,
headers: requestOpts.headers,
}).then(function(response) {
console.log("7: in request.. returning response")
return response;
})
};
function sendRequest(requestOpts = {}) {
console.log("6: in sendRequest.. returning axios")
return (
axios({
method: requestOpts.method,
url: requestOpts.url,
headers: requestOpts.headers,
}).then((response) => {
console.log("Then ===> " + response)
return response;
}).catch((error) => {
console.log("Error ==> " + error)
return error;
})
);
}
Step 7 never shows up.. things just move on and complete. Have I got this set up correctly? Here is the calling module:
module.exports = async function axiosCaller() {
console.log('4: in axiosCaller')
const authHeader = buildBasicAuth();
let response = await request({
url: 'redacted',
method: 'get',
headers: {
authHeader
},
}).then((response) => {
console.log(response);
return handleResponse(response);
});
}
I think you should not return the async response in .then instead you need to return axios response as promise as
async sendRequest() {
return await axios(....)
}
and while calling sendRequest function you need to mention await infront of that or simply use .then to capture the response
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 need to transform this code into clean code that uses callbacks, because this code does not allow me to use body information elsewhere.
const endpoints = [];
function getDevicesFromPartnerCloud() {
var options = {
method: 'GET',
url: 'https://database-dcda.restdb.io/rest/endpoints',
headers: {
'cache-control': 'no-cache',
'x-apikey': '*****************************'
}
};
request(options, function (error, response, body) {
var data = JSON.parse(body);
data.forEach(function(data, index) {
let endpoint = createSceneEndpoint(data._id, data.name);
endpoints.push(endpoint);
});
});
return endpoints;
}
I think the cleanest way to do it would be to use a Promise to handle the asynchronous request. One of the most important things to remember is that functions should ideally do only one thing. That way, they are easier to test, reason about, and refactor. I would pull the code that actually makes the request into a separate function and have it return the body, then have your getDevicesFromPartnerCloud call that new function, get the data back, and process it however it wants. Most importantly, this "frees" the data from being stuck in the request callback, because you're wrapping it in a promise, and resolving it when the data is available.
Something like:
const endpoints = [];
function requestDevices() {
return new Promise(function(resolve, reject) {
const options = {
method: 'GET',
url: 'https://database-dcda.restdb.io/rest/endpoints',
headers: {
'cache-control': 'no-cache',
'x-apikey': '*****************************',
},
};
request(options, function(error, response, body) {
if (error) {
reject(error)
}
resolve({ response: response, body: body });
});
});
}
async function getDevicesFromPartnerCloud() {
const devicesResponse = await requestDevices();
const data = JSON.parse(devicesResponse.body);
data.forEach(function(data, index) {
const endpoint = createSceneEndpoint(data._id, data.name);
endpoints.push(endpoint);
});
// Do whatever else you need with devicesResponse.body
return endpoints;
}
If you wanted to go more of an es6 direction, maybe something like
let endpoints;
const requestDevices = () =>
new Promise((resolve, reject) => {
request(
{
method: 'GET',
url: 'https://database-dcda.restdb.io/rest/endpoints',
headers: {
'cache-control': 'no-cache',
'x-apikey': '*****************************',
},
},
(error, response, body) => (error ? reject(error) : resolve(body)),
);
});
const getDevicesFromPartnerCloud = async () => {
try {
const body = await requestDevices();
const data = JSON.parse(body);
endpoints = data.map(({ _id, name }) =>
createSceneEndpoint(_id, name),
);
// Do whatever else you need with devicesResponse.body
// doStuff(body)
return endpoints;
} catch (e) {
console.error(e);
}
};