I'm trying to wrap my head around this issue I'm facing concerning async/await and Promises. I managed to boil my issue down to the following code:
async function sleep(ms: number) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
async function fetchMock(): Promise<any> {
return new Promise(() => {
throw 'error fetching result';
});
}
async function main(): Promise<any> {
const kickedOffRequest = fetchMock();
await sleep(10);
return kickedOffRequest;
}
main()
.then(() => console.log('resolved promise!'))
.catch(error => console.error('caught error!', error));
I receive the following warning:
(node:82245) UnhandledPromiseRejectionWarning: error fetching result
(node:82245) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:82245) [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.
caught error! error fetching result
(node:82245) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
You can observe the same issue in this sandbox. I noticed that commenting out the await sleep(10)
fixes the issue, but I apparently know less about promises than I thought. Why does commenting that line out make my program work? I'm tempted to ask how to fix the Promise rejection was handled asynchronously
error, but I hope that once I understand how await sleep(10)
causes the error I get I will be able to fix this one on my own.
Thanks in advance for taking the time to read/answer this question!
Inside an async function, you can use the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, at which point the fulfilled value of the promise is treated as a return value, or the rejected value is thrown.
In async functions, there is no resolve or reject function. There's return and throw , which are the idiomatic ways to resolve and reject the async function's promise.
We must always add a catch() , otherwise promises will silently fail. In this case, if thePromise is rejected, the execution jumps directly to the catch() method. You can add the catch() method in the middle of two then() methods, but you will not be able to break the chain when something bad happens.
A promise is used to handle the asynchronous result of an operation. JavaScript is designed to not wait for an asynchronous block of code to completely execute before other synchronous parts of the code can run. With Promises, we can defer the execution of a code block until an async request is completed.
The original concept of promises was that you could have a rejected promise sitting around for some time before attaching a catch handler to it. For example, Firefox used to warn of uncaught rejection errors only when a rejected promise with no rejection handler was garbage collected from memory.
Somebody decided that programmers couldn't be trusted with managing promise rejections properly and changed the HTML spec to require browsers to throw "unhandled promise rejection" errors if a rejected promise has no rejection handlers added before code returns to the event loop.
(I think unhandled rejections can survive without error in the micro task queue for a tick or two, before control returns to the event loop proper, but haven't tested it lately.)
The ECMAScript specification added an abstract means of notifying the host environment of an unhandled promise rejection without specifying what, if any, action should be taken.
On a case by case basis you can prevent the host being notified by adding a rejection handler that is never used. The reasoning is that adding a dummy rejection handler to a promise means that should it be rejected it has a rejection handler already - or if it was rejected the host is notified the promise now has a rejection handler - and you can call then
and catch
multiple times on the same promise.
Changing
async function fetchMock(){
return new Promise(() => {
throw 'error fetching result';
});
}
to
async function fetchMock(){
let promise = new Promise(() => {
throw 'error fetching result';
});
promise.catch(()=>null); // unused rejection handler
return promise;
}
should work around the unwanted HTML5 host behavior implemented in V8, the JavaScript engine used in node.
The detection of unhandled rejection in node.js is imperfect. There are specific spots in the life cycle of a rejected promise where the engine checks to see if there's a handler and it does not always wait until the last possible moment so it can miss places that you add a handler. In your specific case, you may need to attach a .catch()
handler locally, then finish up the work you want to do, then rethrow the error. This work-around will work for you while still maintaining the desired resolve/reject from main()
(e.g. without changing the interface to main).
So, this isn't particularly super pretty, but it meets the spec we talked about in comments.
main()
calls fetchMock()
fetchMock()
was originally called.fetchMock()
takes longer than that custom delay time to resolve or reject, then no further delay is added.main()
returns then follows the promise that fetchMock()
returned, either rejected or resolved with the same reason or value.The key ingredient is that it captures the time right before calling fetchMock()
and then when fetchMock()
either resolves or rejects, it decides whether to delay any more time before passing the resolve/reject value/reason on through.
function sleep(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
function fetchMock() {
return new Promise((resolve) => {
throw 'error fetching result';
//resolve('this is our result');
});
}
function handler(start, minWaitTime, isErr = false) {
return async function(val) {
let diff = minWaitTime - (Date.now() - start);
if (diff > 0) {
await sleep(diff);
}
if (isErr) {
throw val;
} else {
return val;
}
}
}
function main() {
let start = Date.now();
const minWaitTime = 1000;
return fetchMock().then(handler(start, minWaitTime), handler(start, minWaitTime, true));
}
main()
.then(() => console.log('resolved promise!'))
.catch(error => console.error('caught error!', error));
Note, also that sleep()
and fetchMock()
already directly return promises and don't use await
so there is no requirement for them to be async
.
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