Exception gets swallowed in promise chain - javascript

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.

Related

Is it possible to catch errors between chained promises?

So I am wondering if this works?
S3.getObject()
.promise()
.then()
.catch() // catch error from the first then() statement
.then()
.catch() // catch error from the second then() statement
or do I need to place all 'catches' in the end? Can I have multiple catch then? Will they be fired in the order of the 'then' statements throwing errors?
It depends of your actual goals.
As a matter of fact, .then() method takes two parameters:
onFullfilled: Callback to be invoked when the promise is fulfilled.
onRejected: Callback to be invoked when the promise is rejected.
In fact, .catch(fn) is just a shorthand for .then(null, fn).
Both .then() and .catch() each return a new promise which resolves to its return value. In other words:
A resolved promise of that value if it isn't a promise.
The actual return value if it is already a promise (that will be fulfilled or rejected).
A rejected promise if the return value is a rejected promise (as previous point says) or any error is thrown.
The main reason behind the use of .then(onFullfill).catch(onReject) pattern instead of .then(onFullfill, onReject) is that, in the former (which is equivalent to .then(onFullfill).then(null, onReject)), we are chaining the onReject callback to the promise returned by first .then() instead of directly to the original promise.
The consequence of this is that if en error is thrown inside the onFullfill callback (or it returns a promise which happen to resolve to a rejected state), it will be catched by the chained .catch() too.
So, answering to your question, when you do something like:
P.then(...)
.then(...)
.then(...)
.catch(...)
;
You are chaining promises "supposing" all will go fine "and only check at the end". That is: Whenever any step fails, all subsequent .then()s are bypassed up to the next (in this case the last) .catch().
On the other hand, if you insert more .catch()s in between, you would be able to intercept rejected promises earlier and, if appropriate, solve whatever were going on and turn it into a resolved state again in order to resume the chain.

Constructor of a custom promise class is called twice (extending standard Promise)

I'm playing with Promise Extensions for JavaScript (prex) and I want to extend the standard Promise class with cancellation support using prex.CancellationToken, complete code here.
Unexpectedly, I'm seeing the constructor of my custom class CancellablePromise being called twice. To simplify things, I've now stripped down all the cancellation logic and left just a bare minimum required to repro the issue:
class CancellablePromise extends Promise {
constructor(executor) {
console.log("CancellablePromise::constructor");
super(executor);
}
}
function delayWithCancellation(timeoutMs, token) {
// TODO: we've stripped all cancellation logic for now
console.log("delayWithCancellation");
return new CancellablePromise(resolve => {
setTimeout(resolve, timeoutMs);
}, token);
}
async function main() {
await delayWithCancellation(2000, null);
console.log("successfully delayed.");
}
main().catch(e => console.log(e));
Running it with node simple-test.js, I'm getting this:
delayWithCancellation
CancellablePromise::constructor
CancellablePromise::constructor
successfully delayed.
Why are there two invocations of CancellablePromise::constructor?
I tried setting breakpoints with VSCode. The stack trace for the second hit shows it's called from runMicrotasks, which itself is called from _tickCallback somewhere inside Node.
Updated, Google now have "await under the hood" blog post which is a good read to understand this behavior and some other async/await implementation specifics in V8.
Updated, as I keep coming back to this, adding static get [Symbol.species]() { return Promise; } to the CancellablePromise class solves the problem.
First Update:
I first thought .catch( callback) after 'main' would return a new, pending promise of the extended Promise class, but this is incorrect - calling an async function returns a Promise promise.
Cutting the code down further, to only produce a pending promise:
class CancellablePromise extends Promise {
constructor(executor) {
console.log("CancellablePromise::constructor");
super(executor);
}
}
async function test() {
await new CancellablePromise( ()=>null);
}
test();
shows the extended constructor being called twice in Firefox, Chrome and Node.
Now await calls Promise.resolve on its operand. (Edit: or it probably did in early JS engine's versions of async/await not strictly implemented to standard)
If the operand is a promise whose constructor is Promise, Promise.resolve returns the operand unchanged.
If the operand is a thenable whose constructor is not Promise, Promise.resolve calls the operand's then method with both onfulfilled and onRejected handlers so as to be notified of the operand's settled state. The promise created and returned by this call to then is of the extended class, and accounts for the second call to CancellablePromise.prototype.constructor.
Supporting evidence
new CancellablePromise().constructor is CancellablePromise
class CancellablePromise extends Promise {
constructor(executor) {
super(executor);
}
}
console.log ( new CancellablePromise( ()=>null).constructor.name);
Changing CancellablePromise.prototype.constructor to Promise for testing purposes causes only one call to CancellablePromise (because await is fooled into returning its operand) :
class CancellablePromise extends Promise {
constructor(executor) {
console.log("CancellablePromise::constructor");
super(executor);
}
}
CancellablePromise.prototype.constructor = Promise; // TESTING ONLY
async function test() {
await new CancellablePromise( ()=>null);
}
test();
Second Update (with huge thanks to links provided by the OP)
Conforming Implementations
Per the await specification
await creates an anonymous, intermediate Promise promise with onFulilled and onRejected handlers to either
resume execution after the await operator or throw an error from it, depending on which settled state the intermediate promise achieves.
It (await) also calls then on the operand promise to fulfill or reject the intermediate promise. This particular then call returns a promise of class operandPromise.constructor. Although the then returned promise is never used, logging within an extended class constructor reveals the call.
If the constructor value of an extended promise is changed back to Promise for experimental purposes, the above then call will silently return a Promise class promise.
Appendix: Decyphering the await specification
Let asyncContext be the running execution context.
Let promiseCapability be ! NewPromiseCapability(%Promise%).
Creates a new jQuery-like deferred object with promise, resolve and reject properties, calling it a "PromiseCapability Record" instead. The deferred's promise object is of the (global) base Promise constructor class.
Perform ! Call(promiseCapability.[[Resolve]], undefined, « promise »).
Resolve the deferred promise with the right operand of await. The resolution process either calls the then method of the operand if it is a "thenable", or fulfills the deferred promise if the operand is some other, non-promise, value.
Let stepsFulfilled be the algorithm steps defined in Await Fulfilled Functions.
Let onFulfilled be CreateBuiltinFunction(stepsFulfilled, « [[AsyncContext]] »).
Set onFulfilled.[[AsyncContext]] to asyncContext.
Create an onfulfilled handler to resume the await operation, inside the async function it was called in, by returning the fulfilled value of the operand passed as argument to the handler.
Let stepsRejected be the algorithm steps defined in Await Rejected Functions.
Let onRejected be CreateBuiltinFunction(stepsRejected, « [[AsyncContext]] »).
Set onRejected.[[AsyncContext]] to asyncContext.
Create an onrejected handler to resume the await operation, inside the async function it was called in, by throwing a promise rejection reason passed to the handler as its argument.
Perform ! PerformPromiseThen(promiseCapability.[[Promise]], onFulfilled, onRejected).
Call then on the deferred promise with these two handlers so that await can respond to its operand being settled.
This call using three parameters is an optimisation that effectively means then has been called internally and won't be creating or returning a promise from the call. Hence settlement of the deferred will dispatch calling one of its settlement handlers to the promise job queue for execution, but has no additional side effects.
Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
Set the code evaluation state of asyncContext such that when evaluation is resumed with a Completion completion, the following steps of the algorithm that invoked Await will be performed, with completion available.
Store where to resume after a successful await and return to the event loop or micro task queue manager.

Node.js vs Browser: Chaining rejected promises

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);

Why does a returned Promise not have a method on it .then when logged to the console?

I understand that a new promise has a .then method on it.
But when I console log the promise returned from getUserData('jeresig') I get this object logged to the console (with no .then method).
Promise {
_bitField: 0,
_fulfillmentHandler0: undefined,
_rejectionHandler0: undefined,
_promise0: undefined,
_receiver0: undefined }
Is .then actually on the new promise object or perhaps it only gets added to the object later asynchronously?
So does that mean .then is called asynchronously even though it looks synchronous?
let Promise = require('bluebird'),
GitHubApi = require('github'),
let github = new GitHubApi({
version: '3.0.0'
});
function getUserData (user){
return new Promise(function(resolve, reject){
github.user.getFollowingFromUser({
user: user,
per_page: 2
}, function(err, res){
if (err){ reject(err) }
else {
resolve(res)
}
})
})
}
console.log(getUserData('jeresig'))
// .then(function(data){
// console.log('data', data)
// })
// .catch(function(error){
// console.log('error', error)
// })
The .then() handler is already there. This is just an issue with what you're looking at in the console.
It's on the prototype of the object which is not shown by default in console.log(). Since it looks like you are looking at a Bluebird promise, if you look at the returned promise in the console in Chrome, you can expand the object in the console and see everything that is on the promise object, including the methods on the prototype. .then() is there from the moment the promise is created.
You can also see for sure that it's there by doing this:
var p = getUserData('jeresig');
console.log(p.then);
And, .then() has to be there initially or the whole concept of the promise will not work because you call .then() on it synchronously. That's how you register your interest in knowing when the promise is resolved in the future.
Yes, .then is actually on the new promise object after creation (it in Promise prototype). And it is synchronous, because it still add callbacks to fulfill and reject events.
Try
console.log(getUserData('jeresig').then)
So does that mean .then is called asynchronously even though it looks
synchronous?
then method is synonymous with "do something after this promise is resolved". then behaves differently according to the Promise State (Polymorphism) which can confuse initially unless you read the source or spent some time understanding the concept.
If Promise1 is resolved already, then it returns a Promise2 to you and also settles that Promise2 with the transformation function supplied by then. If Promise1 is not resolved at that point, it registers a function that would get called after Promise1 is resolved and returns Promise2. In either case you immediately get a Promise2 object. callbacks doesn't return anything to you which is not really functional programming, it just relies on after effect.
The main idea of Promise vs the normal callback is that it returns an Object to you immediately which is called the Promise Object. The advantage is that you can immdeiately attach a future event to it by using the then when this Promise say Promise1 is completed and not somewhere else in the program. This is highly scalable say if you want to loop and attach something then.then.then etc!
This return Object is an important concept of Promise, if you didn't get this Object in return you would have to do this in the callback1 function somewhere else in the program and not instantly. Imagine having to register one more aysnchronous call after callback1. It starts to get really messy. Everything works, however it will start getting complex to maintain. And you are not going to write functional programming like this https://stackoverflow.com/a/35786369/452102 which is really an idiomatic way to write things.
Going through an implementation of Promise will be more beneficial to learn the concepts since there are lot of places where API behaves differently depending on the situation. The benefit of this Promise abstraction is that, once the end user gets used to it, it all looks very seamless.

Are there any (negative) side effects when I don't use the "then" function of a Promise?

I have a function which returns a Promise.
Now, sometimes it makes sense for the consumer to use the "then" function on that Promise. But sometimes the consumer simply does not care about when the Promise resolves, and also not about the result - in other words the same function should also be able to be called in a "fire and forget" manner.
So I want these two usage scenarios:
func().then( ... ); // process Promise
func(); // "fire and forget"
This apparently works, but I wonder if this is considered "bad practice", and in particular if this usage pattern could have any unwanted side effects, ie. leading to memory leaks? Right now I am using bluebird, but I consider to switch to native Promises if that makes any difference.
Remember that every call to then results in a new promise. So any Promise implementation that had a memory leak as a result of a promise not having any resolution handlers attached would be a broken implementation, what with all those promises that we never hook handlers to being returned. I very much doubt implementations of ES2015's promises, Bluebird, Q, etc. have that behavior.
Separately, as conceptually a promise's resolution handlers are basically just functions stored by the promise and then called when appropriate, it's not likely to be a memory leak if you never give it any functions to store.
But, there is a problem with your fire-and-forget, just not a memory leak problem: It breaks one of the main Promise rules: Either handle rejection, or return the promise chain to something else that will handle rejection. Since you're not doing that, if the operation fails, you'll have an unhandled rejection. Unhandled rejections are reported to the console and in some environments may terminate your app (at some point, Node.js may start terminating the process when this happens, see this open issue).
If the fact that then returns a new promise is a surprise, consider:
let p1 = new Promise(resolve => {
setTimeout(() => {
resolve('a');
}, 100);
});
let p2 = p1.then(result => {
console.log("p1.then got " + result);
return 'b';
});
p2.then(result => {
console.log("p2.then got " + result);
});
which outputs
p1.then got a
p2.then got b

Categories

Resources