purposely throw error in a promise? - javascript

How to purposely make a promise fail? Sometime I just skip the test and assume everything is fine, but I want to purposely make the promise to fail so that my catch is working.
exports.submitJob = async (req, res, next) => {
const { cv } = req.body
const userId = req.user._id
try {
if(!cv) {
//how to pass error to catch block?
}
const save_submission = new Submission({
userId,
cv
}).save()
} catch(e => {
res.json({
status: 0,
error: e
})
})
next()
}

You can throw new Error('<your string here>');:
Note that catch is not something to be used with function syntax - the proper syntax is catch (e) { /* block that uses e */ }
const submitJobWhichWillFail = async (req, res, next) => {
const cv = null;
try {
if (!cv) {
throw new Error('cv must not be falsey!');
}
const save_submission = new Submission({
userId,
cv
}).save()
} catch (e) {
console.log('res.json with error ' + e);
}
}
submitJobWhichWillFail();

use the throw statement, maybe?
if(!cv) {throw("cv is absent");}
user-defined exception-types (alike one commonly has them in Java or PHP) are also possible and recommended, because one barely can differ the type by a string and otherwise can easily check the typeof the exception in the catch block. just learned from the MDN, that one eg. can also throw DOMException and Error.

Related

Is it correct to throw an Error inside the catch block?

I'm writing a middleware that has a services layer and a controller layer. In order to transmit a clear error message to the user, I catch any errors in the services layer and then throw a new Error with a personalized message, like this:
// services.js
async getOneRecord(id) {
const url = this.url + `/${id}`;
return await axios.get(url).then((res) => res.data);
}
// UserServices.js
async getTaxValue(id) {
try {
const user = await this.getOneRecord(id);
return Number(user.tax) / 100;
} catch (error) {
throw new Error(`User ID not found: ${id}`);
}
}
// UserController.js
static async getUserTax(req, res) {
const { userId } = req.params;
try {
const user = await userServices.getTaxValue(userId);
return res.status(200).json(user);
} catch (error) {
return res.status(404).json(error.message);
}
}
I would like to find out if this is the right way to treat errors or if I'm doing something wrong.
Yes, it's correct and normal to throw new errors in catch blocks.
However, it is not a good practice to ignore the error that you caught. There are a lot of things that can go wrong in getTaxValue, and I would argue that most of them should not result in an "id not found" error. So be very explicit about the error that you expect, test for it with a condition, and rethrow everything else unchanged. Also set the .cause of errors.
In your case, that might be (handling only 404 errors from the user record endpoint):
class NotFoundError extends Error {}
async getTaxValue(id) {
try {
const user = await this.getOneRecord(id);
return Number(user.tax) / 100;
} catch (error) {
if (error.response && error.response.status == 404) { // see AxiosError
throw new NotFoundError(`User ID not found: ${id}`, {cause: error});
}
throw error;
}
}
// UserController.js
async function getUserTax(req, res) {
const { userId } = req.params;
try {
const user = await userServices.getTaxValue(userId);
return res.status(200).json(user);
} catch (error) {
return res.status(error instanceof NotFoundError ? 404 : 500).json(error.message);
}
}

How can I return an error from a function?

Let's say I have a function like this:
const getPlayer = (id) => {
return players[id;]
}
//--------------------------
const client = getPlayer(9);
How can I return the err parameter to the client variable if no player is found? For example:
if (client.err) {
//do something
}
I tried passing the error via throw new Error('my error') , but the function still doesn't get it, what am I doing wrong?:(
So your first instinct was correct, you should use the 'throw' keyword to raise an error. To act on the error you need to use try/catch like I've done below.
const getPlayer = (id) => {
if(id in players) {
return players[id];
}
throw new Error("Oh noes...!");
}
try {
const client = getPlayer(9);
} catch(error) {
console.log(error.message);
}
When an error is thrown inside a function being executed in a try block, execution immediately jumps to the catch block, allowing you to respond to the error appropriately.
Checkout try/catch syntax for that.
For example:
const getPlayer = (id) => {
if (!id) {
throw new Error('no id provided');
}
return players[id]
}
To get this "error" state, when it triggers you can do following:
try {
const client = getPlayer(null);
} catch(error) {
console.log(error.message);
}
I have tried something but not sure if this is what you are after:
let a = (x) => {
if (x == 0) {
throw new Error("Votes are zero");
} else {
return x;
}
};
Run it in the console with the values as a(0) --> will throw you a new error and a(5)

Catching a try error inside an async callback

I'm using a callback to set some ip's on redis db async.
I'm trying to catch the error and send it through express to my error handler middleware.
I'm generating an error on purpose on the select method, but it doesn't catch my error.
See following code:
module.exports = (req, res, next) => {
const redis = require('redis')
const client = redis.createClient()
try {
client.select('2d', (err) => { // instead of 2 number, i use '2d' string, to generate an error on purpose
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
client.set(ip, true, 'EX', 120, (err, rep) => {
return next()
})
})
} catch (err) {
err.type = 'SilentSystem'
next(err)
}
}
From the documentation of the redis npm package, it's clear that it uses standard Node-style callbacks. In a standard Node-style callback, the first argument passed to the callback you provide is either an error or null; that's where and how errors are reported. (You've even defined a parameter called err in your code.) They can't be caught by a try/catch because control has already passed out of the try/catch (and in fact out of the function it's in) long before the error occurs.
So you'd handle it like this:
module.exports = (req, res, next) => {
const redis = require('redis')
const client = redis.createClient()
client.select('2d', (err) => { // instead of 2 number, i use '2d' string, to generate an error on purpose
if (err) {
// Handle error here
err.type = 'SilentSystem'
next(err)
} else {
// Handle success here
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
client.set(ip, true, 'EX', 120, (err, rep) => {
if (err) {
err.type = 'SilentSystem'
next(err)
} else {
next()
}
})
}
})
}
In a comment you've said:
My actual code is a bit more complex so I was trying to avoid calling to avoid repeating calling if(err) and next(err) by using try. What's a better way (less verbose) to handle errors here?
Unfortunately, that's the nature of Node-style callbacks. One option is to give yourself a filtering function you pass all those results through so your common error-handling code is there.
But: You might consider using a lib that "promisifies" Node-style callbacks so you can use promises instead, complete with their chaining mechanism which makes centralized error handling possible. (One such package is promisify, but there are others.) With "promisified" versions of client.select, client.set., etc., that code could look like this:
module.exports = (req, res, next) => {
const redis = require('redis')
const client = makeNiftyPromiseVersionOf(redis.createClient())
client.select('2d')
.then(data => {
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
return client.set(ip, true, 'EX', 120)
})
.then(() => {
next()
})
.catch(err => {
err.type = 'SilentSystem'
next(err)
})
}
Note how the error handling is consolidated at the end; if there's an error in client.select, the then callback is skipped and control passes to the catch. If not, the then callback is executed and client.set is performed, and any errors from it will also go to that catch.
This also opens the door to using ES2017's async/await to write asynchronous code in a synchronous style:
module.exports = (req, res, next) => {
(async () => {
const redis = require('redis')
const client = makeNiftyPromiseVersionOf(redis.createClient())
try {
const data = await client.select('2d');
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
await client.set(ip, true, 'EX', 120)
next()
} catch (err) {
err.type = 'SilentSystem'
next(err)
}
})();
}
Side note: I would remove the require call out of the exported function, and instead do it at the module level:
const redis = require('redis')
module.exports = {
// ...
}

Handling Errors (Rejections) in async/await inside Array#map

Node 8.1.2, I have a structure where one file is calling another file's function in a map. In a real example I would use Promise.all on the map but that's not the question here. Here is the structure:
A.js:
const { b } = require('./B')
function expressStuff (req, res, next) {
things.map(thing => {
return b(thing)
}))
return res.status(200).json(...)
}
B.js:
// Thing -> Promise<Object>
function b (thing) {
return ThingModel.update(...) // this returns a Promise but FAILS and throws an errror
}
module.exports = { b }
OK. So in function b I try to get some async data (from a database). It fails and throws an Uncaught Promise Rejection.
How to make deal with it?
I tried multiple solutions:
A1.js:
const { b } = require('./B')
function expressStuff (req, res, next) {
things.map(thing => {
try {
return b(thing)
} catch (err) {
return next(err)
}
}))
return res.status(200).json(...)
}
But that is still uncaught.
A2.js:
const { b } = require('./B')
function expressStuff (req, res, next) {
try {
things.map(thing => {
return b(thing)
}))
} catch (err) {
return next(err)
}
return res.status(200).json(...)
}
Still unhandled. I tried using Promise.all, I tried double try-catch blocks (since I thought the one inside map might be returning next from the to the map result and not actually from expressStuff function. Still nothing.
The closes I got to the answer was handling the error but then code wouldn't wait for it to be thrown and both res.status() and next would work resulting in race conditions and cannot set headers after they are sent errors.
All I want to do is for the function b to throw an error but catch it in the expressStuff so I can rethrow custom UnprocessableEntityError and pass it to next. It seems like error from file B is not bubbling up to the map where it is called.
How do I do it?
EDIT:
The only way I can make this rejection handled is try-catching it in the B.js. But if I try to rethrow an error/return it - nothing. Error is swallowed. If I try to console.log it - it will be logged though.
DETAILS:
Thanks to marked answer I refactored my actual code and made it to work perfectly.
function expressStuff (res, req, next) {
try {
await Promise.all(things.map(async thing => {
if (ifSomething()) {
await b(thing)
}
}))
} catch (err) {
return next(new MyCustomError('My Custom Error Message'))
}
return res.status(200).json(...)
}
Handling rejections with try/catch works only in async functions when you await the promise - which you haven't attempted yet.
You could do either
async function expressStuff (req, res, next) {
var results;
try {
results = await Promise.all(things.map(b)); // throws when any of the promises reject
} catch (err) {
return next(err) // handle error
}
return res.status(200).json(...)
}
or (like Wait until all ES6 promises complete, even rejected promises)
function expressStuff (req, res, next) {
const resultPromises = things.map(async (thing) => {
try {
return await b(thing); // throws when the promise for this particular thing rejects
} catch (err) {
return defaultValue; // handle error - don't call `next` here
}
});
…
return res.status(200).json(...)
}

this await throwing unexpected token error

I have a simple async function. It just sends a request and returns the data:
export const updatePanorama = async ({ commit }, payload) => {
const urlEnd = '/v1/pano/update'
const type = 'post'
const resp = await api.asyncRequest(urlEnd, type, payload)
commit('SET_PANORAMA', resp.data)
return resp
}
And this is how I'm using the function:
handleUpdatePanorama (panorama) {
const payload = {}
this.updatePanorama(payload).then(resp => {
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
}).catch(() => {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
},
The problem is, the code after catch runs if there's an error inside then. But this way I don't know whether the catch error is an request error or and error triggered by the code inside then.
I'm trying try and catch to solve that problem:
handleUpdatePanorama (panorama) {
try {
const payload = {}
const resp = await this.updatePanorama(payload)
console.log('resp:', resp)
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
} catch (err) {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
},
However, I get an unexpected token error in this line: await this.updatePanorama(payload)
What am I doing wrong?
The problem is, the code after catch runs if there's an error inside then
The solution for that is to not use catch, but the second then parameter. Have a look at the difference between .then(…).catch(…) and .then(…, …) for details.
I'm trying try and catch to solve that problem
That won't work, the catch clause will still be called if there's an exception thrown by setIsLoading or handleAlert.
I get an unexpected token error. What am I doing wrong?
You have not declared the handleUpdatePanorama method as async.
To mitigate the issues and fix the syntax, you could write
async handleUpdatePanorama (panorama) {
var result
try {
const payload = {}
const resp = await this.updatePanorama(payload)
console.log('resp:', resp)
result = ['updateSuccess', 'success']
} catch (err) {
result = ['updateError', 'danger']
} finally {
this.setIsLoading(false)
}
this.handleAlert(...result)
},
If you need to handle errors specifically from updatePanorama, use the second argument to .then(onSuccess, onError)
handleUpdatePanorama(panorama) {
const payload = {}
this.updatePanorama(payload).then(resp => {
this.setIsLoading(false)
this.handleAlert('updateSuccess', 'success')
}, err => {
// handle error from updatePanorama
// you can throw err if you also want to handle the error in .catch()
}).catch(() => {
this.setIsLoading(false)
this.handleAlert('updateError', 'danger')
})
}
note: if you return (or have no return statement) from the error handler, any subsequent .then(onSuccess will execute, if you throw an error (or return Promise.reject() for example, then the .catch() code will also run

Categories

Resources