Reputation: 18281
In my server app I want to return a "forbidden" value when the user has no permissions for the endpoint.
To this end I create a rejected promise for reuse:
export const forbidden = Promise.reject(new Error('FORBIDDEN'))
and then elsewhere in the app:
import {forbidden} from './utils'
...
resolve: (root, {post}, {db, isCollab}) => {
if (!isCollab) return forbidden
return db.posts.set(post)
},
However, when I start my app I get the warning
(node:71620) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: FORBIDDEN
(node:71620) [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.
How can I tell Node that this Promise is fine to be unhandled?
Upvotes: 5
Views: 5165
Reputation: 8150
I wouldn't recommend using the return
statement to provide an Error
- this is ignoring the exact intention of throw
!
Simply use:
if (!isCollab) throw new Error('FORBIDDEN');
If you don't want a stack trace there's no need to over-engineer - simply do:
if (!isCollab) throw 'FORBIDDEN';
If you need a message
property to exist you can simply use:
if (!isCollab) throw { message: 'FORBIDDEN' };
(Note: I recommend against throwing anything other than an instance of Error
! You'll regret it later when things break and you need to debug)
Upvotes: 2
Reputation: 2528
OP's usage is not completely described, but the OP's comment "BTW I didn't want to create a stack trace for every forbidden error because I don't want to leak details about my app. So I prefer to create the rejection only once." leads me to believe that at least part of the OP's motivation is to prevent info leakage from unhandled rejections of forbidden
.
Returning a rejected (but defused) promise behaves differently in a sync vs. an async function. In the former the promise is returned verbatim. In the latter it is rewrapped in a promised and automatically rethrown (equivalent to throwing from inside the function). Whichever use was intended, it makes the program harder to understand.(Wrapping the Promise to be returned in an object or array would solve that problem).
Difference in behavior between sync and async funcs when returning forbidden
:
async function test(){
try {
let a = await (async()=>{return forbidden;})();
} catch(e){console.log(e.message);} // outputs: 'FORBIDDEN'
try {
let a = (()=>{return forbidden;})();
// error undetected
} catch(e){console.log(e.message);} // never reaches here !!!
console.log("something else"); // outputs: something else
let a=(()=>{return forbidden;})(); // UHR + '#<Promise>' + no addr
console.log("something else"); // outputs: something else
await (async()=>{return forbidden;})(); // UHR + '#<Promise>' + no addr leak}
}
test();
The below factory function makeError
would provide a general solution, and it builds on the OP's original inspiration:
const verboten=new Error('verbotten');
const makeError = () => verboten;
async function test2(){
try {
await (async()=>{throw makeError();})();
} catch(e){console.log(e.message);} // outputs: 'verboten'
// uncomment the following to trigger UHR (unhandled rejection)
//await (async()=>{throw makeError();})(); // UHR + 'verboten' + no addr leak
}
Note that makeError
returns the constant object verboten
, rather than itself. (Yes, that is allowed, although it is rarely used.) So the stack trace is a fixed value, unrelated to the error location in the program, just like the original OP's forbidden
.
That's fine for the release version, but a minor change to makeError
could be made for a development version, where seeing the stack is useful:
const makeError = Error;
Upvotes: 1
Reputation: 664444
I create a rejected promise for reuse
Well don't, it might be a lot easier to just create a function for reuse:
export function forbidden() { return Promise.reject(new Error('FORBIDDEN')); }
That will also get you an appropriate stack trace for the error every time you call it.
How can I tell Node that this Promise is fine to be unhandled?
Just handle it by doing nothing:
export const forbidden = Promise.reject(new Error('FORBIDDEN'));
forbidden.catch(err => { /* ignore */ }); // mark error as handled
(and don't forget to include the comment about the purpose of this seemingly no-op statement).
Upvotes: 2