Proper Meteor error handling with async calls using Future - javascript

I am wondering how to properly handle errors with Meteor when using async methods. I have tried the following, but the error is being returned in the result parameter on the client callback instead of the error parameter.
Server code:
Future = Npm.require('fibers/future');
Meteor.methods({
'myServerMethod': function(){
var future = new Future();
// URL to some remote API
var url = UrlOfTheApiIWantToCall;
HTTP.get(url, {//other params as a hash},
function (error, result) {
if (!error) {
future.return(result);
} else {
future.return(error);
}
}
);
return future.wait();
}
});
Client code:
Meteor.call('myServerMethod', function (error, result) {
if(error){
console.warn(error);
}
console.log('result', result);
});
As I was saying above, 'error' is always undefined on the client side event when the HTTP.get() on the server side returned an error. I also tried replacing future.return(error); with future.throw(error); on the server side, but this really throws an error on the server side. The client side error parameter then gets a 500 Server Error, although the error thrown on the server was a 401 Unauthorized error.
So, is it possible to use Fiber's Future properly so that the client callback receives the same error parameter as the server callback?

According to the Meteor.Error docs at http://docs.meteor.com/#/full/meteor_error
Methods can throw any kind of exception. But Meteor.Error is the only kind of error that a server will send to the client. If a method function throws a different exception, then it will be mapped to a sanitized version on the wire. Specifically, if the sanitizedError field on the thrown error is set to a Meteor.Error, then that error will be sent to the client. Otherwise, if no sanitized version is available, the client gets Meteor.Error(500, 'Internal server error').
Which is why you are receiving the 500 Server Error on the client. If you want to preserve the error message and have it be sent to the client, you can do something like this:
Future = Npm.require('fibers/future');
Meteor.methods({
'myServerMethod': function(){
var future = new Future();
// URL to some remote API
var url = UrlOfTheApiIWantToCall;
HTTP.get(url, {//other params as a hash},
function (error, result) {
if (!error) {
future.return(result);
} else {
future.throw(error);
}
}
);
try {
return future.wait();
}
catch(err) {
// Replace this with whatever you want sent to the client.
throw new Meteor.Error("http-error", err);
}
}
});

Related

Error: Cannot remove headers after they are sent to the client

I was working on admin registration and admin data retrieving react app. The registration works fine but retrieving admin data is crushing my backend. I have encountered this error when call the given endpoint from my react app. But when I call it from Postman it works very fine. And when I see the console on my browser my react app sends two calls simultaneously instead of one. On these calls my app crushes. If any one can show me how to solve this problem?
For backend = Node.js with express.js framework
For frontend = React
This is the error I am getting
node:internal/errors:465
ErrorCaptureStackTrace(err);
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot remove headers after they are sent to the client
at new NodeError (node:internal/errors:372:5)
at ServerResponse.removeHeader (node:_http_outgoing:654:11)
at ServerResponse.send (C:\Users\Momentum\Documents\The Technologies\Madudi-App-Api\node_modules\express\lib\response.js:214:10)
at C:\Users\Momentum\Documents\The Technologies\Madudi-App-Api\api\index.js:22:72
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
code: 'ERR_HTTP_HEADERS_SENT'
}
[nodemon] app crashed - waiting for file changes before starting...
This is how I setup my endpoint and changed the data to a string in order to get simple response but it crushes
const makeHttpRequest = (controller, helper) => {
const makeRequest = (req, res) => {
try {
var data = "Trying response";
res.status(200).send({ status: true, data: data });
} catch (error) {
console.log(`ERROR: ${error.message}`);
res.status(400).send({ status: false, error: error.message });
}
};
return { makeRequest };
};
const makeApi = ({router, controller, helper}) => {
router.get("/test", (req, res) => res.send("Router is Woking..."));
router.get("/admin/get_all_admins", async (req, res) => res.send(await makeHttpRequest(controller, helper).makeRequest(req, res)));
}
module.exports = { makeApi }
And this is the call from my react app
export default function GetAllUsers() {
useEffect(() =>{
try{
const response = axios.get('http://localhost:5000/admin/get_all_admins').then(async (response) => {
console.log('response ', response)
return response.data;
});
}catch(error) {
return [];
}
}, [])
I'm not familiar with this method of responding to requests, but in my own opinion the error you are facing happens when you're sending multiple response.
This may be the asynchronous nature of JavaScript, there by causing another request to be sent after the function is done.
You should also try to return the response, so that once it's done it cancels out of the function. You can use the example below
const handler = (req,res) => {
return res.status(200).json(data)}
This particular error happens when you try to send more than one response for the same incoming request (something you are not allowed to do).
You are calling res.send() more than one for the same request on your server.
The first happens in the makeRequest() function.
The second time happens in this line of code:
router.get("/admin/get_all_admins", async (req, res) => res.send(await makeHttpRequest(controller, helper).makeRequest(req, res)));
You can't do that. You get ONE response per incoming request. So, either send the response in makeRquest() and don't send it in the caller. Or, don't send the response in makeRequest() and just return what the response should be and let the caller send it. Pick one model or the other.
I am not familiar with this way of setting up the server. Looks strange to me. However, in router.get("/admin/get_all_admins" your sending a response which calls a function makeHttpRequest that also sends a response. Thus you get an error Cannot remove headers after they are sent to the client because you're sending a response twice.

How can i send data through an API to other servers only using Nodejs and Express?

I'm new to NodeJS and I am currently working on getting an API working. Currently running is Express for that purpose and i really would like to stick to express to solve it.
My goal is to let other people send me their data through links (Example would be: http://localhost:1000/api/?product=test) so i can just grab them with a simple 'var productname = req.param('product'); That part works just fine.
But i would like to simply call a method to send data from my server, meaning i would like to trigger sending the data with a function and then send the data as a link to another server. (Example would be to http://google.com/search?q=test)
I can't seem to get it to work even if i directly work with the documentation from express: https://nodejs.org/api/http.html#http_http_get_url_options_callback
Could anyone point me in the right direction?
If i try the code snippet below, I'm not even getting a console.log.
My current code attempt is:
// testing purpose to call the method and get a console log
sendServerUpdates('chair');
function sendServerUpdates(product){
url = 'google.com/';
app.get(url + 'search', (res) => {
const {statusCode} = res;
const contentType = res.headers['content-type'];
let error;
if (statusCode !== 200) {
error = new Error('Request Failed.\n' + `Status Code:
${statusCode}`);
} else if (!/^application\/json/.test(contentType)) {
error = new Error('Invalid content-type.\n' + `Expected
application/json but received ${contentType}`);
}
if (error) {
console.error(error.message);
// Consume response data to free up memory
res.resume();
return;
}
// Information for me that the system is sending a message
console.log('sending update');
// sending (if its working) the parameter product
res.status(200).send(product);
})
}
}

Error from nodejs server thrown not being passed back to AJAX

I have the following AJAX that will send the entered data to the node server and the controller will check whether such data exist in the database or not.
If I do enter the data correctly, then everything is working fine.
However, I tried enter anything that the database does not have and it immediately throw an error, causing the server to stop. The error said that I did not handle the event, so I tried with res.json(err) in the controller instead of throw new Error, hoping that the error will be passed back to AJAX under the error key, but it is still not working. The error still gets thrown and the node server terminate itself.
I would like the server to continue and alert to the user that the data that was entered is not in the database but I have no idea why my approach is not correct.
I was thinking of using this SO thread if I'm able to get the error message back first from server side.
jQuery Ajax error handling, show custom exception messages
To solve the server from stopping, I used the code in app.js that was referred from this link
How do I prevent node.js from crashing? try-catch doesn't work
I'm not sure whether should I use the accepted answer for my case.
function createProduct(inputval){
let inputAction = window.location.pathname;
$.ajax({
type: "POST",
url: inputAction,
data: {order: inputval.split('-')[0].trim(), lot: inputval.split('-')[1].substring(0,5)},
success: function(data) {
$('#product').val('');
//Another function to add HTML
display(data);
},
error: function(jqXHR, textStatus, errorThrown) {
console.log("XHR" + jqXHR)
console.log("Status" + textStatus)
console.log(errorThrown)
}
});
}
Controller File
exports.createProduct = function (req, res) {
db.Product.findOne({ "order": req.body.order, "lot": req.body.lot }).exec(function (err, product) {
if (!product || err){
throw new Error("The product entered returns null");
}
res.json(product);
});
};
Main File: app.js
process.on('uncaughtException', function (err) {
console.error(err);
console.log("Node NOT Exiting...");
});
You should use correct status code for your response. I suggest change your controller like below snippet
exports.createProduct = function (req, res) {
db.Product.findOne({ "order": req.body.order, "lot": req.body.lot }).exec(function (err, product) {
if (err){
res.status(500).end();//means internal server error
} else if (!product) {
res.status(404).end();//means product not found
} else {
res.json(product);
}
});
};
I finally figure it out thanks to feedback from other community, so I thought I would just share it here. It's so simple and silly me for neglecting such statement.
First, the code in app.js can just be removed.
Second, based on the answer given by #Milad Aghamohammadi. Instead of just:
res.status(500).end();
Use:
return res.status(500).json({err: "Server error"});
This way, the error is able to be handled by the AJAX error function and the node server will not be terminated from the event loop.

Is Meteor.Error changing when thrown to client?

I have this:
Meteor.methods({
'foo'() {
try{
...//some http.get
} catch (e) {
console.log(e); //-> { [Error: ETIMEDOUT] code: 'ETIMEDOUT', connect: true }
if(e.code === 'ETIMEDOUT') { throw e; }
}
}
});
So now i am on client:
Meteor.call('foo', function(error, result) {
if(error){
if(error.code === 'ETIMEDOUT') {
//this block is never reached.. why?
}
}
}
But it seems the error.code is not the same as on server (It seems like it's been changed to Internal Server Error). Why is this? And more important, how can i get my original (in this case timeout) error?
from the manual:
When you have an error that doesn’t need to be reported to the client,
but is internal to the server, throw a regular JavaScript error
object. This will be reported to the client as a totally opaque
internal server error with no details.
That is what you're seeing. Instead:
When the server was not able to complete the user’s desired action
because of a known condition, you should throw a descriptive
Meteor.Error object to the client.
Meteor.Error takes three arguments: error, reason, and details.
so you might do something like this:
if (e.code === 'ETIMEDOUT') {
let userMessage = 'The remote call timed out.';
let detail = `${userMessage}: ${JSON.stringify(e)}`;
console.error(detail);
throw new Meteor.Error('remote-call-timed-out', userMessage, detail);
}
The first argument, the "error" (i call it a "code"), is something you can program against on the client to take a specific action or internationalize the user message. that's what we do in our system. (and if the code is not found, we show the userMessage). the detail gets written to the server log and put into the browser console.log.

express - catch request failed error

I am using the request module to send requests to a URL
var req = require('request');
when the response is received, I would like to write that file on the node server, so I am piping it to crateWriteStream
req.get(myUrl)
.on('error', function(err) {
console.log('there is an error');
})
.pipe(fs.createWriteStream('userdir/abc.png'));
This works fine if there is no error returned by req.get. In case req.get, fails I would like to not write the file locally and instead do something else.
I introduced .on('error'..) for this but the on('error') code never gets executed and .pipe tries to write the file that does not exist.
How can catch an error returned by req.get() and only write when there is no error.
I think you are using streams but normally you can use the callback to receive the all information and then check if there is any error.
Personally I would do something like:
var req = require('request');
req.get(myUrl, function (error, response, body) {
if (error || response.statusCode != 200) {
console.log(error) // Do something with your error
}
// If no errors, this code will be executed
// Write in file
})

Categories