Let’s say I have a function foo which returns a promise. Is there a way to call the function, and optionally Promise.prototype.catch the result only if its rejection is unhandled? I want a solution which works in both node.js and the browser. For example:
const fooResult = foo();
// pass fooResult somewhere else where fooResult may be caught with catch
catchIfUncaught(fooResult, (err) => {
console.log(err); // should be foo rejection only if the rejection is not caught elsewhere
// no unhandled rejection occurs
});
No, there is not. When your function returns a promise, that leaves error handling to the caller - and he'll get an unhandledpromiserejection event if he misses to do that.
The only hack I can imagine would be to recognise then calls, and then cancel your own error handling:
function catchIfUncaught(promise, handler) {
let handled = false;
promise.catch(err => {
if (!handled)
handler(err);
});
promise.then = function(onFulfilled, onRejected) {
handled = true;
return Promise.prototype.then.call(this, onFulfilled, onRejected);
};
return promise;
}
Examples:
catchIfUncaught(Promise.reject(), err => console.log("default handler", err));
catchIfUncaught(Promise.reject(), err => console.log("default handler", err))
.catch(err => console.log("catch handler", err));
catchIfUncaught(Promise.reject(), err => console.log("default handler", err))
.then(null, err => console.log("then rejection handler", err));
catchIfUncaught(Promise.reject(), err => console.log("default handler", err))
.then(res => {})
.catch(err => console.log("chained catch handler", err));
catchIfUncaught(Promise.reject(), err => console.log("default handler", err))
.then(res => {});
// unhandled rejection (on the chained promise)
As you can see, this is only useful when the caller of your function completely ignores the result - which is really uncommon. And if he does, I'd recommend to still let the caller handle errors.
A similar hack I devised earlier would be to use the handler as the default for onRejected:
…
promise.then = function(onFulfilled, onRejected = handler) {
// ^^^^^^^^^
return Promise.prototype.then.call(this, onFulfilled, onRejected);
};
This would activate the default handler in the catchIfUncaught(…).then(res => …); case, but probably be highly counter-intuitive to the caller in longer chains.
Also notice that neither of these two hacks work properly together with await, where they always lead to an exception that the caller needs to catch. And same for any other builtin that expects a thenable - they always call .then with two arguments.
You could take a look to this package https://npmjs.org/package/e-promises
but you have to change your code to use the new mechanism
import the EPromise
extends it using YourPromise extends EPromise (optional)
assign YourPromise.prototype.unchaught to your catchIfUncaught implementation
change codes in foo, each place that make promises must change to use YourPromise, etc new YourPromise(executor) / YourPromise.resolve / YourPromise.all / ...
You can just catch the error case, if you don't care about the passing case.
catchIfUncaught.catch(function (err) {
console.error('We had an error: ', err)
})
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
Related
I have a function that is used to add a record to the IndexDb database:
async function addAsync(storeName, object) {
return new Promise((res, rej) => {
// openDatabaseAsync() is another reusable method to open the db. That works fine.
openDatabaseAsync().then(db => {
var store = openObjectStore(db, storeName, 'readwrite');
var addResult = store.add(JSON.parse(object));
addResult.onsuccess = res;
addResult.onerror = (e) => {
console.log("addResult Error");
throw e;
};
}).catch(e => {
// Error from "throw e;" above NOT GETTING CAUGHT HERE!
console.error("addAsync ERROR > ", e, storeName, object);
rej(e);
});
})
}
If I try to add a duplicate key, then I expect:
addResult.onerror = (e) => {
console.log("addResult Error");
throw e;
}
to capture that. It does.
But then, I also expect my
.catch(e => {
// Error from "throw e;" above NOT GETTING CAUGHT HERE!
console.error("addAsync ERROR > ", e, storeName, object);
rej(e);
})
to catch that error. But instead I get an "uncaught" log.
Console output:
addResult Error
Uncaught Event {isTrusted: true, type: "error", target: IDBRequest, currentTarget: IDBRequest, eventPhase: 2, …}
Does that final .catch only handle exceptions from the openDatabaseAsync call? I would have thought now as it is chained to the .then.
In summary, here's what I would expect from the above code:
If openDatabaseAsync() fails then I'm not catching that so the error would be sent to the caller of addAsync().
If .then fails then I expect the .catch to catch it, log the error and then reject the promise meaning that the called of addAsync() would need to handle that.
However, I would have thought that I should get the log from the line:
console.error("addAsync ERROR > ", e, storeName, object);
before the reject is sent back to the caller of addAsync(), which may be unhandled at that point.
Your approach would benefit form a larger overhaul.
Generally, don't write a function as async when it's not also using await.
Don't use new Promise() for an operation that returns a promise, such as openDatabaseAsync() does. Return that promise, or switch to async/await.
It would be useful to wrap IndexedDB operations so that they follow promise semantics.
On the example of IDBRequest:
function promisifyIDBRequest(idbr) {
return new Promise( (resolve, reject) => {
idbr.onsuccess = () => resolve(idbr.result);
idbr.onerror = (e) => reject(e.target.error);
});
}
Now you can do this:
async function addAsync(storeName, object) {
const db = await openDatabaseAsync();
const store = openObjectStore(db, storeName, 'readwrite');
return promisifyIDBRequest(store.add(JSON.parse(object)));
}
Add a try/catch block if you want to handle errors inside of addAsync().
It's worth checking out existing solutions that wrap the entire IndexedDB interface with promise semantics, such as https://github.com/jakearchibald/idb.
FWIW, the promise-chain variant of the above function would look like this:
function addAsync(storeName, object) {
return openDatabaseAsync().then( (db) => {
const store = openObjectStore(db, storeName, 'readwrite');
return promisifyIDBRequest(store.add(JSON.parse(object)));
});
}
both variants return a promise for an IDBRequest result.
This is a design question that came up to me while unit testing.
Let's dive into the example:
Imagine this:
async function foo() {
try {
return apiCall()
}
catch (e) {
throw new CustomError(e);
}
}
async function bar() {
return foo()
}
async function main() {
try {
await bar()
}catch(e) {
console.error(e)
}
}
main()
What do we see here? That the only function that hasn't got a try-catch block is bar.
But if foo fails, it should get catched by the main catch.
While unittesting this like
describe('testing bar', () => {
it('foo should throw', () => {
foo.mockImplementantion(() => { throw new CustomError('error')});
bar()
.then((result) => console.log(result))
.catch((err) => { exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
})
})
The output we see is that an Unhandled promise rejection is logged in the console.
So, my question is... even if I know that the main() will catch the error, should I use try-catch block inside all async functions?
try..catch may be necessary if a function is able to recover from an error, do a side effect like logging, or re-throw a more meaningful error.
If CustomError is more preferable than an error that apiCall can throw then try..catch necessary, otherwise it doesn't. Also the problem with foo is that it handles only synchronous errors. In order to handle rejected promises, it should be return await apiCall(), this is a known pitfall of async.
Uncaught rejections are unwanted, they currently result in UnhandledPromiseRejectionWarning and are expected to crash Node in future versions. It's preferable to handle an error in a meaningful way at top level, so main needs to catch the error. This can be delegated to process uncaughtRejection event handler but it may be beneficial for it to stay extra level of error handling that should be never reached.
The output we see is that an Unhandled promise rejection is logged in the console.
This shouldn't happen. A rejection needs to be handled by the test. One possible point of failure is explained above, foo can return original error from apiCall instead of CustomError in case it wasn't correctly mocked, this will fail the expectation and result in unhandled rejection in catch(). Another point of failure is that the test has unchained promise because it wasn't returned, the test always passes.
Asynchronous test that uses promises should always return a promise. This can be improved by using async..await. foo is async, it's expected to always return a promise:
it('foo should throw', async () => {
foo.mockImplementantion(() => { return Promise.reject(new CustomError('error')) });
await expect(bar()).rejects.toThrow(CustomError);
})
Now even if foo mock fails (foo mock won't affect bar if they are defined in the same module as shown) and bar rejects with something that is not CustomError, this will be asserted.
No. You don't need to use try/catch in every async/await. You only need to do it at the top level. In this case your main function which you are already doing.
Weather you should is a matter of opinion. The go language designers feel strongly enough about this that is has become the standard in go to always handle errors at each function call. But this is not the norm in javascript or most other languages.
Unhandled promise rejection
Your unhandled promise rejection is thrown by your it() function because you are not telling it to wait for the promise to complete.
I assume you are using something like mocha for the unit test (other frameworks may work differently). In mocha there are two ways to handle asynchronous tests:
Call the done callback - the it() function will always be called with a done callback. It is up to you weather you want to use it or like in your posted code to not use it:
describe('testing bar', () => {
it('foo should throw', (done) => {
foo.mockImplementantion(() => { throw new CustomError('error')});
bar()
.then((result) => {
console.log(result);
done(); // ------------- THIS IS YOUR ACTUAL BUG
})
.catch((err) => {
exepect(err).toBeInstanceOf(CustomError);
done(); // ------------- THIS IS YOUR ACTUAL BUG
})
})
})
Return a Promise. If you return a promise to the it() function mocha will be aware that your code is asynchronous and wait for completion:
describe('testing bar', () => {
it('foo should throw', (done) => {
foo.mockImplementantion(() => { throw new CustomError('error')});
return bar() // <----------- THIS WOULD ALSO FIX IT
.then((result) => {
console.log(result);
})
.catch((err) => {
exepect(err).toBeInstanceOf(CustomError);
})
})
})
In short, there is nothing wrong with your code. But you have a bug in your unit test.
As #Bergi told me I will post some solutions right here
I wrap the function in a try catch block
1.
async function bar() {
try{
return foo()
} catch (e) {
throw e
}
}
Rewrite the test
describe('testing bar', () => {
it('foo should throw', (done) => {
foo.mockImplementantion(() => { throw new CustomError('error')});
bar()
.then((result) => { throw result }) // this is because we are expecting an error, so if the promise resolves it's actually a bad sign.
.catch((err) => {
exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
done();
})
})
Use return in the test case
describe('testing bar', () => {
it('foo should throw', () => {
foo.mockImplementantion(() => { throw new CustomError('error')});
return bar()
.then((result) => { throw result })
.catch((err) => { exepect(err).toBeInstanceOf(CustomError)}) // this is what we are testing
})
})
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 have a rejected promise somewhere in my promise chain like this. I know following code will work similarly.
let promise = Promise.resolve(3);
let p1 = promise.then(() => 2)
.then(() => {throw 'some error'})
.then(null, (err) => {console.log(err)})
let p2 = promise.then(() => 2)
.then(() => {throw 'some error'})
.catch((err) => {console.log(err)})
Is there any difference conceptually in chaining promises with .then(null, (error) {}) instead of just .catch((err) => {})
Promise.catch() is equal to Promise.then(null, callback). You can use these interchangably. However Promise.catch is more descriptive and has the following advantages:
Easier to know that it is a error handling mechanism when you read .catch than when you read .then(null, callback)
Actually less code because you can leave out the null as a first argument of .then()
To conclude, there is no functional difference. However, .catch() is more descriptive and less code and thus superior.
As for the promise chaining it doesn't have any effect as they are functionally similar.
Can someone please explain why returning an Axios promise allows for further chaining, but returning after applying a then()/catch() method does not?
Example:
const url = 'https://58f58f38c9deb71200ceece2.mockapi.io/Mapss'
function createRequest1() {
const request = axios.get(url)
request
.then(result => console.log('(1) Inside result:', result))
.catch(error => console.error('(1) Inside error:', error))
return request
}
function createRequest2() {
const request = axios.get(url)
return request
.then(result => console.log('(2) Inside result:', result))
.catch(error => console.error('(2) Inside error:', error))
}
createRequest1()
.then(result => console.log('(1) Outside result:', result))
.catch(error => console.error('(1) Outside error:', error))
createRequest2()
.then(result => console.log('(2) Outside result:', result))
.catch(error => console.error('(2) Outside error:', error))
<script src="https://unpkg.com/axios#0.16.1/dist/axios.min.js"></script>
https://jsfiddle.net/nandastone/81zdvodv/1/
I understand that Promise methods should return a value to be chained, but why is there a difference between these two return methods?
Your first example returns the original promise. Your second example returns a different promise, the one created by calling catch.
The critical differences between the two are:
In your second example, you're not passing on the resolution value, so the promise returned by your then is resolved with undefined (the return value of console.log).
In your second example, you're converting rejections into resolutions with undefined (by returning the result of console.log out of catch). A catch handler that doesn't throw or return a promise that's rejected converts a rejection into a resolution.
One of the key things about promise chains is that they transform the result; every call to then or catch creates a new promise, and their handlers can modify what's sent downstream as the result passes through them.
The usual pattern would indeed be to return the result of the chain, but for the functions in the chain to either intentionally transform the result or pass it on. Normally, you wouldn't have a catch handler except at the terminal end of the chain, unless you're using it to correct the error condition (intentionally converting a rejection into a resolution).
If you wanted to just log what passed through while still allowing callers to see it but did want to return the result of the chain for whatever reason, you'd do this:
return request
.then(result => { console.log(result); return result; })
.catch(error => { console.error(error); return Promise.reject(error); });
or using throw:
return request
.then(result => { console.log(result); return result; })
.catch(error => { console.error(error); throw error; });