In the following snippet I would like to validate the fields in the first async method.
If they are not valid I would like to return an error to the user immediately.
How do I do that?
var form = new formidable.IncomingForm();
async1.series([
function (callback) {
form.parse(req);
form.on('field', function (name, val) {
// Get the fields
});
form.on('fileBegin', function (name, file) {
if (file.name !== "") {
file.path = __dirname + '/upload/' + file.name;
}
});
callback();
},
function (callback) {
form.on('file', function (name, file) {
try {
// Do something with the file using the fields retrieved from first async method
}
catch (err) {
logger.info(err);
}
});
callback();
}
], function (err) {
//the upload failed, there is nothing we can do, send a 500
if (err === "uploadFailed") {
return res.send(500);
}
if (err) {
throw err;
}
return res.status(200);
});
No. You don't need to use try/catch in every async/await. You only need to do it at the top level.
To recap: Throwing error from an async function won't spit out a "plain exception". Async functions and async methods always return a Promise, either resolved or rejected. To intercept exceptions from async functions you must use catch() .
I would extract the form checking into a function:
var form = new formidable.IncomingForm();
function check(name, cb, err) {
return new Promise((res,rej) => {
form.on('field', function(n, val) {
if(n !== name) return;
if(cb(val)){
res(val);
}else{
rej(err);
}
});
});
}
form.parse(req);
So now we can implement the checks and use Promise.all to summarize them:
Promise.all(
check("username", val => val.length > 4, "username isnt valid"),
check("password", val => true, "we need a password")
).then(_ => res.json({status:200}))
.catch(err => res.json({err}));
If not all all parameters have been passed, this will wait endlessly. So lets terminate if it was ended:
const ended = new Promise((_,rej) => form.on("end", () => rej("params required"));
Promise.race(
ended,
Promise.all(
check("username", val => val.length > 4, "username isnt valid"),
check("password", val => true, "we need a password")
)
).then(_ => res.json({status:200}))
.catch(err => res.json({err}));
So with that we can create a good flow of data. e.g.:
const login = Promise.all(
//usable as one liners
check("username", val => val.length >= 8, "username invalid"),
//or more extensible
check("password", val => {
if( val.length < 8 ) return false;
//other checks
console.log(password);
return true;
}, "password invalid")
//the field values are resolved by the promises so we can summarize them below
).then(([username,password]) =>
//a random (maybe async) call to evaluate the credentials
checkAgainstDB(username,password)
//we can directly fail here, err is "password invalid" or "username invalid"
).catch(err => res.json({error:"login failed",details:err}));
//another parameter can be extra handled
const data = check("something", val => val.length);
//we need to summarize all the possible paths (login /data in this case) to one that generates the result
Promise.race(
//here we join them together
Promise.all(login, data)
.then((l, d) => res.json(whatever),
//and we use the ended promise ( from above ) to end the whole thing
ended
//and at last the errors that can occur if the response ended or that have not been canceled early
).catch(e => res.json(e));
var form = new formidable.IncomingForm();
async1.series([
function (callback) {
form.parse(req);
form.on('field', function (name, val) {
if (!name || !val) {
// the moment callback is called with an error, async will stop execution of any of the steps
// in the series and execute the function provided as the last argument
// idimoatic node, when calling the callback with instance of Error
return callback(new Error('InvalidParams'));
}
/**
* This is from async documentation: https://caolan.github.io/async/docs.html#series
* Run the functions in the tasks collection in series, each one running once the previous function
* has completed. If any functions in the series pass an error to its callback, no more functions are
* run, and callback is immediately called with the value of the error. Otherwise, callback receives
* an array of results when tasks have completed.
*/
});
form.on('fileBegin', function (name, file) {
if (file.name !== "") {
file.path = __dirname + '/upload/' + file.name;
}
});
form.on('end', function () {
// call callback with null to specify there's no error
// if there are some results, call it like callback(null, results);
return callback(null);
});
// if you call the callback immediately after registering event handlers for on('field') etc,
// there will be no time for those events to be triggered, by that time, this function will be
// done executing.
//callback();
},
function (callback) {
form.on('file', function (name, file) {
try {
// Do something with the file using the fields retrieved from first async method
}
catch (err) {
logger.info(err);
return callback(err);
}
});
// This should also not be called immediately
//callback();
}
], function (err) {
//the upload failed, there is nothing we can do, send a 500
if (err === "uploadFailed") {
return res.send(500);
}
if (err.message === 'InvalidParams') {
// This will be immediately returned to the user.
return res.sendStatus(400);
}
if (err) {
// I'm not sure if this was just for the example, but if not, you should not be throwing an error
// at run time.
throw err;
}
return res.status(200);
});
I added some comments in the code where I needed to show where and how to create an error and how it's bubbled up to the user immediately.
Reference: Async Documentation
P.S. Code Snippet is not runnable, but it has a better representation of the code.
-- edit --
After knowing more from the comment, adding another snippet. You are unreasonably mixing callback and event handling. You can just pass a callback to form.parse and the callback is called when all fiels are collected. You can do validation, return error immediately or just handle the form fields right away.
form.parse(req, function(err, fields, files) {
if (err) return res.sendStatus(400);
if (fields.areNotValid()) return res.sendStatus(400);
// parse fields
});
Or, you can register event handlers for it. All events as they flow in will be handled concurrently, like async.series.
var form = new formidable.IncomingForm();
form.parse(req);
form.on('field', (name, val) => {
if (!name || val) {
console.log('InvalidParams')
return res.sendStatus(400);
}
});
form.on('fileBegin', (name, file) => {
if (file.name !== "") {
file.path = __dirname + '/upload/' + file.name;
}
});
form.on('file', (name, file) => {
});
form.on('error', (err) => {
console.log('ParsingError');
return res.sendStatus(400);
})
form.on('end', () => {
if (res.headersSent) {
console.log('Response sent already')
} else {
// handle what you want to handle at the end of the form when all task in series are finished
return res.sendStatus(200);
}
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With