Node.js vs Browser: Chaining rejected promises - javascript

If I try this on Chrome Version 56.0.2924.87 (64-bit) - Expected result..
Promise.reject(null).then(console.log);
> Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: null}
Whereas if I try on Node v7.8.0 - Potential error?
Promise.reject(null).then(console.log);
> Promise { <pending> }
Am I doing something wrong or is this a bug? (I'm assuming the former)
I'm having trouble mocking rejected promises through a handle chain as the rejection doesn't carry through the chain :(
Looking at the MDN docs I think I've got the syntax correct: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then

Both outputs are fine. You should be getting something like Promise {<rejected>: null} when logging Promise.reject(null) in both consoles.
But what when you chain a .then(console.log) to it?
Chrome applies the rule "then() without a rejection handler returns a promise that gets rejected when the original is rejected", and it does so immediately - it already knows that the result is going to be a rejected promise
Node applies the rule "promises are always asynchronous". It returns a pending promise and creates callbacks to settle it - in this case, given the original is already rejected and no rejection handler was passed, it is immediately scheduled to reject the result promise.
Both implementations are allowed by the Promises/A+ standard, the first one might be a bit more efficient while the second one is closer to the behaviour described in the EcmaScript specification.
In practice, you won't notice a difference.

Since you are rejecting your promise, you need to either define an error handler function as the second arg:
Promise.reject(null).then(console.log, console.error);
Or use a .catch():
Promise.reject(null).catch(console.error);

Related

Difference of exception and unfulfilled promise?

I am reading about error handling, and the book says
If a throw occurs inside a Promises then handler function, then it is
a rejection. One way to think about exceptions and rejections is that exceptions are synchronous errors and rejections are asynchronous errors.
Questions
The book writes rejection, but shouldn't that be unfulfilled?
I were under the impression that a throw is always an exception?
And why are exceptions only for synchronous code?
An unfulfilled Promise is simply one that hasn't been fulfilled, which is quite possible even if it doesn't reject. For example:
const prom = new Promise((resolve) => {
// code here that never calls `resolve`
});
That is a Promise that will never be fulfilled - but it won't reject either.
An "unfulfilled" Promise may not necessarily hang forever like the above - it's simply a Promise that hasn't resolved yet.
a throw is always an exception?
To an extent, yes, though it behaves somewhat differently when inside a Promise - it won't cause an error event. Instead, it'll try to find a .catch handler in the Promise chain it's currently in, and if it doesn't find one, an unhandledrejection event will be fired.
And why are exceptions only for synchronous code?
That's just how the language was designed.
I think you need to understand the reason why rejections are needed, the problem it tries to solve. Consider a simple example such as this:
try {
iDoNotExist
} catch(e) {
//e ReferenceError
}
Pretty simple, the code is being evaluated and "tried" for errors. There's such an error and well, it gets caught.
Now, what if we changed it a little bit into this:
try {
setTimeout(function(){ iDoNotExist }, 1000);
} catch(e) {
//e ReferenceError
}
If you run this code, you'll notice that your javascript environment will emit an Error. It's not being caught here. Why? Because the code that throws the error is not being run when it's being "tried", only the setTimeout function which simply schedules the function and well, that function is doing it's job, it's scheduling it correctly. This in turn is the same reason why so many people get it wrong when they try accessing a value that's product of an async function. E.g.
let result;
setTimeout(() => result = true, 1000);
console.log(result) // Why is this undefined? - Famous last question
Enter promises to the rescue, which not only allow you to recover a value which you would typically return, it also lets you recover any potential error that happens on said async operations. Providing you with semantics that are similar with synchronous code (even more when paired with async functions).
So, I would say rejections are complementary to exceptions. To preserve your program flow when there's an asynchronous boundary (just like fulfilled promises do to preserve function composition).
As for what you would call unfulfilled well... there's a couple of things. unfulfilled means "not fulfilled". A rejected promise IS, technically, an unfulfilled promise. However, an unresolved promise (a promise in a pending state) is ALSO an unfulfilled promise. Technically.
Most people will think of the latter but really it could be either. Mostly because it comes from a time where "fulfill" and "resolve" where used interchangeably (which is wrong). But also because that's typically the expected result of a promise resolution.
Under no circumstance is an unresolved promise a rejected promise. An unresolved promise is in a transitory state in which it could transit into either a fulfilled promise or a rejected one.

ES6 Promises Error Handling

I'm currently reading "YKDJS - Async & Performance" by Kyle Simpson, in particular Chapter 3 - Promises.
The author says that any Promise for which no rejection handler is registered receives a default one:
let p = new Promise(function(resolve, reject) { resolve("Yay!"); });
p.then(
function(val) { /* Do whatever */ }
/* Default rejection handler
, function(e) { throw e; } */
);
Later in the chapter he claims that one problem with the way Promises are designed is the following:
In any Promise chain, any error that happens in one of the handler functions of the last Promise in the chain is just "swallowed" instead of being reported.
He proposes to change the way Promises work so that any Promise that doesn't have a rejection handler reports the error by throwing a global error. Then he proposes a theoretical Promise#defer() function one could use on a Promise to prevent this reporting behavior.
Now my question is: How do these two go together? It's true that any Promisethat doesn't have a rejection handler receives a default one which just throws the rejection value as a global error:
Promise.reject("Oops");
/* VM668:1 Uncaught (in promise) Oops */
So already Promises seem to work in just the way he proposes.
Am I misunderstanding something? Thanks for any help.
The Uncaught Handling he mentions in
Some Promise libraries have added methods for registering something
like a "global unhandled rejection" handler, which would be called
instead of a globally thrown error. But their solution for how to
identify an error as "uncaught" is to have an arbitrary-length timer,
say 3 seconds, running from time of rejection. If a Promise is
rejected but no error handler is registered before the timer fires,
then it's assumed that you won't ever be registering a handler, so
it's "uncaught."
In practice, this has worked well for many libraries, as most usage
patterns don't typically call for significant delay between Promise
rejection and observation of that rejection.
has been standardised as unhandled rejection warnings (not using an arbitrary timer, but firing right away). It does indeed work quite well.
He also states in Chapter 4 of ES6 & Beyond
[When] we are not listening for that rejection, […] it will be
silently held onto for future observation. If you never observe it by
calling a then(..) or catch(..), then it will go unhandled. Some
browser developer consoles may detect these unhandled rejections and
report them, but this is not reliably guaranteed; you should always
observe promise rejections.

Why it is an error to ignore a rejected promise on Chrome?

I don't want to do anything if the promise is rejected, like getPromise().then(foo=>{});. Why is it an error on Chrome?
(new Promise((resolve, reject)=>{reject()}))
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: undefined}
VM3250:2 Uncaught (in promise) undefined
On Node and Firefox, it is OK to ignore the rejected part.
Promise rejection is like uncaught exception.
if you want to ignore exception - catch it, but don't handle,
same here - add .catch statement, but do nothing
Promise.reject(new Error('bad..')).catch(e => {})
I would not however recommend that, as promises reject for a reason, so you might want to add some sort of handling logic
Without a promise reject handler, chrome would throws reason of the rejection asynchronously that doesn't effects any JavaScript process, just prints the reason to console. If you get annoyed with this, you need to add handler, e.g. Promise.reject(reason).catch(reason => {}).
UPDATE: Why is an error? It may be because rejection is conventionally considered as an exception (an error).
P.S. Your "question" look like a feedback rather than a question. I personally prefer getting the reason as an error.
Rejecting a promise basically means something bad happened, and that you should be handling it with a call to catch. If you reject a promise without having a catch, it will throw an exception (more accurately, an unhandled rejection)
var a = (new Promise((resolve, reject) => {
reject("my error")
})).catch((err) => {
console.error(err)
})
I'm guessing it is a specificity of V8 if this happens only under Chrome, but it makes sense to me

Exception gets swallowed in promise chain

I noticed something very strange happening when an exception is thrown inside a chain of promises in Parse for React Native. The promise chain never resolves, and never rejects, and the exception is never thrown. It just disappears silently.
Here's sample code to recreate the problem:
// Replacing this with Promise.resolve() prints the error.
// Removing this stage prints the error.
Parse.Promise.as()
// Removing this stage causes a red screen error.
.then(function() {
// Replacing this with Parse.Promise.as() causes a red screen error.
return Promise.resolve();
})
.then(function () {
throw new Error("There was a failure");
})
.then(function () { console.log("Success")}, function (err) { console.log(err) });
As you can see from the comments, it only seems to happen in this particular sequence of events. Removing a stage, or swapping a Parse promise for a native JS promise, causes things to behave again. (In my actual code, the "Promise.resolve()" stage is actually a call into a native iOS method that returns a promise.)
I'm aware that Parse promises don't behave exactly like A+ compliant promises (cf. https://stackoverflow.com/a/31223217/2397068). Indeed, calling Parse.Promise.enableAPlusCompliant() before this section of code causes the exception to be caught and printed. But I thought that Parse promises and native JS promises could be used together safely.
Why is this exception disappearing silently?
Thank you.
Why is this exception disappearing?
Parse does by default not catch exceptions, and promises do swallow them.
Parse is not 100% Promises/A+ compatible, but nonetheless it does try to assimilate thenables that are returned from then callbacks. And it does neither catch exceptions nor executes its own callbacks asynchronously.
What you are doing can be reproduced without then using
var p1 = new Parse.Promise();
var p2 = new Parse.Promise();
// p2 should eventually settle and call these:
p2._resolvedCallbacks = [function (res) { console.log("Success") }];
p2._rejectedCallbacks = [function (err) { console.log(err) }];
function handler() {
throw new Error("There was a failure");
}
// this is what the second `then` call sets up (much simplified):
p1._resolvedCallbacks = [function(result) {
p2.resolve(handler(result)); // throws - oops
}];
// the native promise:
var p = Promise.resolve();
// what happens when a callback result is assimilated:
if (isThenable(p))
p.then(function(result) {
p1.resolve(result);
});
The problem is that p1.resolve is synchronous, and executes the callbacks on p1 immediately - which in turn does throw. By throwing before p2.resolve can be called, p2 will stay forever pending. The exceptions bubbles up and becomes the completion of p1.resolve() - which now throws in a callback to a native then method. The native promise implementation catches the exception and rejects the promise returned by then with it, which is however ignored everywhere.
silently?
If your "native" promise implementation supports unhandled rejection warnings, you should be able to see the exception hanging around in the rejected promise.
For your consideration in addition to technical reasons provided in your quoted answer:
Compliant promises
ES6/A+ compliant Promise instances share:
When a promise is settled, its settled state and value are immutable.
A promise cannot be fulfilled with a promise.
A then registered 'fulfill' listener is never called with a promise (or other thenable) object as argument.
Listeners registered by then are executed asynchronously in their own thread after the code which caused them to be executed has run to completion.
Irrespective of whether a listener was registered for call back whan a promise becomes settled ( 'fulfilled' or 'rejected'),
the return value of a listener is used to fulfill resolve the promise returned by its then registration
a value thrown (using throw) by a listener is used to reject the promise returned by then registration, and
A promise resolved with a Promise instance synchronizes itself with the eventual settled state and value of the promise provided as argument. (In the ES6 standard this is described as "locked in").
A promise resolved with a thenable object which is not a Promise instance will skip synch as required: if at first resolved with a thenable which "fulfills" itself with a thenable, the Promise promise will re-synchronize itself with the most recent 'thenable' provided. Skipping to a new thenable to synchronize with cannot occur with A+ promises because they never call a "fulfilled" listener with a thenable object.
Non Compliant promises
Potential characteristics of non compliant promise like objects include
allowing the settled state of a promise to be changed,
calling a then 'fulfilled' listener with a promise argument,
calling a then listener, either for 'fulfilled' or 'rejected' states, synchronously from code which resolves or rejects a promise,
not rejecting a then returned promise after a listener registered in the then call throws an exception.
Interoperability
Promise.resolve can be used to settle its returned promise with a static value, perhaps mainly used in testing. Its chief purpose, however, is to quarantine side effects of non compliant promises. A promise returned by Promise.resolve( thenable) will exhibit all of behaviours 1-7 above, and none of the non compliant behaviours.
IMHO I would suggest only using non A+ promise objects in the environment and in accordance with documentation for the library which created them. A non compliant thenable can be used to to resolve an A+ promise directly (which is what Promise.resolve does), but for full predictability of behaviour it should be wrapped in a Promise object using Promise.resolve( thenable) before any other use.
Note I tried to test Parse promises for A+ compliance but it does not seem to provide a constructor. This makes claims of "almost" or "fully" A+ compliance difficult to quantify. Thanks to zangw for pointing out the possibility of returning a rejected promise form a listener as an alternative to throwing an exception.

Avoid forgotten promise returns

When I'm using promises to express dependencies between jobs, where the resolved value becomes unimportant, there is some danger that I might be forgetting a return somewhere. Example:
startSomething().then(function() {
Q.all(tasks.map(function(task) { return task.startIt(); }))
}).then(collectOutput).done();
Here the Q.all returns a promise, and I should have returned that. Not doing so means that by the time collectOutput gets called, all tasks have been started, but there are no guarantees that they finished.
This kind of error results in a race condition, and may be extremely hard to reproduce and track down. So I wonder, is there some tool to help detect and avoid this kind of problem? Perhaps some promise library which warns when a function along the way returns undefined? Or detects promises with no listeners, the way Bluebird does for unhandled rejections?
Actually, bluebird will warn you if you created a promise in a handler but did not return it. If you're willing to drop Q.
Here's a more in-depth explanation about bluebird's warnings
Warning: a promise was created in a handler but none were returned from it This usually means that you simply forgot a return statement
somewhere which will cause a runaway promise that is not connected to
any promise chain.
For example:
getUser().then(function(user) {
getUserData(user);
}).then(function(userData) {
// userData is undefined
});

Categories

Resources