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

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

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

Handle promise resolves indvidually in promise.all()

There is a lot of information on how to handle errors when using promise.all() using catch but what I'm trying to achieve is to handle every time a promise inside of this promise.all() resolves. The reason I'm trying to do this is because I am attempting to setup a custom progress bar in console and I need to call the tick method every time a promise is resolved.
this.getNewSources = function () {
var bar = new ProgressBar(':bar', {total: this.getSourceMap().size});
var timer = setInterval(function () {
bar.tick();
if (bar.complete) {
console.log('\ncomplete\n');
clearInterval(timer);
}
}, 100);
let promiseArr = [];
for (let x of this.getSourceMap().values()) {
promiseArr.push(this.requestArticles(x.getName(), x.getCat(), x.getKey()));
}
return Promise.all(promiseArr).then(() => {
console.log("Articles loaded this round: " + this.articles.size);
console.log('all sources updated');
this.loadedArticles = true;
console.log(this.articleCount);
console.log(this.articles.size);
}).catch(e => {
console.log(e);
});
};
I'm trying to figure out a way of being able to call the bar.tick() method when each individual promise resolves.
(Answering my own question.)
I handled it by adding a then handler where I'm getting the promise from requestArticles (where I'm pushing them into the promiseArr array). I had to be sure to pass the value that handler receives out of the handler so it propagates to Promise.all, see *** lines:
this.getNewSources = function () {
var bar = new ProgressBar(':bar', {total: this.getSourceMap().size});
var timer = setInterval(function () {
if (bar.complete) {
console.log('\ncomplete\n');
clearInterval(timer);
}
}, 100);
function updateProgressBar() {
bar.tick()
}
let promiseArr = [];
for (let x of this.getSourceMap().values()) {
promiseArr.push(this.requestArticles(x.getName(), x.getCat(), x.getKey())
.then(value => { // ***
updateProgressBar(); // ***
return value; // ***
}) // ***
);
}
return Promise.all(promiseArr).then(() => {
console.log("Articles loaded this round: " + this.articles.size);
console.log('all sources updated');
this.loadedArticles = true;
console.log(this.articleCount);
console.log(this.articles.size);
}).catch(e => {
console.log(e);
});
};
That way, my handlers get called as the promises complete individually, and since I'm returning the value I receive, the promise created by my call to then will resolve with that value, which Promise.all will see. Rejections will skip over that handler and go straight to the handlers hooked up by Promise.all.
The ascii progress library on npm
Result output in console:
(With thanks to T.J. Crowder for his initial explanation, which made me realize I could do this where I'm pushing onto the array. He said he preferred deleting that answer and having me post this instead.)

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

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.

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.

sequential deferred promise with ajax call running out of order

If you look at showResults(), you will see it first creates a promise, then runs getSelectedIds() which returns a promise, then it makes the api call with those selected ids, which is an ajax call, then console logs sr df3 finished. the problem im having an extreme hard time figuring out is i think i have some missing return statements somewhere, so the promise is getting resolved too early or something.
Can somebody PLEASE spot where im going wrong here, and tell me what is needed in either the getApi ajax call or the previous, or following event so that "sr df3 finished" DOES NOT happen until after the ajax call has returned its promise.
app.core.getSelectedIds = function(){
var dfd = $.Deferred();
//eliminates having to make an unnecessary ajax call if we already have them
if( $.isEmptyObject(app.core.selectedIds) ){
//gather up selected ids
$('input[type=checkbox]').each(function (key, value) {
//look for checked checkboxes only
if ($(this).is(':checked') && $(this).hasClass('results')) {
//get id from hidden input next to checkbox, set ids into an object for later use
stdIds[key] = $(this).next().attr('value');
}
});
app.core.selectedIds = stdIds;
}
dfd.resolve();
return dfd.promise();
};
app.test.getApi = function(newToken, nextPageNum){
var df1,df2;
if(nextPageNum){ app.test.nextPageNum = nextPageNum; }
df1 = app.test.ajaxCall1();
df2 = df1.then( function(){
console.log('test df2 started');
console.log(app.test.standardsObj); //the ajax calls results after being applied to the methods property
});
df2.done(function(){
console.log('test df2 finished');
});
};
app.ui.showResults = function(newToken){
var df1,df2,df3;
df1 = $.Deferred();
df2 = df1.then(function(){
console.log('sr df2 started');
return app.core.getSelectedIds(); //is a promise
});
df2.done(function(){
console.log(app.core.selectedIds);
console.log('sr df2 finished');
});
df3 = df2.then(function(){
console.log('sr df3 started');
app.test.getApi(newToken);
});
df3.done(function(){
console.log('sr df3 finished');
});
df1.resolve();
};
app.test.ajaxCall1 = function(){
var idArr = [];
//For now set up an array to cast those values to
$.each(app.core.selectedIds, function( key, value ){
idArr[key] = value;
});
return app.core.methodByRoute('ajax_call_1', {ids: idArr}, 'GET')
.done(function(retrievedResults){
app.test.standardsObj = retrievedResults;
})
};
console log output is
sr df2 started
Object { 1="498", 2="501", 3="502", more...}
sr df2 finished
sr df3 started
sr df3 finished
test df2 started
["valid data"]
test df2 finished
app.core.getSelectedIds = function(){
var dfd = $.Deferred();
…
dfd.resolve();
return dfd.promise();
}
Looks like this function doesn't need to return a promise at all if it doesn't do anything asynchronous, and would resolve the deferred immediately anyway?
i think i have some missing return statements somewhere
Yes, exactly. The basic rule is: return a promise from every function that does something asynchronous. In your case, that includes the app.test.getApi function and the df2.then(function(){…}) callback that calls it - if the callback doesn't return anything, df3 is resolved immediately after df2, as it doesn't know what to wait for.
Btw, you don't need that df1 - just start the chain with the first promise that a function returns.
app.core.getSelectedIds = function() {
if ($.isEmptyObject(app.core.selectedIds)) {
$('input[type=checkbox]').each(function (key, value) {
if ($(this).is(':checked') && $(this).hasClass('results')) {
stdIds[key] = $(this).next().attr('value');
}
});
app.core.selectedIds = stdIds;
}
return $.when(app.core.selectedIds); // a promise. just to start the chain.
// maybe this function needs to do
// something async in the future
};
app.test.getApi = function(newToken, nextPageNum) {
if (nextPageNum) {
app.test.nextPageNum = nextPageNum;
}
var df1 = app.test.ajaxCall1();
var df2 = df1.then( function(){
console.log('test df2 started');
console.log(app.test.standardsObj); //the ajax calls results after being applied to the methods property
});
df2.done(function(){
console.log('test df2 finished');
});
return df2;
// ^^^^^^^^^^^
};
app.ui.showResults = function(newToken) {
var df1 =app.core.getSelectedIds(); // is a promise
df1.done(function() {
console.log(app.core.selectedIds);
console.log('sr df2 finished');
});
var df2 = df1.then(function() {
console.log('sr df2 started');
return app.test.getApi(newToken);
// ^^^^^^
});
df2.done(function(){
console.log('sr df2 finished');
});
return df2;
// ^^^^^^^^^^^ just for convenience, if we want to chain something
// after a .showResults() call
};

Categories

Resources