I use a module called Puppeteer.
I tried waiting for a selector on my page that may not appear. Out of the two approaches I took, only the try-catch method worked.
try-catch block - working
try {
await page.waitForSelector('.element');
//element appeared
} catch (error) {
//element did not appear
}
promise chaining - not working
await page.waitForSelector('.element')
.catch((error) => {
//element did not appear
})
.then(() => {
//element appeared
});
It seems that waitForSelector does return a Promise as indicated in the API, but I can't figure why the latter approach didn't work. It threw the error anyway.
Have anyone encountered the same issue?
You should restructure your Promise Chaining example to use the then() method before the catch() method.
Consider the following example using page.waitForSelector():
// Correct Method
await page.waitForSelector('#example').then(() => {
console.log('SUCCESS');
}).catch(e => {
console.log('FAIL');
});
If the element does not exist, then FAIL will be logged to the console. Otherwise, if the element does exist, the output will be SUCCESS.
On the other hand, take a look at the example below in which then() and catch() are reversed:
// Incorrect Method
await page.waitForSelector('#example').catch(e => {
console.log('FAIL');
}).then(() => {
console.log('SUCCESS - not necessarily');
});
If the element does not exist, then FAIL will be logged to the console, but regardless of whether the element exists or not, SUCCESS will also be written to the console. This is because logging SUCCESS is the next immediate step in the chain after attempting to catch an error.
Using then() before catch() will allow you to print one of two messages and achieve your desired result.
Related
I have a function which gets given a list of ids and then maps over that list and calls an API for each one:
let fullDetails: Array<any> = [];
ids.map(async (id) => {
try {
const result = await api.getFullDetails(id);
if (result.data) fullDetails.push(result.data);
} catch {
// Handle error
}
});
The problem I'm having is that sometimes the getFullDetails function will return an error just because the record it's looking for doesn't exist. But, I don't really care if the record doesn't exist (to be honest, I don't really care about any errors here) - I'm happy to just skip that one and move on to the next. No matter what I do, though, my code seems to bail out at that point if the result is an error.
I've tried leaving out the try-catch block, but then I get a 'Possible unhandled Promise rejection' error and fullDetails remains empty (although I know for sure that one of the ids worked ok).
I also tried rewriting to use Promise.all, like this:
let results = ids.map((id) =>
api.getFullDetails(id),
);
Promise.all(results)
.then((result) => {
console.log(result);
})
.catch((error) => { console.log('Error')});
but again, it goes into the catch block if there's any kind of error. Again I tried leaving out the catch block here, but then I got the 'Possible unhandled Promise rejection' error again, and the result was never shown.
Is there a way to handle this (apart form rewriting the API to not return an error)? Basically I just don't want to check for errors at all, and just ignore them if they occur.
The proper way to use map with async is to use one of the Promise's methods.
It's better to choose Promise.allSettled() rather than Promise.all() in your case. Because, according to MDN,
In comparison, the Promise returned by Promise.all() may be more appropriate if the tasks are dependent on each other, or if you'd like to immediately reject upon any of them rejecting.
Meaning the former case won't reject promises and stop the program execution.
Note that the allSettled() returns an object with two of this three properties:
[{
reason: "Id must be non-negative",
status: "rejected"
}, {
status: "fulfilled",
value: 2
}]
The first case happens when the Promise is rejected and the second when it's fullfiled.
Promise.allSettled(ids.map((id) => api.getFullDetails(id))).then(x => {
let fullDetails = x.map(({value})=>value).filter(Boolean)
console.log(fullDetails)
})
As seen in this working example.
I had a typo on the .then block of a promise and the promise kept failing. I suppose I didn't realize if there was a type it would go to .catch. It took quite a bit of digging to figure out that was the mistake (kept assuming it was something wrong with the promise/async/etc call.)
Is there a way to get JS to tell me "hey, there's a mistake in your .then block!"
code
searchAPI(name)
.then(data => {
// typo was LowerCase instead of toLowerCase
let filtereddowndata = data
.filter(item =>
item.title.toLowerCase().includes(name.LowerCase())
)
etc etc
})
.catch(function() {
console.log("no match found"); // kept going here.
});
The actual error sent to the .catch() (which you were ignoring in your code) would have given you a good clue why the .catch() was being triggered. Use something like this:
.catch(function(e) {
console.log(e);
// any other processing code here
});
I always make sure to log the actual error in my .catch() statements so I can always see exactly why it got triggered and don't blindly assume anything about how the code got here.
This is also why node.js makes it a console warning (and eventually a run-time error) when you don't have any .catch() because the try/catch built into .then() will hide errors from you if you don't expose them yourself in a .catch().
The above would have been enough to give you the precise error in this case. But there are other situations where (for debugging purposes) you sometimes benefit from inserting your own try/catch statements around more localized areas of your code. That would have also shown you what was happening in your .then() handler.
You can run this snippet to see the actual error.
// simulate your searchAPI() call
function searchAPI() {
return new Promise(resolve => {
resolve([{title: "Superman"}, {title: "Spiderman"}]);
});
}
let name = "Joe";
searchAPI(name).then(data => {
// typo was LowerCase instead of toLowerCase
let filtereddowndata = data.filter(item =>
item.title.toLowerCase().includes(name.LowerCase())
);
}).catch(function(e) {
// console.log(e) will work with normal Javascript
// here in a stackoverflow snippet where console.log has been replaced
// you have to look at console.log(e.message) to see the error
console.log("searchAPI failed - ", e.message);
});
I can only emphasise #jfriend's answer that you should always reject with proper error messages and log them.
However, it is also important to understand how promises get rejected and which .catch() callbacks will handle rejections from where. It's possible to better differentiate error origins by not using the traditional .then(…).catch(…) pattern, and even tag each error with the function it's coming from.
In your case, the error message "no match found" was misplaced as it implies that searchAPI failed, while that's not the only reason for the catch handler to be reached. Instead, the better pattern would be
function searchAPI(name) {
…
return Promise.reject(new Error("No match found"));
// ^^^^^^^^^^^^^^^^ error message (or error code)
} // thrown exactly where the error actually occurred
searchAPI(name).then(data => {
let filtereddowndata = data.filter(item =>
item.title.toLowerCase().includes(name.LowerCase())
)
…
}, err => { // <- second `then` argument
console.log("Error from searchAPI", err);
});
// would now cause an unhandled rejection about the LowerCase method call
You can combine this with a catch handler of course:
searchAPI(name).then(data => {
let filtereddowndata = data.filter(item =>
item.title.toLowerCase().includes(name.LowerCase())
)
…
}, err => { // <- second `then` argument
console.log("Error from searchAPI", err);
}).catch(err => {
console.error("Error from promise callback", err);
});
Once a promise reject() callback is called, a warning message "Uncaught (in promise)" appears in the Chrome console. I can't wrap my head around the reason behind it, nor how to get rid of it.
var p = new Promise((resolve, reject) => {
setTimeout(() => {
var isItFulfilled = false
isItFulfilled ? resolve('!Resolved') : reject('!Rejected')
}, 1000)
})
p.then(result => console.log(result))
p.catch(error => console.log(error))
Warning:
Edit:
I found out that if the onRejected handler is not explicitly provided to the .then(onResolved, onRejected) method, JS will automatically provide an implicit one. It looks like this: (err) => throw err. The auto generated handler will throw in its turn.
Reference:
If IsCallable(onRejected)` is false, then
Let onRejected be "Thrower".
http://www.ecma-international.org/ecma-262/6.0/index.html#sec-performpromisethen
This happens because you do not attach a catch handler to the promise returned by the first then method, which therefore is without handler for when the promise rejects. You do have one for the promise p in the last line, but not for the chained promise, returned by the then method, in the line before it.
As you correctly added in comments below, when a catch handler is not provided (or it's not a function), the default one will throw the error. Within a promise chain this error can be caught down the line with a catch method callback, but if none is there, the JavaScript engine will deal with the error like with any other uncaught error, and apply the default handler in such circumstances, which results in the output you see in the console.
To avoid this, chain the .catch method to the promise returned by the first then, like this:
p.then( result => console.log('Fulfilled'))
.catch( error => console.log(error) );
Even if you use Promises correctly: p.then(p1).catch(p2) you can still get an uncaught exception if your p2 function eventually throws an exception which you intend to catch using a mechanism like window.onerror. The reason is that the stack has already been unwound by the error handling done in the promise. To fix this, make sure that your error code (called by the reject function) does not throw an exception. It should simply return.
It would be nice if the error handling code could detect that the stack has already been unwound (so your error call doesn't have to have a flag for this case), and if anyone knows how to do this easily I will edit this answer to include that explanation.
This code does not cause the "uncaught in promise" exception:
// Called from top level code;
// implicitly returns a Promise
testRejectCatch = async function() {
// Nested within testRejectCatch;
// simply rejects immediately
let testReject = function() {
return new Promise(function(resolve, reject) {
reject('test the reject');
)};
}
//***********************************************
// testRejectCatch entry.
//***********************************************
try {
await testReject(); // implicitly throws reject exception
catch(error) {
// somecode
}
//***********************************************
// top level code
//***********************************************
try{
testRejectCatch() // Promise implicitly returned,
.catch((error) => { // so we can catch
window.alert('Report error: ' + error);
// must not throw error;
});
}
catch(error) {
// some code
}
Explanation:
First, there's a terminology problem. The term "catch" is
used in two ways: in the try-catches, and in the Promises.
So, it's easy to get confused about a "throw"; is it throwing
to a try's catch or to a Promise's catch?
Answer: the reject in testReject is throwing to the Promise's
implicit catch, at await testReject; and then throwing on to
the .catch at testRejectCatch().
In this context, try-catch is irrelevant and ignored;
the throws have nothing to do with them.
The .catch at testRejectCatch satisfies the requirement
that the original throw must be caught somewhere,
so you do not suffer the "uncaught in Promise..." exception.
The takeaway: throws from Promises are throws to .catch,
not to try-catch; and must be dealt-with in some .catch
Edit:
In the above code, the reject propagates up through the .catches.
If you want, you can convert over to propagating up the try-catches.
At line 17, change the code to:
let bad = '';
await testReject().catch((error) => {bad = error});
if (bad) throw bad;
Now, you've switched over to the try-catch.
I ran into this issue, but without setTimeout().
In case anyone else runs into this: if in the Promise constructor you just call reject() synchronously, then it doesn't matter how many .then() and .catch() handlers you add to the returned Promise, they won't prevent an uncaught promise rejection, because the promise rejection would happen before you
I've solved that problem in my project, it's a large enterprise one. My team is too lazy to write empty catch hundreds of times.
Promise.prototype.then = function (onFulfilled, onRejected) {
return baseThen.call(this, (x: any) => {
if (onFulfilled)
onFulfilled(x);
}, (x: any) => {
if (onRejected)
onRejected(x);
});
};
When making a GraphQL query, and the query fails, Apollo solves this by having a data-object and an error-object.
When an async error is happening, we get the same functionality with one data-object and one error-object. But, this time we get an UnhandledPromiseRejectionWarning too, with information about: 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..
So, we obviously need to solve this, but we want our async-functions to cast errors all the way up to Apollo. Do we need to try...catch all functions and just pass our error further up the tree? Coming from C#, were an exception just goes all the way to the top if never caught, it sounds like a tedious job to tell Apollo GraphQL that one (or more) leaves failed to retrieve data from the database.
Is there a better way to solve this, or is there any way to tell javascript/node that an uncaught error should be passed further up the call tree, until it's caught?
If you correctly chain your promises, you should never see this warning and all of your errors will be caught by GraphQL. Assume we have these two functions that return a Promise, the latter of which always rejects:
async function doSomething() {
return
}
async function alwaysReject() {
return Promise.reject(new Error('Oh no!'))
}
First, some correct examples:
someField: async () => {
await alwaysReject()
await doSomething()
},
// Or without async/await syntax
someField: () => {
return alwaysReject()
.then(() => {
return doSomething()
})
// or...
return alwaysReject().then(doSomething)
},
In all of these cases, you'll see the error inside the errors array and no warning in your console. We could reverse the order of the functions (calling doSomething first) and this would still be the case.
Now, let's break our code:
someField: async () => {
alwaysReject()
await doSomething()
},
someField: () => {
alwaysReject() // <-- Note the missing return
.then(() => {
return doSomething()
})
},
In these examples, we're firing off the function, but we're not awaiting the returned Promise. That means execution of our resolver continues. If the unawaited Promise resolves, there's nothing we can do with its result -- if it rejects, there's nothing we can do about the error (it's unhandled, as the warning indicates).
In general, you should always ensure your Promises are chained correctly as shown above. This is significantly easier to do with async/await syntax, since it's exceptionally easy to miss a return without it.
What about side effects?
There may be functions that return a Promise that you want to run, but don't want to pause your resolver's execution for. Whether the Promise resolves or returns is irrelevant to what your resolver returns, you just need it to run. In these cases, we just need a catch to handle the promise being rejected:
someField: async () => {
alwaysReject()
.catch((error) => {
// Do something with the error
})
await doSomething()
},
Here, we call alwaysReject and execution continues onto doSomething. If alwaysReject eventually rejects, the error will be caught and no warning will be shown in the console.
Note: These "side effects" are not awaited, meaning GraphQL execution will continue and could very well finish while they are still running. There's no way to include errors from side effects inside your GraphQL response (i.e. the errors array), at best you can just log them. If you want a particular Promise's rejection reason to show up in the response, you need to await it inside your resolver instead of treating it like a side effect.
A final word on try/catch and catch
When dealing with Promises, we often see errors caught after our function call, for example:
try {
await doSomething()
} catch (error) {
// handle error
}
return doSomething.catch((error) => {
//handle error
})
This is important inside a synchronous context (for example, when building a REST api with express). Failing to catch rejected promises will result in the familiar UnhandledPromiseRejectionWarning. However, because GraphQL's execution layer effectively functions as one giant try/catch, it's not really necessary to catch your errors as long as your Promises are chained/awaited properly. This is true unless A) you're dealing with side effects as already illustrated, or B) you want to prevent the error from bubbling up:
try {
// execution halts because we await
await alwaysReject()
catch (error) {
// error is caught, so execution will continue (unless I throw the error)
// because the resolver itself doesn't reject, the error won't be bubbled up
}
await doSomething()
I had a look at the bluebird promise FAQ, in which it mentions that .then(success, fail) is an antipattern. I don't quite understand its explanation as for the try and catch.
What's wrong with the following?
some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })
It seems that the example is suggesting the following to be the correct way.
some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })
What's the difference?
What's the difference?
The .then() call will return a promise that will be rejected in case the callback throws an error. This means, when your success logger fails, the error would be passed to the following .catch() callback, but not to the fail callback that goes alongside success.
Here's a control flow diagram:
To express it in synchronous code:
// some_promise_call().then(logger.log, logger.log)
then: {
try {
var results = some_call();
} catch(e) {
logger.log(e);
break then;
} // else
logger.log(results);
}
The second log (which is like the first argument to .then()) will only be executed in the case that no exception happened. The labelled block and the break statement feel a bit odd, this is actually what python has try-except-else for (recommended reading!).
// some_promise_call().then(logger.log).catch(logger.log)
try {
var results = some_call();
logger.log(results);
} catch(e) {
logger.log(e);
}
The catch logger will also handle exceptions from the success logger call.
So much for the difference.
I don't quite understand its explanation as for the try and catch
The argument is that usually, you want to catch errors in every step of the processing and that you shouldn't use it in chains. The expectation is that you only have one final handler which handles all errors - while, when you use the "antipattern", errors in some of the then-callbacks are not handled.
However, this pattern is actually very useful: When you want to handle errors that happened in exactly this step, and you want to do something entirely different when no error happened - i.e. when the error is unrecoverable. Be aware that this is branching your control flow. Of course, this is sometimes desired.
What's wrong with the following?
some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })
That you had to repeat your callback. You rather want
some_promise_call()
.catch(function(e) {
return e; // it's OK, we'll just log it
})
.done(function(res) {
logger.log(res);
});
You also might consider using .finally() for this.
The two aren't quite identical. The difference is that the first example won't catch an exception that's thrown in your success handler. So if your method should only ever return resolved promises, as is often the case, you need a trailing catch handler (or yet another then with an empty success parameter). Sure, it may be that your then handler doesn't do anything that might potentially fail, in which case using one 2-parameter then could be fine.
But I believe the point of the text you linked to is that then is mostly useful versus callbacks in its ability to chain a bunch of asynchronous steps, and when you actually do this, the 2-parameter form of then subtly doesn't behave quite as expected, for the above reason. It's particularly counterintuitive when used mid-chain.
As someone who's done a lot of complex async stuff and bumped into corners like this more than I care to admit, I really recommend avoiding this anti-pattern and going with the separate handler approach.
By looking at advantages and disadvantages of both we can make a calculated guess as to which is appropriate for the situation.
These are the two main approaches to implementing promises. Both have it's pluses and minus
Catch Approach
some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })
Advantages
All errors are handled by one catch block.
Even catches any exception in the then block.
Chaining of multiple success callbacks
Disadvantages
In case of chaining it becomes difficult to show different error messages.
Success/Error Approach
some_promise_call()
.then(function success(res) { logger.log(res) },
function error(err) { logger.log(err) })
Advantages
You get fine grained error control.
You can have common error handling function for various categories of errors like db error, 500 error etc.
Disavantages
You will still need another catch if you wish to handler errors thrown by the success callback
Simple explain:
In ES2018
When the catch method is called with argument onRejected, the
following steps are taken:
Let promise be the this value.
Return ? Invoke(promise, "then", « undefined, onRejected »).
that means:
promise.then(f1).catch(f2)
equals
promise.then(f1).then(undefiend, f2)
Using .then().catch() lets you enable Promise Chaining which is required to fulfil a workflow. You may need to read some information from database then you want to pass it to an async API then you want to manipulate the response. You may want to push the response back into the database. Handling all these workflows with your concept is doable but very hard to manage. The better solution will be then().then().then().then().catch() which receives all errors in just once catch and lets you keep the maintainability of the code.
Using then() and catch() helps chain success and failure handler on the promise.catch() works on promise returned by then(). It handles,
If promise was rejected. See #3 in the picture
If error occurred in success handler of then(), between line numbers 4 to 7 below. See #2.a in the picture
(Failure callback on then() does not handle this.)
If error occurred in failure handler of then(), line number 8 below. See #3.b in the picture.
1. let promiseRef: Promise = this. aTimetakingTask (false);
2. promiseRef
3. .then(
4. (result) => {
5. /* successfully, resolved promise.
6. Work on data here */
7. },
8. (error) => console.log(error)
9. )
10. .catch( (e) => {
11. /* successfully, resolved promise.
12. Work on data here */
13. });
Note: Many times, failure handler might not be defined if catch() is
written already.
EDIT: reject() result in invoking catch() only if the error
handler in then() is not defined. Notice #3 in the picture to
the catch(). It is invoked when handler in line# 8 and 9 are not
defined.
It makes sense because promise returned by then() does not have an error if a callback is taking care of it.
Instead of words, good example. Following code (if first promise resolved):
Promise.resolve()
.then
(
() => { throw new Error('Error occurs'); },
err => console.log('This error is caught:', err)
);
is identical to:
Promise.resolve()
.catch
(
err => console.log('This error is caught:', err)
)
.then
(
() => { throw new Error('Error occurs'); }
)
But with rejected first promise, this is not identical:
Promise.reject()
.then
(
() => { throw new Error('Error occurs'); },
err => console.log('This error is caught:', err)
);
Promise.reject()
.catch
(
err => console.log('This error is caught:', err)
)
.then
(
() => { throw new Error('Error occurs'); }
)