Can't throw error from within an async promise executor function
I've been trying to get a conceptual understanding of why the following code doesn't catch the throw
. If you remove the async
keyword from the new Promise(async (resolve, ...
part then it works fine, so it has to do with the fact that the Promise executor is an async function.
(async function() {
try {
await fn();
} catch(e) {
console.log("CAUGHT fn error -->",e)
}
})();
function fn() {
return new Promise(async (resolve, reject) => {
// ...
throw new Error("<<fn error>>");
// ...
});
}
The answers here, here, and here repeat that "if you're in any other asynchronous callback, you must use reject
", but by "asynchronous" they're not referring to async
functions, so I don't think their explanations apply here (and if they do, I don't understand how).
If instead of throw
we use reject
, the above code works fine. I'd like to understand, fundamentally, why throw
doesn't work here. Thanks!
Solution 1:
This is the async/await version of the Promise
constructor antipattern!
Never ever use an async function
as a Promise
executor function (even when you can make it work1)!
[1: by calling resolve
and reject
instead of using return
and throw
statements]
by "asynchronous" they're not referring to
async
functions, so I don't think their explanations apply here
They could as well. A simple example where it cannot work is
new Promise(async function() {
await delay(…);
throw new Error(…);
})
which is equivalent to
new Promise(function() {
return delay(…).then(function() {
throw new Error(…);
});
})
where it's clear now that the throw
is inside an asynchronous callback.
The Promise
constructor can only catch synchronous exceptions, and an async function
never throws - it always returns a promise (which might get rejected though). And that return value is ignored, as the promise is waiting for resolve
to be called.
Solution 2:
because the only way to "communicate" to the outside world from within a Promise executor is to use the resolve
and reject
functions. You could use the following for your example:
function fn() {
return new Promise(async (resolve, reject) => {
// there is no real reason to use an async executor here since there is nothing async happening
try {
throw new Error('<<fn error>>')
} catch(error) {
return reject(error);
}
});
}
An example would be when you want to do something that has convenient async functions, but also requires a callback. The following contrived example copies a file by reading it using the async fs.promises.readFile
function with the callback based fs.writeFile
function. In the real world, you would never mix fs
functions like this because there is no need to. But some libraries like stylus and pug use callbacks, and I use something like this all the time in those scenarios.
const fs = require('fs');
function copyFile(infilePath, outfilePath) {
return new Promise(async (resolve, reject) => {
try {
// the fs.promises library provides convenient async functions
const data = await fs.promises.readFile(infilePath);
// the fs library also provides methods that use callbacks
// the following line doesn't need a return statement, because there is nothing to return the value to
// but IMO it is useful to signal intent that the function has completed (especially in more complex functions)
return fs.writeFile(outfilePath, data, (error) => {
// note that if there is an error we call the reject function
// so whether an error is thrown in the promise executor, or the callback the reject function will be called
// so from the outside, copyFile appears to be a perfectly normal async function
return (error) ? reject(error) : resolve();
});
} catch(error) {
// this will only catch errors from the main body of the promise executor (ie. the fs.promises.readFile statement
// it will not catch any errors from the callback to the fs.writeFile statement
return reject(error);
// the return statement is not necessary, but IMO communicates the intent that the function is completed
}
}
}
Apparently everyone says this is an anti-pattern, but I use it all the time when I want to do some async stuff before doing something that can only be done with a callback (not for copying files like my contrived example). I don't understand why people think it is an anti-pattern (to use an async promise executor), and haven't seen an example yet that has convinced me that it should be accepted as a general rule.