How to avoid deferred antipattern [duplicate] - javascript

This question already has answers here:
Is there a way to return early in deferred promise?
(2 answers)
Closed 6 years ago.
It's being hard to learn how to avoid the deferred antipatern with the info I could find. I'm using Q library for Nodejs.
As it is possible to read, basically, we've to try to do not reject or answer to a promise with deferred.reject or deferred.resolve.
deferred.resolve must be replaced by a return sentence, so that point is quite simple.
The problem comes when I want use a promise inside a function which must reject the execution in some cases which aren't application errors. For example:
Services.data.isNotForbiddenFieldToUpdate(bodyKeys,forbidden)
.then(() => console.log('Finish'))
Where
isNotForbiddenFieldToUpdate: (fieldsToUpdate,forbiddenFields) => {
var deferred = Q.defer();
//DO A QUERY AND STORE IN result PARAM
if(err) deferred.reject(err)
else if(! result) deferred.reject(new Error('Query is empty.'));
else deferred,resolve();
return deferred.promise;
}
In this case, is not possible to return something different from deferred.promise. If I remove this and return true, the error is that then is unknown.
How could I manage this kind of code without using deferred if is possible? Thanks.

isNotForbiddenFieldToUpdate: (fieldsToUpdate,forbiddenFields) => {
var deferred = Q.defer();
//DO A QUERY AND STORE IN result PARAM
At this point, assuming that the "QUERY" is an asynchronous operation, you will get back a promise. So you likely don't need to create another promise, just reuse the one coming from your "QUERY".
return yourAsyncQuery();
Or, if you need to add things to the result of your "QUERY", then you have to give it a callback to execute when it completes. Inside that callback, you resolve or reject the Promise (Q).
yourAsyncQuery().then(
function(result) {
if (! result)
deferred.reject(new Error('Query is empty.'));
// do more stuff with 'result'.
deferred.resolve(result);
},
function(error) {
deferred.reject(error);
}
);
return deferred.promise;
}

I don't know how in Q is, but with native promises you can do as follows:
isNotForbiddenFieldToUpdate: (fieldsToUpdate,forbiddenFields) =>
new Promise((resolve, reject) => {
//DO A QUERY AND STORE IN result PARAM
if(err){
reject(err);
} else if(! result){
reject(new Error('Query is empty.'));
} else {
resolve();
}
}
I guess with Q should be similar. Anyway, unless you need a very specific Q functionality, I would rather use native Promises.

Related

Angular async function returns undefined instead of promise

I am working with an Angular controller that will add an undetermined amount of questions from one or more surveys uploaded to the server to an existing survey(s) with same name, that's just for understanding, the uploading, handling in back-end and returning response works flawlessly, the real problem here is the specific part of just uploading the new questions, which has the following functions:
//Will save next question
const next_question = function (response, this_survey, response_survey, sur_questions, question) {
deferred = $q.defer();
new_question = new QuestionsService();
//Does a bunch of stuff with question object using all those arguments,
//which is not relevant here
if (check_existing_question(new_question, sur_questions)) {
deferred.resolve();
}
else {
new_question.$save({
actsern: new_question.actsern
}).then(function () {
deferred.resolve();
}).catch(function () {
deferred.reject();
});
}
return deferred.promise;
};
// Save the questions synchronously (wait for a promise to resolve/reject before saving next question)
async function save_question(response, this_survey, response_survey, sur_questions) {
// I want to store all promises in an array and return for future error handling
promises = [];
for (const quest in response_survey) {
// promise is undefined in console.log!!
promise = await next_question(response, this_survey, response_survey, sur_questions, quest);
console.log(promise);
//Commented because if not returns error "promises.push is not a function"
//promises.push(promise);
}
//Still undefined
console.log(promise);
return promise;
//The above is how I managed to make it work but what I really want is:
//return promises;
}
const set_survey_questions = function(response, this_survey, response_survey){
//Never mind this, unrelated to my problem
let sur_questions = QuestionsService.get({
//this_survey is survey object that is being uploaded to, questions and surveys are linked through actsern
actsern: this_survey.actsern
});
sur_questions.$promise.then(function () {
promises = save_question(response, this_survey, response_survey, sur_questions);
$q.all(promises).then(function () {
console.log("Uploaded successfully all questions");
}).catch(function (reason) {
console.log(reason);
});
});
};
The thing is that I have to save the questions synchronously, wait for one to resolve/reject before saving the next, which is why I am using an async loop function. Everything works fine, the questions are uploaded one after the order, everything goes to the server exactly as it's supposed to. The problem is that I would like to get the promises of next_question() from the async function to deal with their errors in the future and do some other stuff after they all have been resolved using the $q.all(), but the problem is that as far as I know the await should return a promise but it just returns undefined and keeps as undefined even after the loop has finished and all promises, supposedly, resolve.
I know the function next_question() works fine because if it is called directly (outside of async function, directly from set_survey_questions()) it saves the question and returns the promise just as it's supposed.
I'm not very experienced with angular or even javascript for that matter so any help or improvement you can think of is welcome.
As far as I know the await should return a promise but it just returns undefined
No. You should pass a promise to the await operator, it will then block execution of the async function until the promise is settled and return the result value of the promise.
The problem is that I would like to get the promises of next_question() from the async function to deal with their errors in the future
That doesn't seem to be compatible with your requirement of sequential saving. There are no multiple promises, there's only one active at a time. If any of them errors, the await will throw an exception and your loop will stop.
I think you really should simplify to
async function set_survey_questions(response, this_survey, response_survey) {
let sur_questions = QuestionsService.get({
actsern: this_survey.actsern
});
await sur_questions.$promise;
try {
for (const quest in response_survey) {
await next_question(response, this_survey, response_survey, sur_questions, quest);
}
console.log("Uploaded successfully all questions");
} catch (reason) {
console.log(reason);
}
}

About chaining es6 Promises, then() and value consumption

This is tightly coupled to Chaining .then() calls in ES6 promises ...
I tried this with some functions that make up a chain of promises, so basically:
var PromiseGeneratingMethod = function(){
return p = new Promise((resolve, reject) =>{
resolve(1)
});
}
var inBetweenMethod = function(){
return PromiseGeneratingMethod()
.then((resolved) => {
if(resolved){
console.log('resolved in between');
//return resolved
/* this changes output to
resolved in between
resolved at last*/
}else{
console.log('something went terribly wrong in betweeen', resolved);
}
});
}
inBetweenMethod().then((resolved) =>{
if(resolved){
console.log('resolved at last')
}else{
console.log('something went terribly wrong', resolved);
}
})
/* ouput:
resolved in between
something went terribly wrong undefined*/
I don't understand why it is like that. doesn't have a Promise just ONE associated return value? why can I change that value in every then? It seems irrational to me. A Promise Object can only have one return value and I thought every then handler will receive the same parameter after the Promise gets resolved?
This way, having two Methods which call then() on the same Promise, the latter one (in asynchronous environments you never know what that is...) will ALWAYS get an empty result, except if EVERY then returns the desired value
If I got it right, the only good thing is that you can build a then().then().then() chain to make it almost synchronous (by returning arbitrary values in every then()) but you still could achieve the same with nested Promises, right?
Can someone help me understand why es6 Promises work that way and if there are more caveats to using those?
doesn't have a promise just ONE associated return value?
Yes.
why can I change that value in every then?
Because every .then() call does return a new promise.
having two methods which call then() on the same Promise
That's not what you're doing. Your then callbacks are installed on different promises, that's why they get different values.
You could do
function inBetweenMethod() {
var promise = PromiseGeneratingMethod();
promise.then(resolved => { … }); // return value is ignored
return promise;
}
but you should really avoid that. You already noticed that you can get the expected behaviour with
function inBetweenMethod() {
var promise = PromiseGeneratingMethod();
var newPromise = promise.then(value => {
…
return value;
});
return newPromise;
}
where the newPromise is resolved with the value that is returned by the callback - possibly the same value that promise fulfilled with.
you are using .then() handler twice, do the following:
var PromiseGeneratingMethod = function(){
return new Promise((resolve, reject) =>{
if (myCondition) resolve(1)
if (!myCondition) reject("failed")
});
}
var inBetweenMethod = function(){
return PromiseGeneratingMethod()
}
inBetweenMethod().then((resolved) =>{
console.log(resolved)
}).catch(function(err) {
console.log(err)
})

Integrate a callback logic based library function into promise chain [duplicate]

This question already has answers here:
How to create a AngularJS promise from a callback-based API
(2 answers)
Closed 4 years ago.
To handle promises I return and chain them with .then(). I must however use a third party library that expects a callback and that does not return a promise.
For clarity, a fake example:
person.sayHello()
.then( response => introduceMyself() )
.then( name => externalLibrary.storeAndGetInfo(name) )
.then( info => saySomeInfo(info) )
.catch( err => console.log(err) );
introduceMyself(){
return asyncFunctionToGetAndSayMyName();
}
sayDomeInfo(info){
console.log(info);
}
My problem is that externalLibrary.storeAndGetInfo expects these params:
storeAndGetInfo(string, callback(valueThatINeedForMyNextChainedFunction));
I have the feeling that I could wrap the external library function in a chainable function (one that returns a promise), and then use the library q to defer and resolve the callback function, but then I'm stuck as I don't know to actually implement it. Or is there another way?
PS in case it makes a difference, this is in a angularjs app
You should wrap your external library's call with a function that returns a a deferred promise:
function promisedStore (name) {
var deferred = Q.defer(); //initialize deferred
externalLibrary.storeAndGetInfo(name, function(error, result) {
if (error) {
deferred.reject(new Error(error)); //reject promise if error in cb
} else {
deferred.resolve(result); //resolve promise if no error in cb
}
});
return deferred.promise;
}

Angular not getting data from FS.readFile with promises

I am trying to use an Angular service to make a call to either use fs.readFile or fs.writeFile depending on type of button pressed in order to understand how node and angular promises interact. What I have is reading writing files, but does not send back read data, nor does it throw any errors for me to understand what has gone wrong.
//HTML
<button ng-click="rw('write')">WRITE FILE</button>
<button ng-click="rw('read')">READ FILE</button>
//angular
angular.module('test', [])
.controller('ctrl', function($scope, RWService){
$scope.rw = function(type){
RWService.rw(type)
.then(
function(res){
console.log('success');
},
function(err){
console.log('error');
})
};
})
.service('RWService',['$http', '$q', function($http, $q){
this.rw = function(type){
var promise = $http.get('./rw/' + type);
var dfd = $q.defer();
promise.then(
function(successResponse){
dfd.resolve(successResponse);
},
function(errorResponse){
dfd.reject(errorResponse);
}
);
return dfd.promise;
};
}]);
//node
var fs = require('fs')
, async = require('async')
, Q = require('Q');
var dest = './file.txt';
var rw = {
write: function(data){
data = data.repeat(5);
return Q.nfcall(fs.writeFile, dest, data);
}
, read: function(data){
data = data.repeat(5);
var deferred = Q.defer();
console.log('inside read');
fs.readFile(dest, 'utf8', function(err, data){
if (err){
deferred.reject('some error');
}else{
deferred.resolve(data);
}
});
return deferred.promise;
}
};
module.exports = exports = rw;
//node server
app.get('/rw/:type', function(req, res, next){
var type = req.params.type;
var data = 'some text string\n';
if (type == 'write'){
//omitted fro brevity
}else{
rw.read(data)
.then(function(response){
return {'response': response};
})
.catch(function(err){
return {'index.js error': err};
});
}
});
I structured the angular $q portion off of this blog post.
Here is a native Promise implementation of your code.
var fs = require('fs');
var dest = './file.txt';
var rw = {
write: function(data){
return new Promise(function (resolve, reject) {
data = data.repeat(5);
fs.writeFile(function (err, result) {
if (err) return reject(err.message);
return resolve(result);
});
});
},
read: function(data){
return new Promise(function (resolve, reject) {
data = data.repeat(5);
fs.readFile(dest, 'utf8', function(err, contents) {
if (err) return reject(err.message);
return resolve(contents.toString());
});
});
}
};
module.exports = exports = rw;
[edit: I just changed the code to put data=data.repeat(5) inside the promise factory method. Basically, if anything CAN raise an exception, you should try to put it inside that promise function, or you again run the risk of silently killing the script again.]
A couple of comments:
Returning deferred is incredibly useful, but you have to be careful about how you use it. I personally only use it if the asynchronous code cannot be wrapped in a simple function (such as a class instance that creates a promise in its constructor and resolves/rejects in different child methods). In your case, probably what is happening is that the script is failing in a way that fs.readFile() never gets called -- and so deferred.resolve() and deferred.reject() will never be reached. In cases like this, you need to use try/catch and always call deferred.reject() in there as well. It is a lot of extra work that is easily avoided.
Instead, you should try to use the vanilla standard implementation of Promises as you see above.
Lastly, Q was a groundbreaking library that basically taught the world how to do promises in the first place, but it has not been updated in years and was never particularly feature-rich or fast. If you need more features, take a look at when.js*, kew or Bluebird (note that Bluebird claims to be the fastest, but I've personally found that to be untrue.)
(*I actually loved working with when.js and find it a bit painful using dumb native promises, but hey, standards are standards.)
[edit: Adding details on the Angular side of things]
So based on your comment, here is what I suspect you are also looking for. You will see that here I am using $http.get() as the only promise. No need to use defer() once you are inside a promise, so actually there is no need to even include $q.
I'm sorry, I've never used service(). Even Angular's own documentation on creating services uses the factory() method, so that's what I'm using here.
.factory('RWService',['$http', function($http){
return {
rw: function (type) {
// $http returns a promise. No need to create a new one.
return $http.get('./rw/' + type)
.then(function (response) {
// You can do other stuff here. Here, I am returning the
// contents of the response. You could do other stuff as
// well. But you could also just omit this `then()` and
// it would be the same as returning just the response.
return response.data;
})
.catch(function (err) {
// You can do other stuff here to handle the error.
// Here I am rethrowing the error, which is exactly the
// same as not having a catch() statement at all.
throw err;
});
}
};
}]);
If you read the comments in the code above, you should realize that you can write the same code like this:
.factory('RWService',['$http', function($http){
return {
rw: function (type) {
return $http.get('./rw/' + type);
}
};
});
The only difference here is that RWService.rw() will eventually resolve the entire response object rather than the response data.
The thing to keep in mind here is that you can (and absolutely should) try to recycle your promises as much as possible. Basically, all you need to know about promises is:
every promise has a then and catch method you can wrap your logic in;
every then and catch return as a new promise;
if you throw an exception inside any then or catch, you will be thrown straight to the next catch, if there is one;
if you return a value from any then or catch, it will be passed as the argument to the very next then in the chain;
when the chain runs out of thens or you throw an exception and there are no more catches, the promise chain ends; and
then and catch are fast, but they are still asynchronous, so don't add new elements to a promise chain if you don't genuinely need them.

Resolving a deferred using Angular's $q.when() with a reason

I want to use $q.when() to wrap some non-promise callbacks. But, I can't figure out how to resolve the promise from within the callback. What do I do inside the anonymous function to force $q.when() to resolve with my reason?
promises = $q.when(
notAPromise(
// this resolves the promise, but does not pass the return value vvv
function success(res) { return "Special reason"; },
function failure(res) { return $q.reject('failure'); }
)
);
promises.then(
// I want success == "Special reason" from ^^^
function(success){ console.log("Success: " + success); },
function(failure){ console.log("I can reject easily enough"); }
);
The functionality I want to duplicate is this:
promises = function(){
var deferred = $q.defer();
notAPromise(
function success(res) { deferred.resolve("Special reason"); },
function failure(res) { deferred.reject('failure'); }
);
return deferred.promise;
};
promises.then(
// success == "Special reason"
function(success){ console.log("Success: " + success); },
function(failure){ console.log("I can reject easily enough"); }
);
This is good, but when() looks so nice. I just can't pass the resolve message to then().
UPDATE
There are better, more robust ways to do this. $q throws exceptions synchronously, and as #Benjamin points out, the major promise libs are moving toward using full Promises in place of Deferreds.
That said, this question is looking for a way to do this using $q's when() function. Objectively superior techniques are of course welcome but don't answer this specific question.
The core problem
You're basically trying to convert an existing callback API to promises. In Angular $q.when is used for promise aggregation, and for thenable assimilation (that is, working with another promise library). Fear not, as what you want is perfectly doable without the cruft of a manual deferred each time.
Deferred objects, and the promise constructor
Sadly, with Angular 1.x you're stuck with the outdated deferred interface, that not only like you said is ugly, it's also unsafe (it's risky and throws synchronously).
What you'd like is called the promise constructor, it's what all implementations (Bluebird, Q, When, RSVP, native promises, etc) are switching to since it's nicer and safer.
Here is how your method would look with native promises:
var promise = new Promise(function(resolve,reject){
notAPromise(
function success(res) { resolve("Special reason") },
function failure(res) { reject(new Error('failure')); } // Always reject
) // with errors!
);
You can replicate this functionality in $q of course:
function resolver(handler){
try {
var d = $q.defer();
handler(function(v){ d.resolve(v); }, function(r){ d.reject(r); });
return d.promise;
} catch (e) {
return $q.reject(e);
// $exceptionHandler call might be useful here, since it's a throw
}
}
Which would let you do:
var promise = resolver(function(resolve,reject){
notAPromise(function success(res){ resolve("Special reason"),
function failure(res){ reject(new Error("failure")); })
});
promise.then(function(){
});
An automatic promisification helper
Of course, it's equally easy to write an automatic promisification method for your specific case. If you work with a lot of APIs with the callback convention fn(onSuccess, onError) you can do:
function promisify(fn){
return function promisified(){
var args = Array(arguments.length + 2);
for(var i = 0; i < arguments.length; i++){
args.push(arguments[i]);
}
var d = $q.defer();
args.push(function(r){ d.resolve(r); });
args.push(function(r){ d.reject(r); });
try{
fn.call(this, args); // call with the arguments
} catch (e){ // promise returning functions must NEVER sync throw
return $q.reject(e);
// $exceptionHandler call might be useful here, since it's a throw
}
return d.promise; // return a promise on the API.
};
}
This would let you do:
var aPromise = promisify(notAPromise);
var promise = aPromise.then(function(val){
// access res here
return "special reason";
}).catch(function(e){
// access rejection value here
return $q.reject(new Error("failure"));
});
Which is even neater
Ok here's my interpretation of what I think you want.
I am assuming you want to integrate non-promise callbacks with a deferred/promise?
The following example uses the wrapCallback function to wrap two non-promise callbacks, successCallback and errCallback. The non-promise callbacks each return a value, and this value will be used to either resolve or reject the deferred.
I use a random number to determine if the deferred should be resolved or rejected, and it is resolved or rejected with the return value from the non-promise callbacks.
Non angular code:
function printArgs() {
console.log.apply(console, arguments);
}
var printSuccess = printArgs.bind(null, "success");
var printFail = printArgs.bind(null, "fail");
function successCallback() {
console.log("success", this);
return "success-result";
}
function errCallback() {
console.log("err", this);
return "err-result";
}
function wrapCallback(dfd, type, callback, ctx) {
return function () {
var result = callback.apply(ctx || this, arguments);
dfd[type](result);
};
}
Angular code:
var myApp = angular.module('myApp', []);
function MyCtrl($scope, $q) {
var dfd = $q.defer();
var wrappedSuccess = wrapCallback(dfd, "resolve", successCallback);
var wrappedErr = wrapCallback(dfd, "reject", errCallback);
var rnd = Math.random();
var success = (rnd > 0.5);
success ? wrappedSuccess() : wrappedErr();
console.log(rnd, "calling " + (success ? "success" : "err") + " callback");
dfd.promise.then(printSuccess, printFail);
}
Example output where the random number is less than 0.5, and so the deferred was rejected.
err Window /fiddlegrimbo/m2sgu/18/show/
0.11447505658499701 calling err callback
fail err-result

Categories