I know for a fact that reading data from firebase with firebase admin returns multiple callbacks. that is why I use ref.once(), like example below:
const ref = db.ref('clients');
ref.once('value', (snapshot) => {
res.send(snapshot.val());
}, (errorObject) => {
console.log('The read failed: ' + errorObject.name);
});
But, when I try to update data I get into the same trouble of receiving multiple callbacks crashing my application, but I can't use once in ref.update, what can I do to prevent receiving multiple callbacks?
app.get('/set-client', (req, res) => {
const ref = db.ref(`users/new_users`)
ref.update({ 'client': uid_client}).then(function(){
console.log("Data saved successfully.");
res.status(200).send("successful")
}).catch(function(error) {
console.log("Data could not be saved." + error);
res.status(201).send("failed")
});
});
Here is a code example.
When interacting with responses in the way you've shown, you might find that using the .then(onFulfilled, onRejected) variant of the then() method may be of use.
Quick note: When using this approach, care must be taken to understand that if the onFulfilled handler throws an exception/rejects, the sibling onRejected handler will not be called with its exception. The exception is instead passed onto the next chained onRejected handler in later then/catch calls. The sibling will only catch exceptions/rejections from steps prior to it in the chain.
Here is an example of the difference:
const somePromise = /* ... */;
const resultPromise = somePromise
.then((data) => { /* handle data */ }) // an error thrown here
.catch((err) => { /* handle error */ }) // gets caught here
// vs.
const resultPromise = somePromise
.then(
(data) => { /* handle data */ }, // an error thrown here, will reject resultPromise
(err) => { /* handle error */ } // instead of being handled here
)
This trait of the onRejected handler in .then(onFulfilled, onRejected) can be applied in a way where headers can't be sent twice for the same response. If for whatever reason the onFulfilled handler throws an exception while trying to send a response, the onRejected handler that is also responsible for sending a response is skipped - preventing any headers already sent errors.
This means that the first code block gets swapped out for:
const ref = db.ref('clients');
ref.once('value')
.then(
(snapshot) => { // got data successfully
console.log('got data successfully');
// don't forget to check snapshot.exists() if data could be missing
res.send(snapshot.val()); // using .json() over .send() is recommended for arbitrary data
},
(error) => { // failed to get data/permission
console.error('Failed to read data at /clients: ', error);
res.status(500).send('Data unavailable.');
}
)
.catch(
(error) => { // if here, either of the above blocks failed - probably due to an error related to the response.
console.error('Failed to send response to client: ', error);
try { res.end() } catch (e) {} // forcefully terminate connection if not already
}
);
and the second code block for:
app.get('/set-client', (req, res) => {
const ref = db.ref(`users/new_users`)
ref.update({ 'client': uid_client }) // uid_client is undefined?
.then(
() => {
console.log("Data updated successfully.");
res.status(200).send("successful");
},
(error) => {
console.error("Data could not be saved.", error);
res.status(500).send("failed"); // don't use HTTP 201 Created here
}
)
.catch(
(error) => { // if here, either of the above blocks failed - probably due to an error related to the response.
console.error('Failed to send response to client: ', error);
try { res.end() } catch (e) {} // forcefully terminate connection if not already
}
);
});
The error handler that logs response errors could be rewritten so that it can be reused by taking in the relevant response object (so it can terminated when needed) and returning the error handler:
const buildResponseErrorHandler = (response) => ((error) => {
console.error('Failed to send response to client: ', error);
try { response.end() } catch (e) {} // forcefully terminate connection if not already
});
// usage:
somePromise
.then(
sendResponseHandlerForSuccess,
sendResponseHandlerForFailure
)
.catch(buildResponseErrorHandler(res)) // buildResponseErrorHandler(res) returns (err) => { /* logs problem */ }
Related
In my app I have a category collection that saves the category title with its image ID. The image is saved in another collection with its meta data like path , type and so on. So for retrieving category I should retrieve category image in image collection by its ID and add image path to category object that is retrieved from category collection and send it to client...But I don't know where should I send categories to client?
When I send the response face this error :
throw er; // Unhandled 'error' event
^
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:561:11)
at ServerResponse.header (H:\node.js\online-store\app\node_modules\express\lib\response.js:771:10)
at ServerResponse.send (H:\node.js\online-store\app\node_modules\express\lib\response.js:170:12)
at ServerResponse.json (H:\node.js\online-store\app\node_modules\express\lib\response.js:267:15)
at ServerResponse.send (H:\node.js\online-store\app\node_modules\express\lib\response.js:158:21)
at H:\node.js\online-store\app\controllers\pcategory.controller.js:123:19
at H:\node.js\online-store\app\node_modules\mongoose\lib\model.js:4845:18
at processTicksAndRejections (internal/process/task_queues.js:77:11)
Emitted 'error' event on Function instance at:
at H:\node.js\online-store\app\node_modules\mongoose\lib\model.js:4847:15
at processTicksAndRejections (internal/process/task_queues.js:77:11) {
code: 'ERR_HTTP_HEADERS_SENT'
}
This is my code :
exports.getAll = async (req, res) => {
try{
const categories = await ProductCategory.find({});
categories.map(async(category)=>{
await File.findById(category.imageID).exec(function(err,file){
if(err){
console.log(err)
}else if(file) {
category.imagePath = file.file_path;
tempCategories.push(category)
}
res.send(tempCategories);
})
})
return res.send(tempCategories);
}catch {
res.json(err =>{
console.log(err);
res.status(500).send({
message:
err.message || "There is an error in retrieving category"
});
})
}
}
The problem is that nothing in your code is waiting for the asynchronous operations you're doing in your map callback to complete, so it does the res.send at the end right away — and then does res.send again within the map callback later when the async operations complete. Instead, wait for them to finish and send the result.
Also, you're using res.send where I suspect you want res.json, and using res.json later incorrectly (it doesn't take a callback).
See comments:
exports.getAll = async (req, res) => {
try {
// Get the categories
const categories = await ProductCategory.find({});
// Get the files for the categories, wait for the result
const result = await Promise.all(categories.map(async (category) => {
const file = await File.findById(category.imageID).exec();
// You probably can't modify the `category` object, so let's create
// and return a new object
return {...category, imagePath: file.file_path};
}));
// Send the result converted to JSON
return res.json(tempCategories);
} catch (err) { // Accept the error
// Send an error response
res.status(500).json({
message: err.message || "There is an error in retrieving category"
});
}
};
Side note: Your original code was using map without using the array it creates. That's an antipattern (sadly it seems to be one someone somewhere is teaching). I wrote up why and what to do instead here. (In my update to your code, I still use map, but I use the array it creates, passing it to Promise.all so we can wait for all those promises to settle.)
Your Code Like this,
Now Issue is You are sending two times Headers.
You can use like this, Firstly Declare array and push into it what you need and then last of your logic return it or send it.
exports.getAll = async (req, res) => {
try {
const categories = await ProductCategory.find({});
let tempCategories = []; // New Line
await Promise.all(categories.map(async (category) => {
await File.findById(category.imageID).exec(function (err, file) {
if (err) {
console.log(err)
} else if (file) {
category.imagePath = file.file_path;
tempCategories.push(category)
}
});
return category;
}));
res.send(tempCategories);
} catch {
res.json(err => {
console.log(err);
res.status(500).send({
message:
err.message || "There is an error in retrieving category"
});
})
}
}
so I have a chain of request that are sent, and once catch at the end, the problem is if I have an error i wanna retry that specific request that caught the error, I know one solution to this would be to add a catch at the end off all the request i send, and when it catches an error it retries that request, but that would lead to too many catch statements, I just want one catch statement at the end that when it catches an error it retrys the specific request
rp.get('https://www.off---white.com/en-us/api/products/' + variant, options2)
.then((data) => {
// doo stuff with request
return rp.post('https://www.off---white.com/en-us/api/bags/' + bagId + '/items', options2)
})
.then((data) => {
// doo stuff with request
})
.catch((error) => {
})
Your example indicates that some requests depend on the response of a previous request. Adding a catch handler at the end of the promise chain would make it extremely difficult to retry the request and continue with subsequent requests. You need to handle the error at the request, not at the end of the promise chain. This is pretty simple to do if you wrap up the request in a helper method.
function request(opts) {
return rp(opts).catch(() => request(opts));
}
request({url: 'https://www.off---white.com/en-us/api/products/' + variant, ...options2})
.then((data) => {
// doo stuff with request
return request({method: 'POST', url: 'https://www.off---white.com/en-us/api/bags/' + bagId + '/items', ...options2});
})
.then((data) => {
// doo stuff with request
})
.catch((error) => {
});
It's not really clear what options2 is and why you use it as the request body in the second request, so this may not work exactly as you would expect, but the parameters passed into request can be tweaked to fit your use case. This will also result in an infinite request loop if the request always fails, you should implement some basic error handling to avoid this infinite loop (e.g., only retry X number of times, or retry only when you get a specific error, etc.)
First of all, you will need to design your rp.get() function in a way such that somehow whenever there is an error, the error object must have an identifier (stored as type key) as of from which request the error is propagated, then on a conditional basis, you can handle the error in a single catch() block accordingly.
const rpWrapper = (type) => { //add other required params
rp.get() //pass required params
.then( (res) => result )
.catch( (err) => { throw { type, err }) }
}
Then, modify your original code to something like,
rpWrapper.get('https://www.off---white.com/en-us/api/products/' + variant, options2)
.then((data) => {
// doo stuff with request
return rpWrapper.post('https://www.off---white.com/en-us/api/bags/' + bagId + '/items', options2)
})
.then((data) => {
// doo stuff with request
})
.catch((error) => {
if(error.type === "request1") { //handle error on first req }
if(error.type === "request2") { //handle error second req }
})
I can create 4 error scenarios when calling http.get(url, cb):
httpThrows()
Can be triggered with a wrong format of the url, or wrong callback, etc.
function httpThrows() {
try {
http.get("www.missing-protocol.com", res => {
console.log(res.statusCode);
});
} catch (error) {
console.log("http.get() throws an error.");
}
}
requestError()
It the main error handler and triggers on some network related issue, e.g. DNS lookup failed or server not responding, etc.
function requestError() {
var req = http.get("http://some-url-that-does-not-exist.com", res => {
console.log(res.statusCode);
});
req.on("error", err => {
console.log("req.on('error') called with error");
});
}
errorCode()
Server responded normally so no network errors (can handle server errors).
function errorCode() {
http.get("http://httpstat.us/501", res => {
console.log("Got error code:", res.statusCode);
});
}
responseError() (the problem)
An http.IncomingMessage is given in the callback as response or res. According to the documentation it is a Readable steam and that steam can emit an error event.
function responseError() {
http.get("http://some-ulr-with-error-halfway-through.com/", res => {
console.log(res.statusCode);
// This will never be emitted?
res.on("error", err => {
console.log("res.on('error') called with error", err);
});
});
}
So this last handler:
Is this event ever triggered when using http.request or http.get?
If so what can trigger the event?
For my understanding the only way to end up with an error in that case is if there would be an issue with Node or the Engine and in both cases you can't do much about it.
In this situations I prefer not to handle those cases because you have less code to review and maintain.
For example
randomLibPromise.then((data, err) => { // do something with err })
how would I translate that with await?
This promise comes from a library so I don't have control over the fact that the error goes through then instead of catch. What I have is:
let data = await randomLibPromise.catch(err)
but I don't get that second parameter and I can't retrieve the error.
Every example I find on Google talk about catch for error handling but not then.
To "fix" the weird promise, you could just throw err if there is one, and return the data otherwise:
var fixed = randomLibPromise.then((data, err) => {
if(err) throw err;
return data;
})
The result will be a promise which does the correct thing, i.e. passes the data to a then, an error to a catch or, if awaiting, either returns data or throws the error.
fixed.then(data => { /* handle data */ }).catch(err => { /* handle err */ });
// or
try {
var data = await fixed;
// handle data
} catch(err) {
//handle err
}
So I have an Express app that uses middleware to parse JSON POST requests and then populate a req.body object. Then I have a promise chain that validates the data against a schema using Joi, and then stores it in a database.
What I would like to do is check if an error was thrown after one of these processes, handle it appropriately by sending a status code, then COMPLETELY ABORT the promise chain. I feel like there should be some EXTREMELY CLEAN AND SIMPLE way to do this, (perhaps some sort of break statement?) but I can't find it anywhere. Here is my code. I left comments showing where I hope to abort the promise chain.
const joi = require("joi");
const createUserSchema = joi.object().keys({
username: joi.string().alphanum().min(4).max(30).required(),
password: joi.string().alphanum().min(2).max(30).required(),
});
//Here begins my promise chain
app.post("/createUser", (req, res) => {
//validate javascript object against the createUserSchema before storing in database
createUserSchema.validate(req.body)
.catch(validationError => {
res.sendStatus(400);
//CLEANLY ABORT the promise chain here
})
.then(validatedUser => {
//accepts a hash of inputs and stores it in a database
return createUser({
username: validatedUser.username,
password: validatedUser.password
})
.catch(error => {
res.sendStatus(500);
//CLEANLY ABORT the promise chain here
})
//Only now, if both promises are resolved do I send status 200
.then(() => {
res.sendStatus(200);
}
)
});
You can't abort a promise chain in the middle. It's going to either call a .then() or a .catch() later in the chain (assuming there are both and assuming your promises resolve or reject).
Usually, the way you handle this is you put one .catch() at the end of the chain and it examines the type of error and takes appropriate action. You don't handle the error earlier in the chain. You let the last .catch() handle things.
Here's what I would suggest:
// helper function
function err(status, msg) {
let obj = new Error(msg);
obj.status = status;
return obj;
}
//Here begins my promise chain
app.post("/createUser", (req, res) => {
//validate javascript object against the createUserSchema before storing in database
createUserSchema.validate(req.body).catch(validationError => {
throw err("validateError", 400)
}).then(validatedUser => {
//accepts a hash of inputs and stores it in a database
return createUser({
username: validatedUser.username,
password: validatedUser.password
}).catch(err => {
throw err("createUserError", 500);
});
}).then(() => {
// success
res.sendStatus(200);
}).catch(error => {
console.log(error);
if (error && error.status) {
res.sendStatus(error.status);
} else {
// no specific error status specified
res.sendStatus(500);
}
});
});
This has several advantages:
Any error propagates to the last .catch() at the end of the chain where it is logged and an appropriate status is sent in just one place in the code.
Success is handled in just one place where that status is sent.
This is infinitely extensible to more links in the chain. If you have more operations that can have errors, they can "abort" the rest of the chain (except the last .catch() by just rejecting with an appropriate error object).
This is somewhat analogous to the design practice of not having lots of return value statements all over your function, but rather accumulating the result and then returning it at the end which some people consider a good practice for a complicated function.
When debugging you can set breakpoints in one .then() and one .catch() to see the final resolution of the promise chain since the whole chain goes through either the last .then() or the last .catch().
.catch returns a resolved Promise by default. You want a rejected Promsise. So, you should return a rejected promise from inside the .catch, so that future .thens won't execute:
.catch(validationError => {
res.sendStatus(400);
return Promise.reject();
})
But note that this will result in a console warning:
Uncaught (in promise) ...
So it would be nice to add another .catch to the end, to suppress the error (as well as catch any other errors that come along):
const resolveAfterMs = ms => new Promise(res => setTimeout(() => {
console.log('resolving');
res();
}), ms);
console.log('start');
resolveAfterMs(500)
.then(() => {
console.log('throwing');
throw new Error();
})
.catch(() => {
console.log('handling error');
return Promise.reject();
})
.then(() => {
console.log('This .then should never execute');
})
.catch(() => void 0);
If you want to avoid all future .thens and future .catches, I suppose you could return a Promise that never resolves, though that doesn't really sound like a sign of a well-designed codebase:
const resolveAfterMs = ms => new Promise(res => setTimeout(() => {
console.log('resolving');
res();
}), ms);
console.log('start');
resolveAfterMs(500)
.then(() => {
console.log('throwing');
throw new Error();
})
.catch(() => {
console.log('handling error');
return new Promise(() => void 0);
})
.then(() => {
console.log('This .then should never execute');
})
.catch(() => {
console.log('final catch');
});
A cleaner solution for what you are trying to accomplish might be to use express-validation, which is a simple wrapper around joi that provides you with express middleware for validation of the body, params, query, headers and cookies of an express request based on your Joi schema.
That way, you could simply handle any Joi validation errors thrown by the middleware within your "generic" express error handler, with something like:
const ev = require('express-validation');
app.use(function (err, req, res, next) {
// specific for validation errors
if (err instanceof ev.ValidationError)
return res.status(err.status).json(err);
...
...
...
}
If you don't want to use the express-validation package, you could write your own simple middleware that does more or less the same thing, as described here (see example here).
One strategy is to separate your error handling in subpromises which have their individual error handling. If you throw an error from them, you'll bypass the main promise chain.
Something like:
return Promise.resolve().then(() => {
return createUserSchema.validate(req.body)
.catch(validationError => {
res.sendStatus(400);
throw 'abort';
});
}).then(validatedUser => {
// if an error was thrown before, this code won't be executed
// accepts a hash of inputs and stores it in a database
return createUser({
username: validatedUser.username,
password: validatedUser.password
}).catch(error => {
// if an error was previously thrown from `createUserSchema.validate`
// this code won't execute
res.sendStatus(500);
throw 'abort';
});
}).then(() => {
// can put in even more code here
}).then(() => {
// it was not aborted
res.sendStatus(200);
}).catch(() => {
// it was aborted
});
You can skip the Promise.resolve().then() wrapping, but it's included for illustrative purposes of the general pattern of subdividing each task and its error handling.