strange behaviour of promise looping indefinitely - javascript

Having this script:
const got = require("got");
const getStream = require("get-stream");
const app = require("express")();
async function httpGet() {
console.log("getting response");
let targetUrl = "http://localhost:3000/api";
let gotOptions = {
method: "get",
headers: { "content-type": "application/json" },
body: undefined,
retries: 0
};
let response = await new Promise(async (resolve, reject) => {
let stream = got.stream(targetUrl, gotOptions);
stream.on("error", async error => {
try {
resolve(stream);
} catch (err) {
reject(err);
}
});
stream.on("response", async res => {
try {
resolve(await getStream(res));
} catch (err) {
reject(err);
}
})
});
return response;
}
async function apiMiddleware(req, res, next) {
try {
const response = await httpGet();
console.log(response);
} catch (e) {
throw new Error("some error");
}
next();
}
app.use("/", [apiMiddleware]);
app.get("/api", (req, res) => {
res.json({ data: "some output" });
});
app.get("/someapp", (req, res) => {
res.end();
});
app.listen(3000, () => {
console.log("listening on port 3000");
});
Will (upon visiting "localhost:3000/someapp") console.log
getting response
getting response
getting response
getting response
getting response
getting response
getting response
getting response
getting response
getting response
getting response
...
When debugging, the promise will always throw me at the start of httpGet(), so what is going on?

what is going on?
You've registered the apiMiddleware for all routes (/), that includes the /api route. So whenever you are requesting anything from the server, apiMiddleware() is getting called, which will - wait for it - make a http request to http://localhost:3000/api! This leads to an infinite recursion. And, since you're waiting for the response before sending a response, probably also to quickly running out of memory.

Related

Mongoose QueryStream and Request Module

When performing a QueryStream on a Model such as
User.find({}).stream().on('data', (user) => {
request.post(someOptions, (error, response, body) => {
if(!error && response.statusCode === 200) {
user.someProperty = body.newValue;
user.save((err) => {
if (err) console.log(err)
});
} else {
console.log(error);
console.log(response);
}
}).on('end', () => {
console.log('all users have new values');
});
I'm receiving the console output for the end event and then receiving an error:
all users have new values
TypeError: user.save is not a function
at Request._callback
I'm assuming this means that the request.post function is running one too many times, as I tried to do this without a stream and simply a
.find({} (err, users) => {
for(user in users) {
request.post...
}
});
but this was returning a bad status code from the api I'm posting to as request.post() was sending an undefined value in the options I'm passing that I get from the user document, even though all the documents do have defined values. Although, I wasn't receiving a error for save not being a function.
Can anyone shed some light on what's happening?
Here's a simplified version assuming you don't need stream (which should be swapped out for cursor) and you have request-promise-native available
const users = await User.find().exec()
for(let user of users) {
const body = await request.post(someOptions)
if(body) {
user.someProperty = body.newValue
await user.save()
}
}
From the code it looks like you're making the same post and updating the same value of every user. If that is the case, why not:
const body = await request.post(someOptions)
if(body) {
await User.update(
{ },
{ $set: { someProperty: body.newValue } },
{ multi: true }
)
}

Executing statements after a successful asynchronous request in a try/catch block

I am trying to understand why a console.log statement will not output to the terminal after a successful asynchronous network request. I understand why it would not execute if the request fails, since the execution would jump to the catch block. However, after completing a successful request I see no trace of the logging statement. I have the following code:
function processRequest (url, res) {
return axios.get(url).then(response => {
res.send({status: 'PASS', message: `${response.status}, on ${url}`});
});
}
app.post('/api', async (req, res) => {
let response;
try {
response = await processRequest(someValidURL, res);
console.log('after request'); //this statement does not show up after successful request
} catch (error) {
console.error(error);
}
}
In order to minimize this post, I did not include the require statements for express and the axios libraries as well as the express set-up code. Any help is appreciated.
That is because you are returning a response from within processRequest which means your request processing is finished there and the response is sent.
To achieve what you are trying to do, you should do it like this
function processRequest (url) {
return axios.get(url);
}
app.post('/api', async (req, res) => {
let response;
try {
response = await processRequest(someValidURL);
console.log('after request', response);
res.send({status: 'PASS', message: `${response.status}, on ${url}`});
} catch (error) {
console.error(error);
res.send({status: 'ERROR'});
}
}

How catch error in the front-end from response of expressjs?

My problem is the next:
//express server
app.post('/register', (req, res) => {
const {
password,
passwordConfirm
} = req.body;
if (password === passwordConfirm) {
//...
} else {
res.status(400).json("Passwords aren't matching")
}
})
//react function
onSubmitSignIn = () => {
const { password, passwordConfirm } = this.state;
let data = new FormData();
data.append('password', password);
data.append('passwordConfirm', passwordConfirm);
fetch('http://localhost:3001/register', {
method: 'post',
body: data
})
.then(response => response.json())
.then(user => {
//logs error message here
console.log(user)
})
//but I want to catch it here, and set the message to the state
.catch(alert => this.setState({alert}))
}
When I send the status code, and the message from express as the response, the front-end obviously recognize it as the response, that's why it logs the message to the console as "user". But how to send error which goes to the catch function?
fetch will really only error if it for some reason can't reason the API. In other words it will error on network errors. It will not explicitly error for non 2XX status codes.
You need to check the ok property as described here:
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful
https://developer.mozilla.org/en-US/docs/Web/API/Response/ok
--
fetch('http://localhost:3001/register', {
method: 'post',
body: data
})
.then(response => {
if (!response.ok) {
throw new Error('my api returned an error')
}
return response.json()
})
.then(user => {
console.log(user)
})
.catch(alert => this.setState({alert}))
The problem is, that fetch is not recognizing the HTTP errors as Promise rejections.
The Promise returned from fetch() won't reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally, and it will only reject on network failure or if anything prevented the request from completing.
(Source)
You can checkout the linked source of the fetch repo which also states a suggestion for handling HTTP error statuses.
What if you throw an error :
app.get("/", function (req, res) {
throw new Error("BROKEN"); // Express will catch this on its own.
});
And then catch this error in the front end ?
See here for reference
EDIT
Maybe should you return the error with return next() so that the rest of the code is not processed in the server method :
app.get("/", function (req, res) {
return next(new Error('BROKEN'));
});
//express server
app.post('/register', (req, res) => {
try {
const {
password,
passwordConfirm
} = req.body;
if (password === passwordConfirm) {
//...
} else {
res.status(400).json("Passwords aren't matching")
}
} catch (error) {
res.status(500).send(error);
}
})
//react function
onSubmitSignIn = () => {
const {
password,
passwordConfirm
} = this.state;
let data = new FormData();
data.append('password', password);
data.append('passwordConfirm', passwordConfirm);
fetch('http://localhost:3001/register', {
method: 'post',
body: data
})
.then(response => response.json())
.then(user => {
//logs error message here
console.log(user)
})
//but I want to catch it here, and set the message to the state
.catch(alert => this.setState({
alert
}))
}

superagent / supertest with async / await

Goal is to set the variable auth correctly for further use, hence i want to refactor the function loginUser:
function loginUser(user, request, auth) {
return function(done) {
request
.post('/users/login')
.send(credentials)
.expect(200)
.end(onResponse);
function onResponse(err, res) {
auth.token = res.body.token;
return done();
}
};
}
loginUser(user, request, auth)(function() {
request.get(testUrl)
.set('Authorization', `bearer ${auth.token}`)
.expect(200, done);
});
to use async / await like this (without the callback):
auth = await loginUser(user, request);
request.get(testUrl)
.set('Authorization', `bearer ${auth.token}`)
.expect(200, done);
But i am struggling of returning / setting auth correctly (it would not matter if i pass auth as parameter or as return value).
What i tried was stuff like this:
async function loginUser(user, request) {
let auth;
await request
.post('/users/login')
.send(credentials)
.expect(200)
.end(onResponse);
function onResponse(err, res) {
auth.token = res.body.token;
}
return auth;
}
But auth was never set correctly.
Don't use 'end' syntax, that's for callbacks:
const response = await request.post(...)
.expect(200)
const {body: {token}} = response
return token
Basically it should look like sync code
The problem is that the onResponse method is being executed later than you the return of the function because of the event loop in Nodejs. So you will have to do resolve the promise exactly when you receive the data
The method loginUserInternal could be like this:
function loginUserInternal(user, request) {
return new Promise((resolve,reject) => {
let auth = {};
request
.post('/users/login')
.send({
username: user.username,
password: user.password_decoded,
})
.expect(200)
.end(onResponse);
function onResponse(err, res) {
if(err) return reject(err)
auth.id = res.body.id;
auth.token = res.body.token;
auth.tokenExpires = res.body.tokenExpires;
resolve(auth)
}
})
}
And call it like you were doing with async await.

How to set default rejected promise behavior for all my Express middlewares?

I'm using promises inside express middleware. I want to use the async/await methods.
app.get('/data1',async function(req,res) {
data = await getData1(); // This line throw an error,
res.send(data)
})
app.get('/data2',async function(req,res) {
data = await getData2(); // This line throw an error
res.send(data)
})
This makes the browser wait forever.
On the server I see
(node:251960) UnhandledPromiseRejectionWarning: Unhandled promise rejection
Now, to fix it for one middleware I'm doing:
app.get('/data1',async function (req,res){
return (async function(){
data = await getData1()
})().catch(() => {
res.send("You have an error")
}
})
app.get('/data2',async function (req,res){
return (async function(){
data = await getData2()
})().catch(() => {
res.send("You have an error")
}
})
I don't like this repetion. How can I set default error? I have tried for example:
app.use(function(error,req,res,next)){
res.send('You have an error')
}
But it didn't work.
In other words: How to set default function to be called when Express middlewares returning a rejected promise?
Now I found a way how to do it, I'm still keep the question open for more suggestions
app.get("/data1",
wrap_middleware(async (req, res) => {
data1=await getData1()
res.send(data1)
})
}
app.get("/data1",
wrap_middleware(async (req, res) => {
data2=await getData2()
})
}
function wrap_middleware(func) {
return async (req, res, next) => {
func(req, res, next).catch(err => {
console.log(err.message);
res.send("Error");
});
};
}
I don't understand the use of sending the same error for different function but I think the handling error code could be write in more readable way (just catch the error and do with them what you want the same way you catch errors in any route middleware):
function getData1(){
return new Promise( (resolve,reject) => {
setTimeout(() =>{
reject(new Error('error has occur!'));
},2000);
});
}
router.get('/data1', async (req,res, next) => {
try{
const data = await getData1();
res.send(data);
}
catch(ex){
res.send(ex.message);
// next(ex); => sending it to express to handle it
}
});
If you want a global error handling then its not any different from any code you want catch errors globally - you can set a function that take as param , the response object and the async code and create general catch for every async call comes from middleware (which has response object)

Categories