FASTIFY: Is there a way for the onResponse hook to execute ONLY when the handler sends 200 and not execute in any other case? - fastify

I'm trying to increment a value when the request is successful. Yet the onResponse executes whatever the response from the handler is. Is there a way for the onResponse hook to execute ONLY when the handler sends 200 and not execute in any other case?
/routes/gif.ts
export default (
fastify: Iserver,
_opts: FastifyPluginOptions,
next: (error?: Error) => void
): void => {
fastify.get<{
Headers: IHeaders;
}>('/gif', {
// Execute only when handler sends a reply with 200.
onResponse: async (req): Promise<void> => {
db.increment(req.headers['some-header']);
}
},
// Handler
async (req, reply) => {
// If no header in request, stop route execution here.
if (!req.headers['some-header']) return reply.code(400).send();
reply.code(200).send();
}
);
next();
};

You just need to check for the status code:
onResponse: (request, reply, done): Promise<void> => {
if (reply.statusCode === 200) {
// fire&forget
db.increment(req.headers['some-header'])
.catch(err => request.log.error('error incrementing');
}
done()
}

Related

Returning from a express middleware using promise and breaking out from loop returns 404

I am trying to return an array of an object(car) from express using database functions used below with stolenCarDb object, but the database functions function perfectly fine
Doing the below returns a 404 with error UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
router.post("/reportstolen", function (request: express.Request, response: express.Response, next: (err?: Error) => void) {
stolenCarDb.put_car(request.body)
.then(() => {
stolenCarDb.get_available_cops()
.then(cops => {
cops.forEach(cop => {
stolenCarDb.assign_cop_car(cop._id, request.body._id)
.then(() => response.status(201).send(`assgined cop ${cop._id} to car`))
})
})
.then(() => response.status(201).send("created"))
})
.catch(() => response.status(500).send("error"))
.finally(next)
})
You are sending a response to the user multiple times, which you cannot do.
You should send a response only one time, after all operations completed.
Edit:
If I had to rewrite the code myself, I would use async/await. You could read more about that here.
Also while rereading your code, I don't see the point of calling next in the finally block.
Async/await
router.post("/reportstolen", async function (request: express.Request, response: express.Response, next: (err?: Error) => void) {
try {
await stolenCarDb.put_car(request.body)
const cops = await stolenCarDb.get_available_cops();
for (const cop of cops) {
await stolenCarDb.assign_cop_car(cop._id, request.body._id);
}
response.status(201).send("created")
} catch (err) {
response.status(500).send("error")
}
});
If you want to display every cop to car addition you can do:
router.post("/reportstolen", async function (request: express.Request, response: express.Response, next: (err?: Error) => void) {
try {
await stolenCarDb.put_car(request.body)
const cops = await stolenCarDb.get_available_cops();
for (const cop of cops) {
await stolenCarDb.assign_cop_car(cop._id, request.body._id);
res.write(`assgined cop ${cop._id} to car\n`);
}
response.status(201).send("created")
} catch (err) {
response.status(500).send("error")
}
});
I would also recommend you read this post about using promises in loops and why you don't want to use promises in forEach loops.

Express: send response after getting another response via an api call

I want my website to respond after a successful api call, which is initiated via a post request and also changes some database values.
If there was NO post request the site should also load as usual.
If I do something like this, then the site is getting loaded as usual and then I get an error because of the second rendering attempt.
I guess because node does wait for the receipt, but in parallel does already execute the loadNewSite() function:
app.all('/customer', function(req, res) {
if (Object.keys(req.body).length != 0) {
apiCall(someParameter)
.on('error', error => {console.log(error);} )
.on('receipt', function() {loadNewSite();} );
}
function loadNewSite() {
return res.render('site.html');
}
loadNewSite()
})
try removing the last loadNewSite() as one is already called when you on reciept
check with req.method whether it's a POST request or not.
app.all('/customer', function(req, res) {
// if method is not post handle seperately
if(req.method != 'POST'){
return loadNewSite('site.html');
}
if (Object.keys(req.body).length != 0) {
apiCall(someParameter)
.on('error', error => {console.log(error);} )
.on('receipt', function() {loadNewSite();} );
}
function loadNewSite() {
return res.render('site.html');
}
})
I would create a promise to execute the Api call and resolve it on receipt or reject it on error. Then make the callback async and await for the api call promise.
I left the final call to loadNewSite in case of error, obviously you can modify it and make a function that maybe returns something different in error case.
const execApiCall = (params) => {
return new Promise((resolve, reject) => {
apiCall(params)
.on('error', error => {reject(error);} )
.on('receipt', function() {resolve();} );
})
};
app.all('/customer', async function(req, res) {
function loadNewSite() {
return res.render('site.html');
}
if (Object.keys(req.body).length != 0) {
try {
await execApiCall(params);
return loadNewSite();
} catch (e) { //handle errors }
}
loadNewSite()
})

Mocha, how to test async code without UnhandledPromiseRejectionWarning

I'm testing my server-side api endpoints with mochajs and I can't figure out how to do it properly.
I started with code that had the following logic:
it('test', (doneFn) => {
// Add request handler
express.get('/test', (req, res, next) => {
// Send response
res.status(200).end();
// Run some more tests (which will fail and throw an Error)
true.should.be.false;
// And that's the problem, normally my framework would catch the
// error and return it in the response, but that logic can't work
// for code executed after the response is sent.
});
// Launch request
requests.get(url('/test'), (err, resp, body) => { // Handle response
// I need to run some more tests here
true.should.be.true;
// Tell mocha test is finished
doneFn();
});
});
But the test doesn't fail because it throws in the request handling callback.
So I googled around and found that my problem could be solved using promises, and it does, now the test fails. This is the resulting code:
it('test', (doneFn) => {
let handlerPromise;
// Add request handler
express.get('/test', (req, res, next) => {
// Store it in a promise
handlerPromise = new Promise(fulfill => {
res.status(200).end();
true.should.be.false; // Fail
fulfill();
});
});
// Send request
requests.get(url('/test'), (err, resp, body) => {
// Run the other tests
true.should.be.true;
handlerPromise
.then(() => doneFn()) // If no error, pass
.catch(doneFn); // Else, call doneFn(error);
});
});
But now I end up with a deprecation warning because the error is handled in a different process tick than the one it was thrown.
The errors are: UnhandledPromiseRejectionWarning and PromiseRejectionHandledWarning
How can I make my test fail after the response is sent, and avoid having an
unhandledPromiseRejectionWarning?
This works
it('test', (doneFn) => {
let bindRequestHandler = new Promise((reslove, reject) => {
// Add request handler
app.testRouter.get('/test', (req, res, next) => {
// Send response
res.status(200).end();
try { // Here, we need a try/catch/reject logic because we're in a callback (not in the promise scope)
// Run some more tests (which will fail and throw an Error)
true.should.be.false;
} catch (error) { // Catch the failing test errors and reject them
reject(error);
}
resolve();
});
});
let sendRequest = new Promise((reslove, reject) => {
// Launch request
requests.get(url('/test'), (err, resp, body) => { // Handle response
try {
// I need to run some more tests here
true.should.be.true;
} catch (error) { // Catch the failing test errors and reject them
reject(error);
}
reslove();
});
});
Promise.all([bindRequestHandler, sendRequest])
.then(res => doneFn())
.catch(doneFn);
});

Unit test promise result inside Event Listener in Node

I have the following code that I want to test.
emitter.on("request", function(req, res) {
mock_finder.getMockedResponse().then((mockedResponse) => {
res.end(mockedResponse);
});
});
Then, I have this unit test.
it("should return mocked response", () => {
// given
const mockedResponse = {
success: true
};
mock_finder.getMockedResponse.mockImplementation(() => Promise.resolve(mockedResponse));
const res = {
end: jest.fn()
}
// when
emitter.emit('request', req, res);
// then
expect(res.end).toHaveBeenCalledWith(mockedResponse);
});
This test is not working because res.end(mockedResponse); is executed after the test finishes.
How can I test the promise response after an event is called?
Given there's no real async code going on here, you can verify the result on the next tick:
if('should return mocked response', done => {
...
emitter.emit('request', req, res);
process.nextTick(() => {
expect(res.end).toHaveBeenCalledWith(mockedResponse);
done()
});
})

Angular2 - Multiple service calls in OnInit method of component

How can I make two service calls in the OnInit() method of the component ?
export class ApartmentComponent implements OnInit {
public apartments: Object[];
public temp: Object[];
constructor(private apartmentService: ApartmentService) {
this.apartmentService = apartmentService;
}
ngOnInit() {
this.apartmentService.getApartments().subscribe(res => this.apartments = res);
this.apartmentService.getStats().subscribe(res => this.temp = res);
console.log(JSON.stringify(this.temp));
}
}
In service.ts
getApartments() {
return this.http.get('./api/businessunits/butype').map((res: Response) => res.json());
}
getStats(){
console.log('Request reached');
return this.http.get('./api/apartments/getstats').map((res: Response) => res.json());
}
in server.ts (ExpressJS)
router.route('/api/businessunits/butype')
.get(function(req, res) {
BusinessUnit.find({unitID: {$exists: true}, UnitType: {$exists: true}},'unitID UnitType',{sort:{unitID: 1}},function(err, businessunits) {
if (err)
res.send(err);
res.json(businessunits);
});
});
router.route('/api/apartments/getstats')
.get(function(req, res) {
//Apartment.aggregate([{$match:{_id: "aptType"}},{$group:{_id:{aptType:"$aptType"},count:{$sum:1}}}],function(err, apartments) {
Apartment.find('aptType',function(err, apartments) {
if (err)
res.send(err);
res.json(apartments);
});
});
The getApartments() works fine individually when I comment out getStats() method call.
I am getting the following error
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:335:11)
at ServerResponse.header (M:\workspace\Angular2StartKit\node_modules\express
Subscribing to observables is an async operation, that means this just schedules a tasks to be done later.
When console.log(JSON.stringify(this.temp) is executed, the call to the server in getStats() (if it actually makes one - I just assume it does) wasn't even sent, and therefor definitely no response received yet.
It is also not clear from the code in your question whether the request for getApartments() or getStats() is sent first.
To preserve a specific order in async operations, you need to chain them properly so that the next is executed when the former is completed.
If you just want to print the result of getStats() this can be done like
ngOnInit() {
this.apartmentService.getApartments().subscribe(res => this.apartments = res);
this.apartmentService.getStats().subscribe(res => {
this.temp = res;
JSON.stringify(this.temp)
});
}
alternatives are
ngOnInit() {
this.apartmentService.getApartments().subscribe(res => this.apartments = res);
this.apartmentService.getStats()
.map(res => this.temp = res);
.subscribe(temp => console.log(JSON.stringify(this.temp));
});
}
or
ngOnInit() {
this.apartmentService.getApartments().subscribe(res => this.apartments = res);
this.apartmentService.getStats()
.map(res => this.temp = res);
.toPromise().then(temp => console.log(JSON.stringify(this.temp));
});
}
If you want to chain 2 subscribes
this.apartmentService.getApartments().subscribe(res => this.apartments = res);
this.apartmentService.getStats().subscribe(res => this.temp = res);
there are lots of possiblilities like flatMap() depending on your requirements. You might want that they are sent one after the other is completed, or send both as soon as possible but then wait for both to complete. There are different ways to handle errors, ...
For more details see http://blog.thoughtram.io/angular/2016/01/06/taking-advantage-of-observables-in-angular2.html

Resources