stop/exit for loop on first reject promise in angular JS - javascript

I have tried all various way to do so but still didnt figure out how to resolve this.
I have a method iterator that return promise; this method runs for x times in a loop. I want if iterator return rejected promise than it should stop the execution and goes to error bloc.
I have tried with below approach taken from this SO asnwerbut this send the error but execute till the x times.
var foo = function(formdata) {
var d = $q.defer();
CallService.init()
.then(function(response) {
return CallService.fun1(formdata);
})
.then(function(response) {
var row = {1:'one', 2:'two', 3: 'three' };
var promiseArray = [];
if (<key condition true>) {
var promise = Object.keys(row).map(function(i) {
return iterator(i, row[i]).then(function(response){
promiseArray.push(response);
}, function(err){
return $q.reject(err);
});
});
return $q.all(promise); // reject if anyone is rejected, otherwise resolve
}
else {
d.reject({
message: "key condition failed."
});
}
return d.promise;
})
.then(successHandler)
.catch(errorHandler);
};
and here is the function that being called for each loop
var iterator = function(num, data) {
var d = $q.defer();
CallService.fun3()
.then(function(response) {
return CallService.fun4(data)
})
.then(function(response) {
d.resolve(num);
})
.catch(function(err) {
d.reject(err);
});
return d.promise;
};
I want that if iterator send reject for the first time than it should not go for the next and break the loop;
how to achieve that?
I have also tried this way
var d = $q.defer();
for (var k in row) {
if (isCalled) break;
var promise = iterator(k, row[k]);
promise.then(function(response) {
promiseArray.push(response);
}, function(err) {
isCalled = true;
d.reject(err);
});
}
if (!isCalled && promiseArray.length > 0) {
console.log('after for loop', promiseArray);
d.resolve(promiseArray);
}
return d.promise;
but this also fails.. it always executes for all the loop in spite of any reject promise.
edit
I have tried with .reduce method but this is somehow works but I need to send promiseArray ( if any entry is there ) alog with the error. here is my working code.
var first_promise = $q.resolve();
var start_promise = Object.keys(row).reduce(function(promise, key){
return promise.then(function(result){
if(angular.isDefined(result)) // as first_promise results undefined
promiseArray.push(result);
return iterator(key, row[key]);
});
}, first_promise);
start_promise.then(function(res){
return $q.when(promiseArray);
}).catch(function(err){
return $q.reject();
});

Updated answer after clarification. Op wants his asynchronous calls to run synchronous. Stopping when they hit a rejection.
Check out this blogpost. That decorator seems to do exactly that.

Related

Return value instead of a promise [to stop nested deferred promise]

I've a bunch of functions which are nested due to top level function is a ajax request.
So i want to return a value instead of a promise in nested child function.
Parent
let getUserPermissions = function(id) {
let deferred = $q.defer();
let promise = accessRequestService.getPermissions(id);
promise.then(function(data) {
deferred.resolve(data);
}, function(err) {
deferred.reject(err);
})
return deferred.promise;
}
Child 1
$rootScope.userInit = function() {
return getUserPermissions(vzid)
.then(function(data) {
//Some code here
return data;
})
}
Child 2
let checkAuthorize = function(toState) {
return $rootScope.userInit().then(
function(data) {
//some code here
return data;
});
}
Level 3
checkAuthorize(toState).then( function(val){
$rootScope.isAuthorized = val;
if ($rootScope.isAuthorized == true) {
$log.info('is Authorized')
} else {
$log.info('is not Authorized');
throw new AuthorizationError()
}
})
At Level 3 we are still working with a promise. Can child 2 return a value instead of promise.
Expectation # Level 3
$rootScope.isAuthorized = checkAuthorize(toState);
if ($rootScope.isAuthorized == true) {
$log.info('is Authorized')
} else {
$log.info('is not Authorized');
throw new AuthorizationError()
}
The hard truth is: you can't, unless you want spaghetti code all around.
The best solution would be to use something like ui-router's resolve, getting all the permissions needed before the page is shown to the user. Then, you could use them on your controllers without any asynchronous calls.
You can use for it async/await construction. And use Babel for support old browsers.
Async
Await
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
console.log('done');
}
f1();
Yes, this type of thing is possible, but it will change the behavior. You'll probably want to keep userInit, but you also add a userInitValue variable and initialize it as follows:
let userInitValue = null;
let userInit = function() {
return getUserPermissions()
.then(function(data) {
userInitValue = data;
return data;
})
}
So now userInitValue will start as null and then later be initialized to the relevant data.
function isKnownAuthorized(toDoSomething) {
// If we don't know whether the user is authorized
// because we are still waiting for the server to tell us
// then return false and disallow access for now
if(!userInitValue) return false;
// Otherwise return the truth
// (as of when we got the server response)
return userInitValue.isAuthorized(toDoSomething);
}
Note again the change in behavior. The price of getting an instant response, perhaps before the server gives you the data, is that the instant response could be wrong. So don't use this in a one-time :: expression in AngularJs.
Based on what you're hoping to achieve in Level 3, I'm guessing this function is going to be called multiple times with the same input. In this case, what I would do is make the call to the promise if there is not a cached result, and cache the result. This way you don't have to go down the promise chain, although I only count one promise in the code provided. There are multiple handlers on resolve, but only one promise.
You can run your code as if it was synchronous using nsynjs: it will evaluate code step-by-step, and if some function returns promise, it will pause execution, wait until promise is resolved, and assigns resolve result to data property. So, code below will be paused on level 1 until promise is resolved to actual value.
var getUserPermissions = function(id) {
return new Promise(function(resolve, reject) {
setTimeout(function(){
resolve({
id: id,
isAdmin: "yes he is",
})
}, 1000);
});
};
function synchronousCode() {
console.log("start");
var vzid = 35;
var userInit = function() {
return getUserPermissions(vzid).data;
};
var checkAuthorize = function() {
return userInit().isAdmin;
};
var isAuthorized = checkAuthorize();
console.log(isAuthorized);
};
nsynjs.run(synchronousCode, null, function(){
console.log("finish");
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
I'm using $state.transitionTo Method to be called before $stateChangeStart.
var transitionTo = $state.transitionTo;
$state.transitionTo = function(to, toParams, options) {
var from = $state.$current,
fromParams = $state.params;
to = to.name ? to : $state.get(to);
$rootScope.state = {
to: to.self,
toParams: toParams,
from: from.self,
fromParams: fromParams,
options: options
}
if (options.notify && options.notify !== false) {
return $q.reject(new AuthorizationError('Rejecting $state.transitionTo', 'Transition Rejected'));
} else {
return checkAuthorize(to).then(function(auth) {
$rootScope.isAuthorized = auth;
return transitionTo(to, toParams, options)
})
}
}
StateChangeStart
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
$log.info("Route change start from", fromState.url, "to", toState.url);
//event.preventDefault();
if ($rootScope.isAuthorized == true) {
$log.info('is Authorized')
//$state.go($rootScope.toState.name);
} else {
event.preventDefault();
$log.info('is not Authorized');
throw new AuthorizationError('User is not Authorized.', 'NOT_AUTHENTICATED')
}
});

How to convert an event listener to a promise using Q

Good day everyone, I'm new to nodejs and I'm trying to learn promises. I'm trying to implement a sort of menu that will execute certain functions based on user input. Here is the code I'm working with:
var q = require('q');
var readline = require('readline-sync');
function createNewPreset(){
var deferred = q.defer();
var preset = {
"name" : undefined,
"unit" : undefined,
"interval" : undefined,
"files" : [],
}
deferred.resolve(preset);
return deferred.promise;
}
function addPreset(){
console.log("adding");
var deferred = q.defer();
createNewPreset()
.then(function(){
console.log("Gettind input in addPreset");
})
.then(getUserInput)
.then(function(data){
console.log("Adding data" + data);
});
}
function getUserInput(){
var deferred = q.defer();
var stdin = process.stdin;
stdin.resume();
stdin.once('data', function(data){
data = data.toString().trim()
if(data.length > 20){
var err = new Error("The entered preset name is too long");
deferred.reject(err);
}
else{
deferred.resolve(data);
}
});
return deferred.promise;
}
function processChoice(input){
var deferred = q.defer();
var error = undefined;
var func;
switch(input){
case "a":
func = addPreset;
break;
case "q":
func = function(){
process.exit();
}
break;
default:
error = new Error("Invalid menu option entered.");
deferred.reject(error);
break;
}
deferred.resolve(func);
return deferred.promise;
}
function prompt(){
var stdout = process.stdout;
//indicates that prompt is being executed
stdout.write("(tic): ");
console.log("Waiting for input");
getUserInput()
.then(processChoice)
.then(function(func){ func(); })
.catch(function(err) {console.log(err)})
.then(prompt);
}
prompt();
I believe the problem lies in getUserInput() and addPreset(), with the event listener. I'd like the promise chain to continue only after the event listener is activated, but I'm not sure how to do that to be honest.
Here are my results:
(tic): Waiting for input
a
adding
Gettind input in addPreset
(tic): Waiting for input
A
Adding dataA
[Error: Invalid menu option entered.]
(tic): Waiting for input
I'd like to make the chain wait on the event listener to execute before it continues. Is there any way to accomplish this?
There's a few errors in your code
in no particular order
.then(function(func){ func(); }) should be .then(function(func){ return func(); }) - because func returns a promise which you want to wait for, without the return there the process was continuing on before addPreset resolved
in several functions you created a deferred without using it - removed those
other places you create a deferred which you resolve and return, just return a q.resolve()
The code below doesn't use q promises at all, I've changed to native Promise to test my answer - but it should be trivial to convert back to q
function createNewPreset(){
return Promise.resolve({
"name" : undefined,
"unit" : undefined,
"interval" : undefined,
"files" : [],
});
}
function addPreset(){
console.log("adding");
return createNewPreset()
.then(function(){
console.log("Gettind input in addPreset");
})
.then(getUserInput)
.then(function(data){
console.log("Adding data " + data);
});
}
function getUserInput(){
return new Promise(function(resolve, reject) {
var stdin = process.stdin;
stdin.resume();
stdin.once('data', function(data){
data = data.toString().trim()
if(data.length > 20){
var err = new Error("The entered preset name is too long");
reject(err);
}
else{
resolve(data);
}
});
});
}
function processChoice(input){
return new Promise(function(resolve, reject) {
var error = undefined;
var func;
switch(input){
case "a":
func = addPreset;
break;
case "q":
func = function(){
process.exit();
}
break;
default:
error = new Error("Invalid menu option entered.");
reject(error);
break;
}
resolve(func);
});
}
function prompt(){
var stdout = process.stdout;
//indicates that prompt is being executed
stdout.write("(tic): ");
console.log("PROMPT waiting for input");
getUserInput()
.then(processChoice)
.then(function(func){ return func(); })
.catch(function(err) {console.log(err)})
.then(prompt);
}
prompt();

Why does my Promise Chain not work in this nested way?

I want to chain 4 functions in a Promise chain like so:
function1 -> function2 -> function3 -> function4
My Promise chain
if ($location.$$url !== "/dashboard") {
vm.customURL = true;
// (1) Set root vars & Rebuild tickerTagsContainer:
var promise = TagFactory.buildUrlObject($location.$$url).then(function() {
console.log('TagFactory.buildUrlObject PROMISE returned');
}).then(function() {
console.log('(2) Re-display tags in viewHeader');
// (2) Re-display tags in viewHeader:
viewHeader = ScopeFactory.getScope('viewHeader');
viewHeader.vh.displayViewHeaderTags().then(function() {
console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
});
}).then(function() {
// (3) Reselect timeSpan:
console.log('(3) Reselect timeSpan');
viewHeader.vh.toggleTimeSpan(vm.timeSpan);
// (4) Refresh URL:
console.log('(4) Refresh URL');
ViewFactory.remakeViewObject($location.$$url);
});
}
The resulting console.logs:
^ Note I never see this log:
viewHeader.vh.displayViewHeaderTags().then(function() {
console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
});
Ideally I want to place my (3) function inside that, then chain my (4) like so:
viewHeader.vh.displayViewHeaderTags().then(function() {
console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
console.log('(3) Reselect timeSpan');
viewHeader.vh.toggleTimeSpan(vm.timeSpan).then(function() {
console.log('(4) Refresh URL');
ViewFactory.remakeViewObject($location.$$url);
});
});
However I never see the console.log from the .then function for displayViewHeaderTags
Here is what my displayViewHeaderTags looks like:
function displayViewHeaderTags() {
vm.viewTickerTags = [];
vm.viewTickerTags = TagFactory.retrieveTickerTags('all');
var deferred = $q.defer();
var tikObjs = vm.viewTickerTags.map(function(el) { return el.ticker; });
var tagObjs = vm.viewTickerTags.map(function(el) { return el.tags; });
var tags = _.flatten(tagObjs);
// forEach loops up to 3 times:
tags.forEach(function(tag, i) {
vm.viewTags = [];
ApiFactory.getTagDataSilm(tag.term_id).then(function(data) {
vm.viewTags.push(data.data.ticker_tag);
if (i === tags.length) {
deferred.resolve();
}
});
});
return deferred.promise;
}
Inside my displayViewHeaderTags function I hit a loop which will run up to 3 times, after it's done getting data, it will fill up and array then calls deffered.resolve. then returns it return deferred.promise;
So why do I never see this log? console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
Your i is never the same as the length, because the i variable starts at zero (array indexes start at zero). Which means the if you have an array with length = 2, your i values will be 0 and 1 respectively. It will never equal to zero. Basically, you would want the condition to be:
vm.viewTags.push(data.data.ticker_tag);
if (i + 1 === tags.length) {
deferred.resolve();
}
Anyway, using defer() is a code smell.
A more elegant way of doing it would be using $q.all
var allPromises = [];
var promise;
tags.forEach(function(tag) {
vm.viewTags = [];
promise = ApiFactory.getTagDataSilm(tag.term_id).then(function(data) {
vm.viewTags.push(data.data.ticker_tag);
});
// Create an array of promises, one promise for each request
allPromises.push( promise );
});
// Return a new promise that will only be resolved
// when all the promises of the array `allPromises` are resolved,
// or is rejected when one of them is.
return $q.all( allPromises );
Your chain is not really doing anything since you're not returning a promise from any of those anonymous functions. You're not seeing that log probably because ApiFactory.getTagDataSilm is failing or never resolving. Try adding an error handler into your flow.
if ($location.$$url !== "/dashboard") {
vm.customURL = true;
// (1) Set root vars & Rebuild tickerTagsContainer:
var promise = TagFactory.buildUrlObject($location.$$url).then(function() {
console.log('TagFactory.buildUrlObject PROMISE returned');
}).then(function() {
console.log('(2) Re-display tags in viewHeader');
// (2) Re-display tags in viewHeader:
viewHeader = ScopeFactory.getScope('viewHeader');
return viewHeader.vh.displayViewHeaderTags().then(function() {
console.log('viewHeader.vh.displayViewHeaderTags FINISHED!');
});
}).then(function() {
// (3) Reselect timeSpan:
console.log('(3) Reselect timeSpan');
return viewHeader.vh.toggleTimeSpan(vm.timeSpan);
}).then(function() {
// (4) Refresh URL:
console.log('(4) Refresh URL');
return ViewFactory.remakeViewObject($location.$$url);
}).catch(function(error) {
console.log('Something failed', error);
});
}
Within displayViewHeaderTags, you can use $q.all, so that rejections are handled for you:
// forEach loops up to 3 times:
vm.viewTags = [];
return $q.all(_.map(tags, function(tag) {
return ApiFactory.getTagDataSilm(tag.term_id).then(function(data) {
vm.viewTags.push(data.data.ticker_tag);
});
}));

How to reuse promises?

I am trying to reuse the the data returned from promise here. But, the problem is, after the first call to checkPromise function, it immediately calls the second function, and the promise for the first function is not fulfilled, so it never returns any data, and hence it never enters the if clause. How do I reuse a promise?
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = new Object;
function apiCall(url) {
return new Promise(function (resolve, reject) {
request(url).spread(function(response, body) {
return resolve(body);
}).catch(function(err) {
console.error(err);
return reject(err);
});
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
}
else {
apiCall(url).then(function(result) {
obj[url] = result;
//do something
});
}
}
checkPromise(url);
checkPromise(url);
You likely have a timing issue. Your apiCall() function is asynchronous. That means it finishes sometime later. As such, each time you call checkPromise(), all you're doing is starting a request and it finishes sometime later. So, you call it the first time and it starts a request (that has not finished yet). Then, your next call to checkPromise() gets called and it does it's if check before the first call has completed. Thus, it finds nothing in the cache yet.
Your code is running two requests in parallel, not one after the other.
If you actually want to wait until the first request is done before executing the second one, then you will have to actually structure your code to do that. You would need to make checkPromise() return a promise itself so code using it could known when it was actually done in order to execute something after it was done.
FYI, I don't see anything in your code that is actually related to reusing promises (which is something you cannot do because they are one-shot objects).
Here's one possible implementation:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = {};
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
return Promise.resolve(rp);
}
else {
return apiCall(url).then(function(result) {
obj[url] = result;
//do something
return result;
});
}
}
checkPromise(url).then(function() {
checkPromise(url);
});
Significant changes:
Return the promise returned by request() rather than create yet another one.
Change checkPromise() so it always returns a promise whether the value is found in the cache or not so calling code can always work consistently.
Sequence the two checkPromise() calls so the first can finish before the second is executed.
A very different approach would be to actually wait on the cache if a result you are interested in is already being loaded. That could be done like this:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = {};
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
// If it's a promise object in the cache, then loading
// If it's a value, then the value is already available
// Either way, we wrap it in a promise and return that
return Promise.resolve(obj[url]);
} else {
var p = apiCall(url).then(function(result) {
obj[url] = result;
//do something
return result;
});
obj[url] = p;
return p;
}
}
checkPromise(url).then(function(result) {
// use result
});
checkPromise(url).then(function(result) {
// use result
});
few problems with your code, first in apiCall, you are doing a promise ant-pattern( no need for that new promise), second your checkPromise is doing a sync operation, so it must either return a promise or have a callback argument, so you code can be changed into:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = new Object;
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
}).catch(function(err) {
console.error(err);
throw err;
});
}
function checkPromise(url) {
var promise = Promise.resolve();
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
}
else {
return apiCall(url).then(function(result) {
obj[url] = result;
//do something
});
}
return promise;
}
checkPromise(url).then(function(){
return checkPromise(url);
});
Given the way you are globally storing the result in 'obj[url]', it'd probably be easiest to do
function checkPromise(url) {
if (!obj[url]) obj[url] = apiCall(url);
obj[url].then(function(result) {
//do something
});
}
to basically make the request, if it hasn't already started, then attach a listener to the promise for when the result has loaded.
Here is the simplest example of how to prevent multiple API calls if there are multiple similar request for something (cache check for example)
var _cache = {
state: 0,
result: undefined,
getData: function(){
log('state: ' + this.state);
if(this.state === 0 ){ // not started
this.state = 1; // pending
this.promise = new Promise(function(resolve, reject) {
return (apiCall().then(data => { _cache.result = data; _cache.state = 2; resolve(_cache.result) }));
})
return this.promise;
}
else if(this.state === 1){ // pending
return this.promise;
}
else if(this.state === 2){// resolved
return Promise.resolve(this.result);
}
},
};
Simulating api call
function apiCall(){
return new Promise(function(resolve, reject) {
log('in promise')
setTimeout(() => {
log('promise resolving')
resolve(1);
}, 1000);
})
}
Making simultaneous requests.
_cache.getData().then(result => { log('first call outer: ' + result);
_cache.getData().then(result => { log('first call inner: ' + result); });
});
_cache.getData().then(result => { log('second call outer: ' + result);
_cache.getData().then(result => { log('second call inner: ' + result); });
});
Only one API call is maden. All others will wait for completion or use the resolved result if it already completed.

in node, using Q, make 2 functions work in parallel but wait only for the first one to fulfill its promise

In my environment I use node +q (which I'm not a great expert of), so the main argument is: promises.
I have a function that needs to make 2 operations in parallel, a very long one and a very short one.
var parallelWrapper = function(input) {
var deferred = Q.defer();
var fastPromise = fastComputation()
.then(function(data) {
deferred.resolve(data)
},
function(err) {
deferred.reject(err)
});
// of course this one below is not going to work properly
var slowPromise = slowComputation()
.then(function(data) {
makeSomething();
})
.then(function(data) {
makeSomethingElse();
})
.fail(function(err) {
console.log(err);
});
Q.all([fastPromise, slowPromise]);
retrun deferred.promise;
}
this function will be called in a chain of promises, because the result of the first operation is needed, while the result of the 2nd is not.
var myLongChainOfFunctions = function() {
var deferred = Q.defer();
firstFunction(someParams)
.then(secondFunction)
.then(thirdFunction)
/*...*/
.then(parallelWrapper)
.then(someFunction(data){
/* Do Something with the data returned only by fastPromise*/
}
/*...*/
.then(lastFunction)
.fail(doSomething)
return deferred.promise;
}
What I would like to do is to make them go in parallel but to resolve as soon as fastPromise is done, so that the chained promises can move forward, but obviously at some point in the future I would also like slowPromise to finish.
So I just would like slowPromise to live its life, do what it has to do and not care too much if succeed or fails.
My impression is that it's not possible with Q, but maybe there's a solution I'm not spotting.
I would have done so:
var parallelWrapper = function(input) {
var fastPromise = fastComputation();
var slowPromise = slowComputation()
.then(function(data) {
return makeSomething();
})
.then(function(data) {
return makeSomethingElse();
}).catch(console.error);
return Q([fastPromise, slowPromise]).all();
}
And second example somthing like this:
var myLongChainOfFunctions = function() {
return firstFunction(someParams)
.then(secondFunction)
.then(thirdFunction)
/*...*/
.then(parallelWrapper)
/*...*/
.then(lastFunction)
.catch(doSomethingWithError)
}
ok, you can do this like:
var parallelWrapper = function(input) {
slowComputation()
.then(function(data) {
return makeSomething();
})
.then(function(data) {
return makeSomethingElse();
}).catch(console.error);
return fastComputation();
}

Categories

Resources