I've got a chained promise using .then(), the issues I am having is i'm getting Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client - I know why i'm getting it (there are 2 responses in the promise) however after doing lots of reading I can't figure out how to break the .then() callback - i've been trying to use return, however it keeps running to the next chain in the promise.
How do I stop the chain at the first res - response, when the checkResult if statement is false?
Promise Chain
await query1()
.then((checkResult) => {
if (!checkResult) {
return res.status(200)
}
})
.then(() => query2())
.then((result) => {
return res.status(201).json({ result: result, status: 201 });
})
.catch((err) => console.error(`Error occurred: ${err}`));
Query 1 Example
async function query1(){
return false
}
Just use async/await which simplifies control flow issues like these:
try {
let checkResult = await query1();
if (!checkResult) {
return res.status(200);
}
let result = await query2();
return res.status(201).json({ result: result, status: 201 });
} catch(err) {
console.error(`Error occurred: ${err}`));
}
Related
I've rewritten the following function about 6 different times and am still getting a "Cannot set headers after they are sent to the client" error. I have found several posts on the topic of promises but still cant figure it out:
Error: Can't set headers after they are sent to the client
Cannot set headers after they are sent to the client
Error: Setting header after it is sent - Help me understand why?
The following function is for a forum and is triggered when a comment is submitted. It check to see that the forum post exists, than if a parent comment exists (in the case it is a subcomment). I am using firestore.
index.js
const functions = require('firebase-functions');
const app = require('express')();
const {postOneForumComment,
} = require('./handlers/forumPosts');
app.post('/forumPost/:forumPostId/:parentId/comment', FBAuth, postOneForumComment);
exports.api = functions.https.onRequest(app);
forumPosts.js
// submit a new comment
exports.postOneForumComment = (req, res) => {
if (req.body.body.trim() === '')
return res.status(400).json({ comment: 'Must not be empty' });
const newComment = {
body: req.body.body,
forumPostId: req.params.forumPostId,
parentId: req.params.parentId
};
db.doc(`/forumPosts/${req.params.forumPostId}`) //check to see if the post exists
.get()
.then((doc) => {
if (!doc.exists) {
return res.status(404).json({ error: 'Post not found' });
}
else if (req.params.forumPostId !== req.params.parentId) { //check to see if the comment is a subcomment
return db.doc(`/forumComments/${req.params.parentId}`) //check to see if the parent comment exists
.get();
}
return "TopLevelComment";
})
.then((data) => {
if (data === 'TopLevelComment' || data.exists) {
return db.collection('forumComments').add(newComment); //post the comment to the database
}
return res.status(500).json({ error: 'Comment not found' });
})
.then(() => {
res.json(newComment);
})
.catch((err) => {
console.log(err.message);
res.status(500).json({ error: 'somethign went wrong' });
});
};
ERROR:
(node:29820) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting
a promise which was not handled with .catch(). (rejection id: 1)
(node:29820) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with
a non-zero exit code.
There are two ways of using promises. Either you use the then/catch callbacks or you can use async/await to allow you to write them synchronously.
then/catch method
// Some code before promise
somePromise.then(() => {
// Some code after promise action is successful
}).catch(err => {
// Some code if promise action failed
})
// Some code after promise definition you think should run after the above code
// THIS IS WHAT IS HAPPENING WITH YOUR CODE
async/await method
// Some code before promise
await somePromise;
// Some code after promise action is successful
The latter approach was introduces to avoid the callback hell problem and it seems that's where your error is arising from.
When using callback callbacks you must make sure that nothing is defined after the promise definition else it will run before the promise resolves (Which is counter-intuitive since placing code B after code B should make A run before B)
Your error is because your callbacks are probably running AFTER the response has been sent and express does not allow you to send multiple responses for a request.
You should make sure that where ever res.send or res.json is being called exist within the callback.
This article should help you understand promises much better...
Hope this helps...
For anyone who stumbles upon this here is a working solution using Promise.all to make sure all promises are fulfilled before moving on. It is not the prettiest function and I plan on going back and turning it into an async/await ordeal per #kwame and #Ajay's recommendation... but for now it works.
// post a comment
// TODO: turn into async await function
exports.postOneForumComment = (req, res) => {
if (req.body.body.trim() === '') return res.status(400).json({ comment: 'Must not be empty' });
const newComment = {
body: req.body.body,
createdAt: new Date().toISOString(),
forumPostId: req.params.forumPostId,
parentId: req.params.parentId,
username: req.user.username,
userImage: req.user.imageUrl,
likeCount: 0
};
const parentPost =
db.doc(`/forumPosts/${req.params.forumPostId}`).get()
.then((doc) => {
if (!doc.exists) {
res.status(404).json({ error: 'Post not found' });
return false;
}
return true;
})
.catch((err) => {res.status(500).json({ error: 'something went wrong while checking the post' });});
const parentComment =
req.params.forumPostId === req.params.parentId ? true :
db.doc(`/forumComments/${req.params.parentId}`).get()
.then((doc) => {
if (!doc.exists) {
res.status(404).json({ error: 'Comment not found' });
return false;
}
if (doc.forumPostId !== req.params.forumPostId) {
res.status(404).json({ error: 'Comment is not affiliated with this post' });
return false;
}
return true;
})
.catch((err) => {res.status(500).json({ error: 'something went wrong while checking the comment' });});
Promise.all([parentPost, parentComment])
.then((values) => {
if (values[0] && values[1]) {
return db.collection('forumComments')
.add(newComment)
.then(() => {
res.json(newComment);
});
}
return console.log("there was an error");
})
.catch((err) => {
res.status(500).json({ error: 'somethign went wrong with the submission' });
});
};
I have this idempotent function with multiple promises that I wrote for Google Cloud Functions.
I want to have retry enabled since my used API is pretty inconsistent. This requires a rejected promise to be returned when a retry is needed.
Therefore I tried to return a promise.all([]) but that does not terminate/stop the function when one of the promises fails. It then even proceeds to the promise.all().then()? This should only happen when all 4 promises are successful.
Who can point me in the right direction? Does it even make sense what I'm trying?
exports.scheduleTask = functions
.firestore.document("tasks_schedule/{servicebonnummer}")
.onCreate((snap, context) => {
servicebonnummer = snap.data().data.servicebonnummer;
bondatum = snap.data().data.bondatum;
servicestatus = snap.data().data.servicestatus;
tijdstip = snap.data().data.tijdstip;
firestorePromise = null;
firestoreFinish = null;
cashPromise = null;
cashFinish = null;
//Firebase
//firestoreSchedule executes the required promise
//checkFinished points to a database where it checks a boolean for idempotency
//firestoreFinish writes to this database and sets the boolean to true when the promise is successful
if (!checkFinished("tasks_schedule", servicebonnummer, "firestore")) {
firestorePromise = scheduleFirestore(
servicebonnummer,
bondatum,
servicestatus,
tijdstip
)
.then(output => {
firestoreFinish = markFinished(
"tasks_schedule",
servicebonnummer,
"firestore"
);
return output;
})
.catch(error => {
console.error(
"scheduleFirestore - Error connecting to Firestore: ",
error
);
return error;
});
}
//SOAP API
//cashSchedule executes the required promise
//checkFinished points to a database where it checks a boolean for idempotency
//cashFinish writes to this database and sets the boolean to true when the promise is successful
if (!checkFinished("tasks_schedule", servicebonnummer, "cash")) {
cashPromise = scheduleCash(
servicebonnummer,
moment(bondatum),
servicestatus,
tijdstip
)
.then(result => {
if (result[0].response.code === "2") {
cashFinish = markFinished(
"tasks_schedule",
servicebonnummer,
"cash"
);
return result;
}
throw new Error("Validation error, response not successful");
})
.catch(error => {
console.error("scheduleCash - Error connecting to CASH API: ", error);
return error;
});
}
//CHECK PROMISES
return Promise.all([
firestorePromise,
firestoreFinish,
cashPromise,
cashFinish
])
.then(result => {
removeTask("tasks_schedule", servicebonnummer);
return result;
})
.catch(error => {
console.error("scheduleTask - Retry: ", error);
return error;
});
});
If you code:
let somePromise = new Promise(...);
return somePromise.then(funcA).catch(funcB);
Then you are indeed returning a promise. However, since you have handlers for that promise in your code, we need to look at what happens in more detail. Let us assume that somePromise is rejected. This will mean that the catch() handler will be invoked. It is the outcome of that catch handler that will be the ultimate resolution of the returned promise.
If we look at the MDN docs for Promise.catch() we find the following:
The Promise returned by catch() is rejected if onRejected throws an
error or returns a Promise which is itself rejected; otherwise, it is
resolved.
If we look at your code,
catch(error => {
console.error("scheduleTask - Retry: ", error);
return error;
});
And now ask:
Does this code throw an error? Nope ... it has no throw statement in it and hence just returns the values passed in.
Does the code return a Promise? Nope ... it is passed an error value and simply returns that error value which I am pretty sure will not itself be a Promise.
This means that the overall Promise returned is concluded in a resolved state and not a rejected state and hence the overall Cloud Function is considered to have concluded and is not retried.
Your options may be:
catch(error => {
console.error("scheduleTask - Retry: ", error);
throw error;
});
or
catch(error => {
console.error("scheduleTask - Retry: ", error);
return Promise.reject(error);
});
References:
Promise.prototype.catch()
Promise.reject()
I wrote a snippet of code that gets some a JSON from the Foursquare API. From this JSON, I get IDs of venues. These IDs are then used to get more details from those specific venues by issuing a fetch() request for every ID, and mapping those requests in an array. That array is then passed into Promise.all(). When the API is available, everything works, but it's the error catching that I can't get my head around.
fetch(`https://api.foursquare.com/v2/venues/search?${params}`)
.then(response => response.json())
.then(data => {
const venueIds = data.response.venues.map(venue => venue.id)
const venuePromises = venueIds.map(venueId => {
fetch(`https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`)
.then(response => {
// Must check for response.ok, because
// catch() does not catch 429
if (response.ok) {
console.log('ok')
return response.json()
} else {
Promise.reject('Error when getting venue details')
}
})
})
Promise.all(venuePromises).then(data => {
const venues = data.map(entry => entry.response.venue) // Error for this line
this.parseFsqData(venues)
}).catch((e) => {console.log(e); getBackupData()})
}).catch((e) => {console.log(e); getBackupData()})
function getBackupData() {
console.log('backup')
}
When the API is not available, I get the following console errors (and more of the same):
TypeError: Cannot read property 'response' of undefined
at MapsApp.js:97
at Array.map (<anonymous>)
at MapsApp.js:97
backup
api.foursquare.com/v2/venues/4b7efa2ef964a520c90d30e3?client_id=ANDGBLDVCRISN1JNRWNLLTDNGTBNB2I4SZT4ZQYKPTY3PDNP&client_secret=QNVYZRG0JYJR3G45SP3RTOTQK0SLQSNTDCYXOBWUUYCGKPJX&v=20180323:1 Failed to load resource: the server responded with a status of 429 ()
Uncaught (in promise) Error when getting venue details
I don't understand why then() after Promise.all() is entered, because response is never ok (there is no ok logged in console). Also, I don't understand why the console.log()'s in the catch() blocks aren't executed, or why they are empty. I don't see any caught error information in console, but still the getBackupData function is called. Finally, it is unclear why the last message in console indicates that the error is uncaught, as I expected reject() to make Promise.all() fail.
How can I tactfully catch any errors (included those not normally caught by catch(), such as 429 errors) and call getBackupData when any errors occur?
Your issues are related: namely, the Promise chain must be returned. If you do not return the Promise, you disconnect any of the caller's Promise#catch handling, and any errors in your Promise / then code will result in unhandled promise rejection errors, such as what you have obtained:
Uncaught (in promise) Error when getting venue details
This uncaught promise rejection appears in your code that handles the resolution of fetch:
if (response.ok) {
console.log('ok')
return response.json()
} else {
Promise.reject('Error when getting venue details') // <----
}
Since this code is being used to construct your venuePromises array, its return value will populate venuePromises. If the response was ok, that array element will have the response JSON from return response.json(). If the response failed, there is no return statement that executes, so the array element has the value undefined. Thus, venuePromises would look like this:
[
{ /** some object for successful response */ },
undefined,
{ /** some other object */ },
...
]
Thus when this array is accessed by your Promise.all's success handler, you get the TypeError since you expected all elements of venuePromises to be valid. This TypeError is caught by the Promise.all's .catch handler (which is why it is logged, and you receive the "backup" text in your log).
To fix, you need to return the Promise.reject, and also the Promise.all. Note that there are some cases of implicit return, but I find it nicer to be explicit, especially if the statement spans multiple lines. Since you're returning the Promise.all statement, you can offload its .then and .catch to the caller, resulting in one less nesting level, and one fewer duplicated .catch handler.
fetch(`https://api.foursquare.com/v2/venues/search?${params}`)
.then(response => response.json())
.then(jsonData => {
const venueIds = jsonData.response.venues.map(venue => venue.id);
const venuePromises = venueIds.map(venueId => {
let link = `https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`;
return fetch(link).then(response => {
// Must check for response.ok, because catch() does not catch 429
if (response.ok) {
console.log('ok');
return response.json();
} else {
console.log(`FAILED: ${link}`);
// Return a Promise
return Promise.reject(`Error when getting venue details for '${venueId}'`);
}
});
});
return Promise.all(venuePromises);
})
.then(venueData => {
const venues = venueData.map(entry => entry.response.venue);
this.parseFsqData(venues);
})
.catch(e => {console.log(e); getBackupData()});
function getBackupData() {
console.log('backup')
}
Try returning the rejected promise.
return Promise.reject('Error when getting venue details')
When working with promises you should return inner promises instad of working with inner "thens".
Check this:
fetch(`https://api.foursquare.com/v2/venues/search?${params}`)
.then(response => response.json())
.then(data => {
const venueIds = data.response.venues.map(venue => venue.id);
const venuePromises = venueIds.map(venueId => {
fetch(`https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`)
.then(response => {
// Must check for response.ok, because
// catch() does not catch 429
if (response.ok) {
console.log('ok')
return response.json()
} else {
return Promise.reject('Error when getting venue details')
}
})
});
return Promise.all(venuePromises)
})
.then(venueValues => {
const venues = venueValues.map(entry => entry.response.venue); // Error for this line
this.parseFsqData(venues);
})
.catch((e) => {console.log(e); getBackupData()})
function getBackupData() {
console.log('backup')
}
When returning Promise.all as a value, you're returning a promise so that you can chain further "then" callbacks. The last catch shall capture all promise rejects.
You're also missing the return in the else clause
Hope this helps
I believe the solution is fairly simple; The response of the nested fetch method is missing a return statement. You should get rid of that mysterious error once it is in place.
const venuePromises = venueIds.map(venueId => {
<missing return statement here> fetch(`https://api.foursquare.com/v2/venues/${venueId}?${otherParams}`)
.then(response => {
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.
I'm trying to get the hang of using Mongoose promises with the async/await functionality of Node.js. When my function printEmployees is called I want to save the list of employees which are queried by the orderEmployees function. While, the console.log statement inside orderEmployees returns the expected query, the console.log inside of printEmployees returns undefined, suggesting that I'm not returning the promise correctly.
I'm new to promises so entirely possible that I'm not correctly understanding the paradigm... any help is much appreciated.
printEmployees: async(company) => {
var employees = await self.orderEmployees(company);
// SECOND CONSOLE.LOG
console.log(employees);
},
orderEmployees: (companyID) => {
User.find({company:companyID})
.exec()
.then((employees) => {
// FIRST CONSOLE.LOG
console.log(employees);
return employees;
})
.catch((err) => {
return 'error occured';
});
},
In order to make orderEmployees behave like async functions, you have to return the resulting promise. There are two rules to follow when using promises without async/await keywords:
A function is asynchronous if it returns a Promise
If you have a promise (for example returned by an async function) you must either call .then on it or return it.
When you are using async/await then you must await on promises you obtain.
This said you will notice that you do not return the promise generated inside orderEmployees. Easy to fix, but its also easy to rewrite that function to async too.
orderEmployees: (companyID) => {
return User.find({company:companyID}) // Notice the return here
.exec()
.then((employees) => {
// FIRST CONSOLE.LOG
console.log(employees);
return employees;
})
.catch((err) => {
return 'error occured';
});
},
or
orderEmployees: async(companyID) => {
try {
const employees = await User.find({company:companyID}).exec();
console.log(employees);
return employees;
} catch (err) {
return 'error occured';
}
},
PS: the error handling is somewhat flawed here. We usually do not handle errors by returning an error string from a function. It is better to have the error propagate in this case, and handle it from some top-level, UI code.
You need to return your Promise.
Currently, you are awaiting on a function that returns undefined.
await only actually "waits" for the value if it's used with a Promise.
Always keep in mind that you can only await Promises or async functions, which implicitly return a Promise1.
orderEmployees: (companyID) => {
return User.find({ company:companyID }).exec()
}
Also really important, you should throw instead of return in your .catch handler. Returning from within a .catch handler will cause the promise chain to trigger it's .then instead of it's .catch thus breaking the error handling chain.
Better yet, don't include .catch at all and let the the actual error bubble up the promise chain, instead of overriding it with your own non-descriptive 'error occured' message.
Error conditions should throw the error, not return it.
1 You can also await non-Promises, but only for values that are evaluated synchronously.
You are not returning a Promise from orderEmployees.
printEmployees: async(company) => {
var employees = await self.orderEmployees(company);
// SECOND CONSOLE.LOG
console.log(employees);
},
orderEmployees: (companyID) => {
return User.find({company:companyID})
.exec()
.then((employees) => {
// FIRST CONSOLE.LOG
console.log(employees);
return employees;
})
.catch((err) => {
return 'error occured';
});
},
You need to return a Promise from orderEmployees
orderEmployees: companyId => User.find({ companyId }).exec()
If you want to do some error handling or pre-processing before you return then you can keep your code as is but just remember to return the result (promises are chainable).
if you're going to use async/await then it works like this.
await in front of the function that returns a promise.
async in front of the wrapping function.
wrap the function body inside try/catch block.
Please have a look on this function, it is a middleware
before i execute a specific route in express.
const validateUserInDB = async (req, res, next) => {
try {
const user = await UserModel.findById(req.user._id);
if (!user) return res.status(401).json({ message: "Unauthorized." });
req.user = user;
return next();
} catch (error) {
return res.status(500).json({ message: "Internal server error." })
}
}
The code after await is waiting the promise to be resolved.
Catch block catches any error happened inside the try block even if the error that is triggered by catch method comes from awaiting promise.