How to fix the promise anti pattern - Ghost Promise? - javascript

I found the term "The Ghost Promise" here, which looks like my case.
I have the code like this:
return Q.Promise(function(resolve, reject) {
firstFunctionThatReturnPromise()
.then(function(firstResult) {
_check(firstResult) ? resolve(firstResult) : return secondFunctionThatReturnPromise();
})
.then(function(secondResult) {
console.log(secondResult);
return thirdFunctionThatReturnPromise(secondResult);
})
.then(function(thirdResult) {
resolve(thirdResult);
})
.catch(function(e) {
reject(e)
});
});
The problem is, even though the _check returns true, it still proceeds to the console.log command (which results in undefined).
In case the _check returns false, things work as expected.
So my question is:
If the behavior described above is normal?
If there is a more elegant way to handle this case?
Update 1: Many questioned that why I use Q.Promise instead of returning the result directly. It's because this is a generic function, shared by other functions.
// Usage in other functions
genericFunction()
.then(function(finalResult) {
doSomething(finalResult);
})
.catch(function(err) {
handleError(err);
});

First off, there's no reason to wrap a new promise around any of this. Your operations already return promises so it is an error prone anti-pattern to rewrap them in a new promise.
Second off, as others have said, a .then() handler has these choices:
It can return a result which will be passed to the next .then() handler. Not returning anything passes undefined to the next .then() handler.
It can return a promise whose resolved value will be passed to the next .then() handler or rejected value will be passed to the next reject handler.
It can throw which will reject the current promise.
There is no way from a .then() handler to tell a promise chain to conditionally skip some following .then() handlers other than rejecting.
So, if you want to branch your promise based on some condition logic, then you need to actually nest your .then() handlers according to your branching logic:
a().then(function(result1) {
if (result1) {
return result1;
} else {
// b() is only executed here in this conditional
return b().then(...);
}
}).then(function(result2) {
// as long as no rejection, this is executed for both branches of the above conditional
// result2 will either be result1 or the resolved value of b()
// depending upon your conditional
})
So, when you want to branch, you make a new nested chain that lets you control what happens based on the conditional branching.
Using your psuedo-code, it would look something like this:
firstFunctionThatReturnPromise().then(function (firstResult) {
if (_check(firstResult)) {
return firstResult;
} else {
return secondFunctionThatReturnPromise().then(function (secondResult) {
console.log(secondResult);
return thirdFunctionThatReturnPromise(secondResult);
})
}
}).then(function (finalResult) {
console.log(finalResult);
return finalResult;
}).catch(function (err) {
console.log(err);
throw err;
});
Even if this is inside a genericFunction, you can still just return the promise you already have:
function genericFunction() {
return firstFunctionThatReturnPromise().then(function (firstResult) {
if (_check(firstResult)) {
return firstResult;
} else {
return secondFunctionThatReturnPromise().then(function (secondResult) {
console.log(secondResult);
return thirdFunctionThatReturnPromise(secondResult);
})
}
}).then(function (finalResult) {
console.log(finalResult);
return finalResult;
}).catch(function (err) {
console.log(err);
throw err;
});
}
// usage
genericFunction().then(...).catch(...)

The behavior is expected. When you chain your .then() statements, you cannot break out of the chain early except by throwing an error.
Your top-level promise (the one returned by Q.Promise()) gets resolved after _check(); but you actually have an inner promise chain that continues to execute.
By specification, then() returns a promise: https://promisesaplus.com/#point-40
You can see for yourself in the source code of Q: https://github.com/kriskowal/q/blob/v1/q.js#L899
For your desired behavior, you'll actually need another nested promise chain.
return Q.Promise(function(resolve, reject) {
firstFunctionThatReturnPromise().then(function(firstResult) {
if (_check(firstResult)) {
resolve(firstResult);
} else {
return secondFunctionThatReturnPromise().then(function(secondResult) {
console.log(secondResult);
return thirdFunctionThatReturnPromise(secondResult);
});
}
});
});

I have never used Q, but everything a promise returns is transformed into a promise and passed to the next .then(). Here, your first .then() don't return anything. So it returns undefined. So undefined is wrapped in a new Promise and passed to the next handler, where you get secondResult == undefined.
You can see it in action in the following CodePen : http://codepen.io/JesmoDrazik/pen/xOaXKE

Related

Why then is always called with undefined?

I have following chain of promises:
return new Promise((resolve, reject) => {
isUserExists(user.username, user.email).then((existResult) => {
/// already exists
if (existResult.rows[0].exists == true) {
reject('Already exists');
} else {
return insertCity({
city_id: user.city_id,
city_name: user.city_name
});
}
}).then((cityResult) => {
/// this is one is always called even I didn't called insertCity above
console.log(cityResult); /// could be undefined if not called
return insertUser(user); /// I don't want this to be executed
});
});
If user already exists, I call reject('Already exists'). But anyway, next then is called anyway, which means insertUser is called also. I don't want it to happen. I just want to stop everything when reject is called at any point. return reject(Already exists) does not help. How can I solve the problem? It seems I don't understand core thing of Promises.
Couple of problems in your code:
You don't need to wrap isUserExists(user.username, user.email) in a Promise constructor because it already returns a Promise.
Inside the callback function of first .then() method, calling reject() rejects the newly created Promise but it doesn't stops the execution of the callback function of the first .then() method until it implicitly returns undefined which then leads to the invocation of the second .then() block.
Solution
You don't need a Promise constructor. Once you have determined that user already exists, you could throw an error.
return isUserExists(user.username, user.email)
.then((existResult) => {
/// already exists
if (existResult.rows[0].exists == true) {
throw new Error("already exists");
}
return insertCity({ city_id: user.city_id, city_name: user.city_name });
})
.then((cityResult) => {
console.log(cityResult);
return insertUser(user);
});
Make sure to add the .catch() block in the calling code to handle the error when the user already exists.
You have two independant promises.
If existResult.rows[0].exists == true then you reject the outer promise.
That doesn't effect the inner promise, which hasn't been rejected. So it's then continues.
Don't nest promises.
Throw an exception if you want to trigger a rejection chain from inside a promise chain.
function isUserExists() {
return Promise.resolve("OK")
}
function getPromise() {
return isUserExists().then(function() {
// it exists!
if (true) {
throw "User exists.";
}
}).then(function() {
console.log("This won't be called");
});
}
getPromise().then(function() {
console.log("Also won't be called");
}).catch(function() {
console.log("Error here!");
});
This is still rather messy though. You'd be better off using await and async syntax:
async function example() {
const existResult = await isUserExists(user.username, user.email);
if (existResult.rows[0].exists == true) throw "Already exists";
const cityResult = await insertCity({
city_id: user.city_id,
city_name: user.city_name
});
console.log(cityResult);
return insertUser(user);
}

Why do JavaScript promises chain rejections into resolved promises?

I'm wondering why rejections are chained as resolved with ES6 promises?
Here's an example:
let p = Promise.reject().then(function () {
return 'one';
}, function() {
return 'two';
});
p.then(function (value) {
// I do not expect this to be executed, since it was not resolved.
console.log(value);
});
The above outputs "two" to the console.
https://jsfiddle.net/pscb88xg/1/
Why does the chaining of a promise mutate a rejection into a successful resolve?
Edit: I want to clarify that the question has practical application.
What if you want to convert data from A to B using chaining.
p.then(function (A) {
return new B(A);
});
The above mutates rejections into resolved values. Even if no reject callback is used.
For example;
let p = Promise.reject('error').then(function (A) {
return new B(A);
});
// some code elsewhere
p.then(function (B) {
console.log('success!');
});
In the above example. The value B is not B but the error, and it was resolved successfully later in the chain.
Is this normal?
Edit: I understand my confusion now. I was extracting HTTP header values in rejections like this.
let p = myHttpService.get(...).then(function() {
//....
}, function(response) {
// get headers
});
The above was chaining my promises to a resolved value, and I didn't understand why. I can fix my code with the following.
let p = myHttpService.get(...).then(function() {
//....
}, function(response) {
// get headers
return Promise.reject(response);
});
After handling an error you usually want your code to continue, similar to how code after a catch block runs like normal, whereas uncaught exceptions abort.
If you want to abort instead then don't handle the error until the end of the chain:
let p = Promise.reject().then(function () {
return 'one';
});
p.then(function (value) {
// This won't run, because the rejection hasn't been handled yet
console.log(value);
}, function() {
return console.log( 'there was a problem' );
}).then(function ( ) {
// This will run because the rejection has been dealt with already.
console.log( 'Moving on');
});
MDN documentation for Promise.prototype.then says:
After the invocation of the handler function [the function passed to then()], the promise returned by then gets resolved with the returned value as its value.
It's meant to allow you to gracefully recover from an error in a promise chain.
An example might be the 304 Not Modified response from the server. If you were to use a promise based library to do an http request any response that's not 2XX will be considered a failure and the promise will be rejected. From an application's point of view however 304 might just as good as a 200 and you'd like to continue as normal.
This is the same behavior as AngularJS's $q provider.
The mutation occurs because in your rejection handler, you are returning a value and not a rejected promise. If you were to instead, pass a rejected promise, it would behave how you were expecting:
let p = Promise.reject().then(function () {
return 'one';
}, function() {
return Promise.reject('two');
});
p.then(function (value) {
// I do not expect this to be executed, since it was not resolved.
console.log(value);
}, function() {
console.log("Rejected, baby!");
});

Code Execution in Promise and using return statements [duplicate]

This question already has answers here:
Do I need to return after early resolve/reject?
(6 answers)
Closed 6 years ago.
Here, while using promise should i need to return the resolve and reject methods
the code executes smoothly but if there are multiple conditions statements then will reject and resolve auto-ends or we have to use return statements
const getJobs = (filters, fieldASTs) => new Promise((resolve, reject) => {
const AST = fieldASTs.fieldNodes[0].selectionSet.selections[0]
.selectionSet.selections[0].selectionSet.selections;
const FIELDS = _.map(AST, n => n.name.value);
if (_.includes(FIELDS, 'employer')) {
Job.find(filters, (err, d) => {
if (err) return reject(err);
// should i need to return or just use reject
if (err === null && d === null) return reject(null);
// return resolve(d) or only resolve()
return resolve(d);
});
} else {
Job.find(filters, (err, d) => {
// here also
if (err) return reject(err);
// here too
return resolve(d);
});
}
});
Whether or not to use a return statement is entirely up the desired flow of control in your function. It actually has nothing to do with promises. If you don't want or need any more code in your function to execute and you aren't already completely isolated in a conditional, then use a return to exit the function. Same issue whether you are or aren't using promises.
Remember, all resolve() or reject() do is change the state of the promise (assuming it is in the pending state) and then, any .then() or .catch() handlers are scheduled for future execution after the current running Javascript returns control back to the system. They are just functions calls like other functions calls.
You do not have to use return statements after calling resolve() or reject().
So, whether or not a return statement is appropriate depends entirely upon your code. If you don't want to execute more code in that block, then return. If you don't want to waste time potentially calling resolve() or reject() elsewhere in the block (which will not actually do anything to the promise), then use a return. If your code is already within a conditional block and nothing else will execute that you don't want to execute, then there is no need for a return.
For example, in this part of your code:
if (_.includes(FIELDS, 'employer')) {
Job.find(filters, (err, d) => {
if (err) return reject(err);
if (err === null && d === null) return reject(null);
return resolve(d);
});
}
it is appropriate to use the return because there is no need to execute any more code in the current function. If you omitted the return there, your code would still function fine (in this particular case) because the extra code you would run would not actually do anything since calling reject() or resolve() after you've already rejected or resolved the promise does not change anything. But, I consider it a wasteful and a bit confusing practice to let code run that doesn't need to run. So, I would always use either a return or a conditional in this case.
Personally, I'd probably write this code like this:
if (_.includes(FIELDS, 'employer')) {
Job.find(filters, (err, d) => {
if (err) return reject(err);
if (d === null) return reject(new Error("unexpected null result from Job.find()"));
return resolve(d);
});
}
Note: I removed the check for if (err === null) because that should be the success case.
Or, I would have promisified Job.find() more generically at a lower level so my logic flow would all be promises.

Is there a shortcut to define and return a rejected promise? [duplicate]

My scenario
I used to have some node.js implementation done using callbacks but I am now refactoring my code to use Promises instead - using Q module. I have the following update() function where the inner _update() function already returns a Promise:
exports.update = function(id, template, callback) {
if (!_isValid(template)){
return callback(new Error('Invalid data', Error.INVALID_DATA));
}
_update(id, template) // this already returns a promise
.then(function() {
console.log('UPDATE was OK!');
callback();
}, function(err) {
console.log('UPDATE with ERRORs!');
callback(err);
});
};
My question
I would like to achieve something like the following:
exports.update = function(id, template) {
if (!_isValid(template)){
// how could I make it return a valid Promise Error?
return reject(new Error('Invalid data', Error.INVALID_DATA));
}
return _update(id, template) // return the promise
.done();
};
Because _update() already returns a promise, I guess changing it this way would be enough (wouldn't be?):
return _update(id, template)
.done();
And... what about if the condition inside the if-clause equals true? How could I refactor
return callback(new Error('Invalid data', BaboonError.INVALID_DATA));
to throw an error to avoid passing the callback into update() and handling that error (or what ever error could ever be returning _update())?
Also, calling update():
myModule.update(someId, someTemplate)
.then(function() { /* if the promise returned ok, let's do something */ })
.catch(function(err) { /* wish to handle errors here if there was any */});
somewhere else in my code:
if there is an error during the promise propagation - it should handle it,
or, if there wasn't an error - it should do some other things
Am I close to what I am expecting? How could I finally achieve it?
I see only two problems.
If you want to explicitly return a rejected promise with a value, you should do that with Q.reject.
Calling .done() on promise means that the promise ends there. It cannot be chained further.
So, your code would look like this
exports.update = function (id, template) {
if (!_isValid(template)) {
return Q.reject(new Error('Invalid data', Error.INVALID_DATA));
}
return _update(id, template);
};
Now, the update function just returns a promise always. Its up to the callers to attach the success or failure handlers to it.

Catching Errors in JavaScript Promises with a First Level try ... catch

So, I want my first level catch to be the one that handles the error. Is there anyway to propagate my error up to that first catch?
Reference code, not working (yet):
Promise = require('./framework/libraries/bluebird.js');
function promise() {
var promise = new Promise(function(resolve, reject) {
throw('Oh no!');
});
promise.catch(function(error) {
throw(error);
});
}
try {
promise();
}
// I WANT THIS CATCH TO CATCH THE ERROR THROWN IN THE PROMISE
catch(error) {
console.log('Caught!', error);
}
You cannot use try-catch statements to handle exceptions thrown asynchronously, as the function has "returned" before any exception is thrown. You should instead use the promise.then and promise.catch methods, which represent the asynchronous equivalent of the try-catch statement. (Or use the async/await syntax noted in #Edo's answer.)
What you need to do is to return the promise, then chain another .catch to it:
function promise() {
var promise = new Promise(function(resolve, reject) {
throw('Oh no!');
});
return promise.catch(function(error) {
throw(error);
});
}
promise().catch(function(error) {
console.log('Caught!', error);
});
Promises are chainable, so if a promise rethrows an error, it will be delegated down to the next .catch.
By the way, you don't need to use parentheses around throw statements (throw a is the same as throw(a)).
With the new async/await syntax you can achieve this. Please note that at the moment of writing this is not supported by all browsers, you probably need to transpile your code with babel (or something similar).
// Because of the "async" keyword here, calling getSomeValue()
// will return a promise.
async function getSomeValue() {
if (somethingIsNotOk) {
throw new Error('uh oh');
} else {
return 'Yay!';
}
}
async function() {
try {
// "await" will wait for the promise to resolve or reject
// if it rejects, an error will be thrown, which you can
// catch with a regular try/catch block
const someValue = await getSomeValue();
doSomethingWith(someValue);
} catch (error) {
console.error(error);
}
}
No! That's completely impossible, as promises are inherently asynchronous. The try-catch clause will have finished execution when the exception is thrown (and time travel still will not have been invented).
Instead, return promises from all your functions, and hook an error handler on them.
I often find the need to ensure a Promise is returned and almost as often needing to handle a local error and then optionally rethrow it.
function doSomeWork() {
return Promise.try(function() {
return request.get(url).then(function(response) {
// ... do some specific work
});
}).catch(function(err) {
console.log("Some specific work failed", err);
throw err; // IMPORTANT! throw unless you intend to suppress the error
});
}
The benefit of this technique (Promise.try/catch) is that you start/ensure a Promise chain without the resolve/reject requirement which can easily be missed and create a debugging nightmare.
To expand on edo's answer, if you want to catch the errors of an async function that you don't want to wait for. You can add an await statement at the end of your function.
(async function() {
try {
const asyncResult = someAsyncAction();
// "await" will wait for the promise to resolve or reject
// if it rejects, an error will be thrown, which you can
// catch with a regular try/catch block
const someValue = await getSomeValue();
doSomethingWith(someValue);
await asyncResult;
} catch (error) {
console.error(error);
}
})();
If someAsyncAction fails the catch statement will handle it.

Categories

Resources