Related
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);
});
};
I am have a problem understanding why rejections are not passed on through a promise chain and I am hoping someone will be able to help me understand why. To me, attaching functionality to a chain of promises implies an intent that I am depending on an original promise to be fulfilled. It's hard to explain, so let me show a code example of my problem first. (Note: this example is using Node and the deferred node module. I tested this with Dojo 1.8.3 and had the same results)
var d = require("deferred");
var d1 = d();
var promise1 = d1.promise.then(
function(wins) { console.log('promise1 resolved'); return wins;},
function(err) { console.log('promise1 rejected'); return err;});
var promise2 = promise1.then(
function(wins) { console.log('promise2 resolved'); return wins;},
function(err) { console.log('promise2 rejected'); return err;});
var promise3 = promise2.then(
function(wins) { console.log('promise3 resolved'); return wins;},
function(err) { console.log('promise3 rejected'); return err;});
d1.reject(new Error());
The results of running this operation is this output:
promise1 rejected
promise2 resolved
promise3 resolved
Okay, to me, this result doesn't make sense. By attaching to this promise chain, each then is implying the intent that it will be dependant upon the successful resolution of d1 and a result being passed down the chain. If the promise in promise1 doesn't receive the wins value, but instead gets an err value in its error handler, how is it possible for the next promise in the chain to have its success function called? There is no way it can pass on a meaningful value to the next promise because it didn't get a value itself.
A different way I can describe what I'm thinking is: There are three people, John, Ginger, and Bob. John owns a widget shop. Ginger comes into his shop and requests a bag of widgets of assorted colours. He doesn't have them in stock, so he sends in a request to his distributor to get them shipped to him. In the mean time, he gives Ginger a rain check stating he owes her the bag of widgets. Bob finds out Ginger is getting the widgets and requests that he get the blue widget when she's done with them. She agrees and gives him a note stating she will. Now, John's distributor can't find any widgets in their supply and the manufacturer doesn't make them any more, so they inform John, who in turn informs Ginger she can't get the widgets. How is Bob able to get a blue widget from Ginger when didn't get any herself?
A third more realistic perspective I have on this issue is this. Say I have two values I want updated to a database. One is dependant on the id of the other, but I can't get the id until I have already inserted it into a database and obtained the result. On top of that, the first insert is dependant on a query from the database. The database calls return promises that I use to chain the two calls into a sequence.
var promise = db.query({parent_id: value});
promise.then(function(query_result) {
var first_value = {
parent_id: query_result[0].parent_id
}
var promise = db.put(first_value);
promise.then(function(first_value_result) {
var second_value = {
reference_to_first_value_id: first_value_result.id
}
var promise = db.put(second_value);
promise.then(function(second_value_result) {
values_successfully_entered();
}, function(err) { return err });
}, function(err) { return err });
}, function(err) { return err });
Now, in this situation, if the db.query failed, it would call the err function of the first then. But then it would call the success function of the next promise. While that promise is expecting the results of the first value, it would instead get the error message from its error handler function.
So, my question is, why would I have an error handing function if I have to test for errors in my success function?
Sorry for the length of this. I just didn't know how to explain it another way.
UPDATE and correction
(Note: I removed a response I had once made to some comments. So if anyone commented on my response, their comments might seem out of context now that I removed it. Sorry for this, I am trying to keep this as short as possible.)
Thank you everybody who replied. I would like to first apologize to everybody for writing out my question so poorly, especially my pseudo code. I was a little too aggressive in trying to keep it short.
Thanks to Bergi's response, I think I found the error in my logic. I think I might have overlooked another issue that was causing the problem I was having. This is possibly causing the promise chain work differently than I thought it should. I am still testing different elements of my code, so I can't even form a proper question to see what I'm doing wrong yet. I did want to update you all though and thank you for your help.
To me, this result doesn't make sense. By attaching to this promise chain, each then is implying the intent that it will be dependant upon the successful resolution of d1 and a result being passed down the chain
No. What you are describing is not a chain, but just attaching all the callbacks to d1. Yet, if you want to chain something with then, the result for promise2 is dependent on the resolution of promise1 and how the then callbacks handled it.
The docs state:
Returns a new promise for the result of the callback(s).
The .then method is usually looked upon in terms of the Promises/A specification (or the even stricter Promsises/A+ one). That means the callbacks shell return promises which will be assimilated to become the resolution of promise2, and if there is no success/error handler the respective result will in case be passed directly to promise2 - so you can simply omit the handler to propagate the error.
Yet, if the error is handled, the resulting promise2 is seen as fixed and will be fulfilled with that value. If you don't want that, you would have to re-throw the error, just like in a try-catch clause. Alternatively you can return a (to-be-)rejected promise from the handler. Not sure what Dojo way to reject is, but:
var d1 = d();
var promise1 = d1.promise.then(
function(wins) { console.log('promise1 resolved'); return wins;},
function(err) { console.log('promise1 rejected'); throw err;});
var promise2 = promise1.then(
function(wins) { console.log('promise2 resolved'); return wins;},
function(err) { console.log('promise2 rejected'); throw err;});
var promise3 = promise2.then(
function(wins) { console.log('promise3 resolved'); return wins;},
function(err) { console.log('promise3 rejected'); throw err;});
d1.reject(new Error());
How is Bob able to get a blue widget from Ginger when didn't get any herself?
He should not be able. If there are no error handlers, he will just perceive the message (((from the distributor) from John) from Ginger) that there are no widgets left. Yet, if Ginger sets up an error handler for that case, she still might fulfill her promise to give Bob a widget by giving him a green one from her own shack if there are no blue ones left at John or his distributor.
To translate your error callbacks into the metapher, return err from the handler would just be like saying "if there are no widgets left, just give him the note that there are no ones left - it's as good as the desired widget".
In the database situation, if the db.query failed, it would call the err function of the first then
…which would mean that the error is handled there. If you don't do that, just omit the error callback. Btw, your success callbacks don't return the promises they are creating, so they seem to be quite useless. Correct would be:
var promise = db.query({parent_id: value});
promise.then(function(query_result) {
var first_value = {
parent_id: query_result[0].parent_id
}
var promise = db.put(first_value);
return promise.then(function(first_value_result) {
var second_value = {
reference_to_first_value_id: first_value_result.id
}
var promise = db.put(second_value);
return promise.then(function(second_value_result) {
return values_successfully_entered();
});
});
});
or, since you don't need the closures to access result values from previous callbacks, even:
db.query({parent_id: value}).then(function(query_result) {
return db.put({
parent_id: query_result[0].parent_id
});
}).then(function(first_value_result) {
return db.put({
reference_to_first_value_id: first_value_result.id
});
}.then(values_successfully_entered);
#Jordan firstly as commenters noted, when using deferred lib, your first example definitely produces result you expect:
promise1 rejected
promise2 rejected
promise3 rejected
Secondly, even if it would produce output you suggest, it wouldn't affect execution flow of your second snippet, which is a bit different, more like:
promise.then(function(first_value) {
console.log('promise1 resolved');
var promise = db.put(first_value);
promise.then(function (second_value) {
console.log('promise2 resolved');
var promise = db.put(second_value);
promise.then(
function (wins) { console.log('promise3 resolved'); },
function (err) { console.log('promise3 rejected'); return err; });
}, function (err) { console.log('promise2 rejected'); return err;});
}, function (err) { console.log('promise1 rejected'); return err});
and that, in case of first promise being rejected will just output:
promise1 rejected
However (getting to the most interesting part) even though deferred library definitely returns 3 x rejected, most of other promise libraries will return 1 x rejected, 2 x resolved (that leads to assumption you got those results by using some other promise library instead).
What's additionally confusing, those other libraries are more correct with their behavior. Let me explain.
In a sync world counterpart of "promise rejection" is throw. So semantically, async deferred.reject(new Error()) in sync equals to throw new Error().
In your example you're not throwing errors in your sync callbacks, you just returning them, therefore you switch to success flow, with an error being a success value. To make sure rejection is passed further, you need to re-throw your errors:
function (err) { console.log('promise1 rejected'); throw err; });
So now question is, why do deferred library took returned error as rejection?
Reason for that, is that rejection in deferred works a bit different. In deferred lib the rule is: promise is rejected when it's resolved with an instance of error, so even if you do deferred.resolve(new Error()) it will act as deferred.reject(new Error()), and if you try to do deferred.reject(notAnError) it will throw an exception saying, that promise can be rejected only with instance of error. That makes clear why error returned from then callback rejects the promise.
There is some valid reasoning behind deferred logic, but still it's not on par with how throw works in JavaScript, and due to that this behavior is scheduled for change with version v0.7 of deferred.
Short summary:
To avoid confusion and unexpected results just follow the good practice rules:
Always reject your promises with an error instances (follow rules of sync world, where throwing value that's not an error is considered a bad practice).
Reject from sync callbacks by throwing errors (returning them doesn't guarantee rejection).
Obeying to above, you'll get both consistent and expected results in both deferred and other popular promise libraries.
Use can wrap the errors at each level of the Promise. I chained the errors in TraceError:
class TraceError extends Error {
constructor(message, ...causes) {
super(message);
const stack = Object.getOwnPropertyDescriptor(this, 'stack');
Object.defineProperty(this, 'stack', {
get: () => {
const stacktrace = stack.get.call(this);
let causeStacktrace = '';
for (const cause of causes) {
if (cause.sourceStack) { // trigger lookup
causeStacktrace += `\n${cause.sourceStack}`;
} else if (cause instanceof Error) {
causeStacktrace += `\n${cause.stack}`;
} else {
try {
const json = JSON.stringify(cause, null, 2);
causeStacktrace += `\n${json.split('\n').join('\n ')}`;
} catch (e) {
causeStacktrace += `\n${cause}`;
// ignore
}
}
}
causeStacktrace = causeStacktrace.split('\n').join('\n ');
return stacktrace + causeStacktrace;
}
});
// access first error
Object.defineProperty(this, 'cause', {value: () => causes[0], enumerable: false, writable: false});
// untested; access cause stack with error.causes()
Object.defineProperty(this, 'causes', {value: () => causes, enumerable: false, writable: false});
}
}
Usage
throw new TraceError('Could not set status', srcError, ...otherErrors);
Output
Functions
TraceError#cause - first error
TraceError#causes - list of chained errors
a simple explanation from here:
In a regular try..catch we can analyze the error and maybe rethrow it if it can’t be handled. The same thing is possible for promises.
If we throw inside .catch, then the control goes to the next closest error handler. But if we handle the error and finish normally, then it continues to the next closest successful .then handler.
In the example below the .catch successfully handles the error:
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) {
alert("The error is handled, continue normally");
}).then(() => alert("Next successful handler runs"));
Here the catch block finishes normally. So the next successful then handler is called.
note that we may have as many .then handlers as we want, and then use a single .catch at the end to handle errors in all of them.
If you have mid catch blocks and you want to break the next chain functions for errors, you shall re-throw the errors inside the catch blocks to signal this error is not handled completely.
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) { // (*) first catch
if (error instanceof URIError) { //just as example
// handle it...
} else {
alert("Can't handle such error");
throw error; // throwing this jumps to the next catch
}
}).then(function() {
// our error is other than URIError, so:
// the code doesn't reach here (jump to next catch)
}).catch(error => { // (**) second catch
alert(`The unknown error has occurred: ${error}`);
// don't return anything => execution goes the normal way
});
In the above example we see the first catch (*) will catch the error but can’t handle it (e.g. it only knows how to handle URIError), so it throws it again. The execution jumps from the first catch (*) to the next one (**) down the chain.
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'); }
)
I am have a problem understanding why rejections are not passed on through a promise chain and I am hoping someone will be able to help me understand why. To me, attaching functionality to a chain of promises implies an intent that I am depending on an original promise to be fulfilled. It's hard to explain, so let me show a code example of my problem first. (Note: this example is using Node and the deferred node module. I tested this with Dojo 1.8.3 and had the same results)
var d = require("deferred");
var d1 = d();
var promise1 = d1.promise.then(
function(wins) { console.log('promise1 resolved'); return wins;},
function(err) { console.log('promise1 rejected'); return err;});
var promise2 = promise1.then(
function(wins) { console.log('promise2 resolved'); return wins;},
function(err) { console.log('promise2 rejected'); return err;});
var promise3 = promise2.then(
function(wins) { console.log('promise3 resolved'); return wins;},
function(err) { console.log('promise3 rejected'); return err;});
d1.reject(new Error());
The results of running this operation is this output:
promise1 rejected
promise2 resolved
promise3 resolved
Okay, to me, this result doesn't make sense. By attaching to this promise chain, each then is implying the intent that it will be dependant upon the successful resolution of d1 and a result being passed down the chain. If the promise in promise1 doesn't receive the wins value, but instead gets an err value in its error handler, how is it possible for the next promise in the chain to have its success function called? There is no way it can pass on a meaningful value to the next promise because it didn't get a value itself.
A different way I can describe what I'm thinking is: There are three people, John, Ginger, and Bob. John owns a widget shop. Ginger comes into his shop and requests a bag of widgets of assorted colours. He doesn't have them in stock, so he sends in a request to his distributor to get them shipped to him. In the mean time, he gives Ginger a rain check stating he owes her the bag of widgets. Bob finds out Ginger is getting the widgets and requests that he get the blue widget when she's done with them. She agrees and gives him a note stating she will. Now, John's distributor can't find any widgets in their supply and the manufacturer doesn't make them any more, so they inform John, who in turn informs Ginger she can't get the widgets. How is Bob able to get a blue widget from Ginger when didn't get any herself?
A third more realistic perspective I have on this issue is this. Say I have two values I want updated to a database. One is dependant on the id of the other, but I can't get the id until I have already inserted it into a database and obtained the result. On top of that, the first insert is dependant on a query from the database. The database calls return promises that I use to chain the two calls into a sequence.
var promise = db.query({parent_id: value});
promise.then(function(query_result) {
var first_value = {
parent_id: query_result[0].parent_id
}
var promise = db.put(first_value);
promise.then(function(first_value_result) {
var second_value = {
reference_to_first_value_id: first_value_result.id
}
var promise = db.put(second_value);
promise.then(function(second_value_result) {
values_successfully_entered();
}, function(err) { return err });
}, function(err) { return err });
}, function(err) { return err });
Now, in this situation, if the db.query failed, it would call the err function of the first then. But then it would call the success function of the next promise. While that promise is expecting the results of the first value, it would instead get the error message from its error handler function.
So, my question is, why would I have an error handing function if I have to test for errors in my success function?
Sorry for the length of this. I just didn't know how to explain it another way.
UPDATE and correction
(Note: I removed a response I had once made to some comments. So if anyone commented on my response, their comments might seem out of context now that I removed it. Sorry for this, I am trying to keep this as short as possible.)
Thank you everybody who replied. I would like to first apologize to everybody for writing out my question so poorly, especially my pseudo code. I was a little too aggressive in trying to keep it short.
Thanks to Bergi's response, I think I found the error in my logic. I think I might have overlooked another issue that was causing the problem I was having. This is possibly causing the promise chain work differently than I thought it should. I am still testing different elements of my code, so I can't even form a proper question to see what I'm doing wrong yet. I did want to update you all though and thank you for your help.
To me, this result doesn't make sense. By attaching to this promise chain, each then is implying the intent that it will be dependant upon the successful resolution of d1 and a result being passed down the chain
No. What you are describing is not a chain, but just attaching all the callbacks to d1. Yet, if you want to chain something with then, the result for promise2 is dependent on the resolution of promise1 and how the then callbacks handled it.
The docs state:
Returns a new promise for the result of the callback(s).
The .then method is usually looked upon in terms of the Promises/A specification (or the even stricter Promsises/A+ one). That means the callbacks shell return promises which will be assimilated to become the resolution of promise2, and if there is no success/error handler the respective result will in case be passed directly to promise2 - so you can simply omit the handler to propagate the error.
Yet, if the error is handled, the resulting promise2 is seen as fixed and will be fulfilled with that value. If you don't want that, you would have to re-throw the error, just like in a try-catch clause. Alternatively you can return a (to-be-)rejected promise from the handler. Not sure what Dojo way to reject is, but:
var d1 = d();
var promise1 = d1.promise.then(
function(wins) { console.log('promise1 resolved'); return wins;},
function(err) { console.log('promise1 rejected'); throw err;});
var promise2 = promise1.then(
function(wins) { console.log('promise2 resolved'); return wins;},
function(err) { console.log('promise2 rejected'); throw err;});
var promise3 = promise2.then(
function(wins) { console.log('promise3 resolved'); return wins;},
function(err) { console.log('promise3 rejected'); throw err;});
d1.reject(new Error());
How is Bob able to get a blue widget from Ginger when didn't get any herself?
He should not be able. If there are no error handlers, he will just perceive the message (((from the distributor) from John) from Ginger) that there are no widgets left. Yet, if Ginger sets up an error handler for that case, she still might fulfill her promise to give Bob a widget by giving him a green one from her own shack if there are no blue ones left at John or his distributor.
To translate your error callbacks into the metapher, return err from the handler would just be like saying "if there are no widgets left, just give him the note that there are no ones left - it's as good as the desired widget".
In the database situation, if the db.query failed, it would call the err function of the first then
…which would mean that the error is handled there. If you don't do that, just omit the error callback. Btw, your success callbacks don't return the promises they are creating, so they seem to be quite useless. Correct would be:
var promise = db.query({parent_id: value});
promise.then(function(query_result) {
var first_value = {
parent_id: query_result[0].parent_id
}
var promise = db.put(first_value);
return promise.then(function(first_value_result) {
var second_value = {
reference_to_first_value_id: first_value_result.id
}
var promise = db.put(second_value);
return promise.then(function(second_value_result) {
return values_successfully_entered();
});
});
});
or, since you don't need the closures to access result values from previous callbacks, even:
db.query({parent_id: value}).then(function(query_result) {
return db.put({
parent_id: query_result[0].parent_id
});
}).then(function(first_value_result) {
return db.put({
reference_to_first_value_id: first_value_result.id
});
}.then(values_successfully_entered);
#Jordan firstly as commenters noted, when using deferred lib, your first example definitely produces result you expect:
promise1 rejected
promise2 rejected
promise3 rejected
Secondly, even if it would produce output you suggest, it wouldn't affect execution flow of your second snippet, which is a bit different, more like:
promise.then(function(first_value) {
console.log('promise1 resolved');
var promise = db.put(first_value);
promise.then(function (second_value) {
console.log('promise2 resolved');
var promise = db.put(second_value);
promise.then(
function (wins) { console.log('promise3 resolved'); },
function (err) { console.log('promise3 rejected'); return err; });
}, function (err) { console.log('promise2 rejected'); return err;});
}, function (err) { console.log('promise1 rejected'); return err});
and that, in case of first promise being rejected will just output:
promise1 rejected
However (getting to the most interesting part) even though deferred library definitely returns 3 x rejected, most of other promise libraries will return 1 x rejected, 2 x resolved (that leads to assumption you got those results by using some other promise library instead).
What's additionally confusing, those other libraries are more correct with their behavior. Let me explain.
In a sync world counterpart of "promise rejection" is throw. So semantically, async deferred.reject(new Error()) in sync equals to throw new Error().
In your example you're not throwing errors in your sync callbacks, you just returning them, therefore you switch to success flow, with an error being a success value. To make sure rejection is passed further, you need to re-throw your errors:
function (err) { console.log('promise1 rejected'); throw err; });
So now question is, why do deferred library took returned error as rejection?
Reason for that, is that rejection in deferred works a bit different. In deferred lib the rule is: promise is rejected when it's resolved with an instance of error, so even if you do deferred.resolve(new Error()) it will act as deferred.reject(new Error()), and if you try to do deferred.reject(notAnError) it will throw an exception saying, that promise can be rejected only with instance of error. That makes clear why error returned from then callback rejects the promise.
There is some valid reasoning behind deferred logic, but still it's not on par with how throw works in JavaScript, and due to that this behavior is scheduled for change with version v0.7 of deferred.
Short summary:
To avoid confusion and unexpected results just follow the good practice rules:
Always reject your promises with an error instances (follow rules of sync world, where throwing value that's not an error is considered a bad practice).
Reject from sync callbacks by throwing errors (returning them doesn't guarantee rejection).
Obeying to above, you'll get both consistent and expected results in both deferred and other popular promise libraries.
Use can wrap the errors at each level of the Promise. I chained the errors in TraceError:
class TraceError extends Error {
constructor(message, ...causes) {
super(message);
const stack = Object.getOwnPropertyDescriptor(this, 'stack');
Object.defineProperty(this, 'stack', {
get: () => {
const stacktrace = stack.get.call(this);
let causeStacktrace = '';
for (const cause of causes) {
if (cause.sourceStack) { // trigger lookup
causeStacktrace += `\n${cause.sourceStack}`;
} else if (cause instanceof Error) {
causeStacktrace += `\n${cause.stack}`;
} else {
try {
const json = JSON.stringify(cause, null, 2);
causeStacktrace += `\n${json.split('\n').join('\n ')}`;
} catch (e) {
causeStacktrace += `\n${cause}`;
// ignore
}
}
}
causeStacktrace = causeStacktrace.split('\n').join('\n ');
return stacktrace + causeStacktrace;
}
});
// access first error
Object.defineProperty(this, 'cause', {value: () => causes[0], enumerable: false, writable: false});
// untested; access cause stack with error.causes()
Object.defineProperty(this, 'causes', {value: () => causes, enumerable: false, writable: false});
}
}
Usage
throw new TraceError('Could not set status', srcError, ...otherErrors);
Output
Functions
TraceError#cause - first error
TraceError#causes - list of chained errors
a simple explanation from here:
In a regular try..catch we can analyze the error and maybe rethrow it if it can’t be handled. The same thing is possible for promises.
If we throw inside .catch, then the control goes to the next closest error handler. But if we handle the error and finish normally, then it continues to the next closest successful .then handler.
In the example below the .catch successfully handles the error:
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) {
alert("The error is handled, continue normally");
}).then(() => alert("Next successful handler runs"));
Here the catch block finishes normally. So the next successful then handler is called.
note that we may have as many .then handlers as we want, and then use a single .catch at the end to handle errors in all of them.
If you have mid catch blocks and you want to break the next chain functions for errors, you shall re-throw the errors inside the catch blocks to signal this error is not handled completely.
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) { // (*) first catch
if (error instanceof URIError) { //just as example
// handle it...
} else {
alert("Can't handle such error");
throw error; // throwing this jumps to the next catch
}
}).then(function() {
// our error is other than URIError, so:
// the code doesn't reach here (jump to next catch)
}).catch(error => { // (**) second catch
alert(`The unknown error has occurred: ${error}`);
// don't return anything => execution goes the normal way
});
In the above example we see the first catch (*) will catch the error but can’t handle it (e.g. it only knows how to handle URIError), so it throws it again. The execution jumps from the first catch (*) to the next one (**) down the chain.
Why can't I just throw an Error inside the catch callback and let the process handle the error as if it were in any other scope?
If I don't do console.log(err) nothing gets printed out and I know nothing about what happened. The process just ends...
Example:
function do1() {
return new Promise(function(resolve, reject) {
throw new Error('do1');
setTimeout(resolve, 1000)
});
}
function do2() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject(new Error('do2'));
}, 1000)
});
}
do1().then(do2).catch(function(err) {
//console.log(err.stack); // This is the only way to see the stack
throw err; // This does nothing
});
If callbacks get executed in the main thread, why does the Error get swallowed by a black hole?
As others have explained, the "black hole" is because throwing inside a .catch continues the chain with a rejected promise, and you have no more catches, leading to an unterminated chain, which swallows errors (bad!)
Add one more catch to see what's happening:
do1().then(do2).catch(function(err) {
//console.log(err.stack); // This is the only way to see the stack
throw err; // Where does this go?
}).catch(function(err) {
console.log(err.stack); // It goes here!
});
A catch in the middle of a chain is useful when you want the chain to proceed in spite of a failed step, but a re-throw is useful to continue failing after doing things like logging of information or cleanup steps, perhaps even altering which error is thrown.
Trick
To make the error show up as an error in the web console, as you originally intended, I use this trick:
.catch(function(err) { setTimeout(function() { throw err; }); });
Even the line numbers survive, so the link in web console takes me straight to the file and line where the (original) error happened.
Why it works
Any exception in a function called as a promise fulfillment or rejection handler gets automatically converted to a rejection of the promise you're supposed to return. The promise code that calls your function takes care of this.
A function called by setTimeout on the other hand, always runs from JavaScript stable state, i.e. it runs in a new cycle in the JavaScript event loop. Exceptions there aren't caught by anything, and make it to the web console. Since err holds all the information about the error, including the original stack, file and line number, it still gets reported correctly.
Important things to understand here
Both the then and catch functions return new promise objects.
Either throwing or explicitly rejecting, will move the current promise to the rejected state.
Since then and catch return new promise objects, they can be chained.
If you throw or reject inside a promise handler (then or catch), it will be handled in the next rejection handler down the chaining path.
As mentioned by jfriend00, the then and catch handlers are not executed synchronously. When a handler throws, it will come to an end immediately. So, the stack will be unwound and the exception would be lost. That is why throwing an exception rejects the current promise.
In your case, you are rejecting inside do1 by throwing an Error object. Now, the current promise will be in rejected state and the control will be transferred to the next handler, which is then in our case.
Since the then handler doesn't have a rejection handler, the do2 will not be executed at all. You can confirm this by using console.log inside it. Since the current promise doesn't have a rejection handler, it will also be rejected with the rejection value from the previous promise and the control will be transferred to the next handler which is catch.
As catch is a rejection handler, when you do console.log(err.stack); inside it, you are able to see the error stack trace. Now, you are throwing an Error object from it so the promise returned by catch will also be in rejected state.
Since you have not attached any rejection handler to the catch, you are not able to observe the rejection.
You can split the chain and understand this better, like this
var promise = do1().then(do2);
var promise1 = promise.catch(function (err) {
console.log("Promise", promise);
throw err;
});
promise1.catch(function (err) {
console.log("Promise1", promise1);
});
The output you will get will be something like
Promise Promise { <rejected> [Error: do1] }
Promise1 Promise { <rejected> [Error: do1] }
Inside the catch handler 1, you are getting the value of promise object as rejected.
Same way, the promise returned by the catch handler 1, is also rejected with the same error with which the promise was rejected and we are observing it in the second catch handler.
I tried the setTimeout() method detailed above...
.catch(function(err) { setTimeout(function() { throw err; }); });
Annoyingly, I found this to be completely untestable. Because it's throwing an asynchronous error, you can't wrap it inside a try/catch statement, because the catch will have stopped listening by the time error is thrown.
I reverted to just using a listener which worked perfectly and, because it's how JavaScript is meant to be used, was highly testable.
return new Promise((resolve, reject) => {
reject("err");
}).catch(err => {
this.emit("uncaughtException", err);
/* Throw so the promise is still rejected for testing */
throw err;
});
According the spec (see 3.III.d):
d. If calling then throws an exception e,
a. If resolvePromise or rejectPromise have been called, ignore it.
b. Otherwise, reject promise with e as the reason.
That means that if you throw exception in then function, it will be caught and your promise will be rejected. catch don't make a sense here, it is just shortcut to .then(null, function() {})
I guess you want to log unhandled rejections in your code. Most promises libraries fires a unhandledRejection for it. Here is relevant gist with discussion about it.
Yes promises swallow errors, and you can only catch them with .catch, as explained more in detail in other answers. If you are in Node.js and want to reproduce the normal throw behaviour, printing stack trace to console and exit process, you can do
...
throw new Error('My error message');
})
.catch(function (err) {
console.error(err.stack);
process.exit(0);
});
I know this is a bit late, but I came across this thread, and none of the solutions were easy to implement for me, so I came up with my own:
I added a little helper function which returns a promise, like so:
function throw_promise_error (error) {
return new Promise(function (resolve, reject){
reject(error)
})
}
Then, if I have a specific place in any of my promise chain where I want to throw an error (and reject the promise), I simply return from the above function with my constructed error, like so:
}).then(function (input) {
if (input === null) {
let err = {code: 400, reason: 'input provided is null'}
return throw_promise_error(err)
} else {
return noterrorpromise...
}
}).then(...).catch(function (error) {
res.status(error.code).send(error.reason);
})
This way I am in control of throwing extra errors from inside the promise-chain. If you want to also handle 'normal' promise errors, you would expand your catch to treat the 'self-thrown' errors separately.
Hope this helps, it is my first stackoverflow answer!
Listen for unhandled errors:
window.addEventListener('unhandledrejection', e => {
// ...
});
window.addEventListener('error', e => {
// ...
});
If error gets swallowed, use self.report(error):
.catch(error => {
self.reportError(error);
});
https://developer.mozilla.org/en-US/docs/Web/API/reportError