Can I resolve a promise from a broadcasted event? - javascript

We're using angular legacy (1.5)
I am trying to bulk load some layers using a 3rd party library.
I need to wait till they are loaded before continuing.
So in my get data section, it calls the library and asks it to add data, I start a $q.defer in this section and assign this to a factory level variable.
In the service for the 3rd party lib, I setup a count for requests out and requests in, when they match, the $broadcast and event to tell me its complete.
I then listen ($on) for this event and set the promise to resolved.
however the application doesn't wait for this.
I understand this is a strange one, but what can I do.
Our code is quite involved, so I have tried to create crude example of what we are trying to archive.
function layerFactory($rootScope, $log, $q, DataService) {
var factory = {
getData:getData,
var _dataPromise;
function getData(data){
_getLayerData(data).then(function(){
_processData(data);
});
}
function _getLayerData(data){
_dataPromise = $q.defer();
DataService.getData(data) // Treat DataService as a 3rd party lib, this doesn't return a promise. I have no way of knowing this is complete until a $broadcast is sent.
_dataPromise.promise;
}
$rootScope.$on('dataLoaded', function(){
_dataPromise = $q.resolve();
});
}
return factory;
}
This isn't waiting for the promise to resolve and instead going into the 'then' statement and processing the next function 'too early' I need it to wait till the first function as finished.
Any ideas?

Ok, I couldn't find a way to make this work, so what I did instead was to set a factory level variable (boolean) to indicate when the loading had started, this was then set to false when the $on event was triggered.
In my getLayerData method, I set up an internal to run every 500 ms, inside this interval function I run a check for the loading variable, if false (ie loaded), then return a deferred.resolve() and cancel the interval.

Related

Angular js html not updating

I wrote a simple graphql query that fetches an array of objects. The array is showing up when I do console.log(). However, the array does NOT update the html when the data is fetched. I need to click on the screen for it to update.
I am using the Angular JS stack along with graphql. It seems though that the issue is to do with angular js only and not the API call.
The following is the api call in the JS:
graphql("...").then(
result => {
$scope.data = result.data;
});
HTML:
<div>{{data.length}}</div>
A cleaner approach is to convert the third-party promise to an AngularJS promise with $q.when:
$q.when(graphql("...")).then(
result => {
$scope.data = result.data;
});
AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc...1 Since the promise comes from outside the AngularJS framework, the framework is unaware of changes to the model and does not update the DOM.
$q.when
Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
— AngularJS $q Service API Reference - $q.when
Try to add scope.$apply();. Like this:
graphql("...").then(
result => {
$scope.data = result.data;
$scope.$apply();
});
A better approach over $apply is $timeout.
The $timeout does not generate error like "$digest already in
progress" because $timeout tells Angular that after the current cycle,
there is a timeout waiting and this way it ensures that there will not
any collisions between digest cycles and thus output of $timeout will
execute on a new $digest cycle.
graphql("...").then(
result => {
$timeout(function() {
$scope.data = result.data;
});
});

How can I apply dynamic locale change in AngularJS tests?

In an AngularJS application using angular-dynamic-locale, I want to test locale-dependent code in different locales.
I am trying to change the locale in an asynchronous setup. In previous Jasmine versions, this was done with a latch function; these are now deprecated and replaced with the done callback.
beforeEach(function (done) {
inject(function ($injector) {
tmhDynamicLocale = $injector.get('tmhDynamicLocale');
console.log('setting locale to French...');
tmhDynamicLocale
.set('fr-fr')
.then(function () {
console.log('locale set.');
done();
})
.catch(function (err) {
done.fail('failed to set locale:', err);
});
});
});
The promise returned by tmhDynamicLocale.set remains pending forever, and the setup times out.
Looking at the innards of tmhDynamicLocale shows that the locale change is scheduled, but never actually applied:
// This line runs
$rootScope.$applyAsync(function() {
// But this callback doesn't
storage[storagePut](storageKey, localeId);
$rootScope.$broadcast('$localeChangeSuccess', localeId, $locale);
deferred.resolve($locale);
});
I have tried calling $browser.defer.flush and digesting/applying the root scope, but the locale change callback is still not executed.
How can I change the locale in this test suite?
Plunker, with logs in tmhDynamicLocale added for clarity.
There doesn't seem to be a clean way to do it, but I've found a hackish workaround.
Why the promise doesn't settle
tmhDynamicLocale loads the locale file via a <script> element. When the load event of this element fires, it schedules the $localeChangeSuccess event and promise resolution.
In production, this works well:
My code calls tmhDynamicLocale.set('fr-fr')
The <script> element is created
The locale file is loaded; load fires
In the load event callback, a new callback is scheduled to be applied asynchronously
The completed-request event also triggers an Angular digest cycle
The digest cycle runs the scheduled callback
This callback resolves the locale change promise
In tests, browser events do not trigger digest cycles, so this happens:
My code calls tmhDynamicLocale.set('fr-fr')
The <script> element is created
Any $timeouts in my test code run here, with an empty $$applyAsyncQueue
The locale file is loaded; load fires
In the load event callback, a new callback is scheduled to be applied asynchronously
My code is not notified of this; it has no way to trigger the new callback, and the promise pends forever
So triggering the promise resolution can't be done inside the Angular digest cycle, or at least I don't see how.
Workaround
Since we can't tell when the callback is scheduled, poll until it is. Unlike $interval, setInterval is not mocked out in tests and allows real polling.
Once the callback is scheduled, $browser.defer.flush will run it, and trigger promise resolution. It throws if there are no deferred tasks to flush, so only call it if the queue is non-empty.
_forceFlushInterval = setInterval(function () {
if ($rootScope.$$applyAsyncQueue.length > 0) {
$browser.defer.flush();
}
});
Conceivably, tasks other than the locale change could be scheduled. So we keep polling until the promise is settled.
tmhDynamicLocale.set('fr-fr')
.then(function () {
clearInterval(_forceFlushInterval);
done();
})
.catch(function () {
clearInterval(_forceFlushInterval);
done.fail();
});
But there are many drawbacks:
Accessing $$private Angular internals is not a good idea, and is likely to break between versions.
Constantly flushing may interfere with other asynchronous setup.
If for some reason the interval doesn't get cleared, it will wreak all kinds of havoc on later tests.
Plunker

How store AngularJS $resource calls in an array to execute all together after?

I'm working with AngularJS 1.5 (I'm really beginner with JS) in a user profile view where the user can change a lot of things in his data.
In this view the data is split in several sections and save all of this data means to do several calls to server ( due to the design of app). So the problem that I found is that when the user modify his data maybe modify only a part of this and when it push save button I don't want call all methods, only to necessary methods.
I've programed the way to detect the changes in data blocks when the user push the save button, sometimes the controller make a call and in other cases two or three. But the problem is that the calls (made with $resource library) is executed asyncronously and I would like can control this better.
I would like do the next: store all calls in a list or array but wihout execute them and after execute all at the same time (more or less). If any of this fails I would like show a error message to user (only a generic error message), but internally log the call that failed, and in the same way only show (and only one) a success message when all calls have ended with success ( and not a message per success call).
I don't know how to do this, some mates say me that maybe I need use $q AngularJS service to do this, or store the promises that $resource have to execute all after (I've trying this without success) or work with promises in JS.
Anyone can give me any idea?
Finally I resolved my problem using $q. At first I wanted store the calls without execute them (I thought that it was better) but finally I can check that only stored the results of this calls is enought for my aim. So, this a skeleton of the solution that I've been done:
At the beginning of the controller
var promises = [];
In all places where I need make a controlled call inside of save user data function:
var deferred = $q.defer();
var promise = vm.teacher.$update(
function () { // Success
deferred.resolve('Success updating the teacher.');
},
function (error) { // Fail
deferred.reject('Error updating the teacher, error: ' + error)
});
promises.push(deferred.promise)
}
...
... vm.otherItems.$update ...
...
And at the end of this function, something like this:
$q.all(promises).then(
function(value){
console.log('Resolving all promises, SUCCESS, value: ')
console.log(value);
toastService.showToast('Success updating the teacher.');
// It deleted old promises for next iteration (if it exists)
promises = [];
},function(reason){
console.log('Resolving all promises, FAIL, reason: ')
console.log(reason);
toastService.showToast('Fail updating the teacher.');
}
)
Thanks for the help!

Angularjs $http interceptors - Cannot generate call to requestError

I'm using $http interceptors to capture all events following an ajax submission. For some reason, I am not able to throw a requestError. I've set up a test app to try and call requestError, but so far I can only get multiple responseErrors.
From angularjs docs:
requestError: interceptor gets called when a previous interceptor threw an error or resolved with a rejection.
This is my test code.
.factory('httpInterceptor',['$q',function(q){
var interceptor = {};
var uniqueId = function uniqueId() {
return new Date().getTime().toString(16) + '.' + (Math.round(Math.random() * 100000)).toString(16);
};
interceptor.request = function(config){
config.id = uniqueId();
console.log('request ',config.id,config);
return config;
};
interceptor.response = function(response){
console.log('response',response);
return response;
};
interceptor.requestError = function(config){
console.log('requestError ',config.id,config);
return q.reject(config);
};
interceptor.responseError = function(response){
console.log('responseError ',response.config.id,response);
return q.reject(response);
};
return interceptor;
}])
.config(['$httpProvider',function($httpProvider) {
$httpProvider.interceptors.push('httpInterceptor');
}])
.controller('MainCtrl',['$http',function($http){
var mainCtrl = this;
mainCtrl.method = null;
mainCtrl.url = null;
var testHttp = function testHttp() {
$http({method:mainCtrl.method,url:mainCtrl.url}).then(
(response)=>{console.log('ok',response);},
(response)=>{console.log('reject',response);}
);
};
//api
mainCtrl.testHttp = testHttp;
}])
I've tried multiple ways of creating http errors, and every time only responseError gets called. Things I've tried:
Get server to return different types of error for every request, e.g. 400 and 500.
Get the server to sleep random times, to get some later requests to respond with an error before earlier requests. Same resource, same server response.
Generate 404 errors by requesting resources which don't exist.
Disconnecting from the internet (responseError -1).
SIMILAR QUESTIONS
1) This question seems to have the answer:
When do functions request, requestError, response, responseError get invoked when intercepting HTTP request and response?
The key paragrapgh being:
A key point is that any of the above methods can return either an
"normal" object/primitive or a promise that will resolve with an
appropriate value. In the latter case, the next interceptor in the
queue will wait until the returned promise is resolved or rejected.
but I think I'm doing what it stipulates, viz random sleep by the server but no luck. I am getting reponseErrors out of order from the request ie as soon as the server responds.
2) A similar question was asked about 1 year ago: Angular and Jasmine: How to test requestError / rejection in HTTP interceptor?
Unfortunately, it only provides an explanation for interceptors. It does not answer my question.
I have tested in Chrome and Firefox. I hope you understand, I've done my best to find a solution to this, but I haven't come across a solution as yet.
This happens because the request isn't rejected at any point. It is supposed to be used like that:
app.factory('interceptor1', ['$q', function ($q) {
return {
request: function (config) {
console.log('request', config);
if (config.url === 'restricted')
return $q.reject({ error: 'restricted', config: config });
}
};
}]);
app.factory('interceptor2', ['$q', function ($q) {
return {
requestError: function (rejection) {
console.log('requestError', rejection);
if (rejection.error === 'restricted')
return angular.extend(rejection.config, { url: 'allowed' });
return $q.reject(rejection);
}
};
}]);
app.config(['$httpProvider',function($httpProvider) {
$httpProvider.interceptors.push('interceptor1');
$httpProvider.interceptors.push('interceptor2');
}]);
Notice that interceptors are supposed to work in stack (starting from transform* hooks in $http request), so the request can't be rejected and recovered within a single interceptor.
I know I'm years late, but I just came across the same problem and I didn't find any of the other answers particularly helpful. So, after spending a number of hours studying AngularJS interceptors, I'd like to share what I learned.
TL;DR
Interceptors are not intuitive, and there are a lot of "gotchas". The thread author and I got caught in a few of them. Problems like this can be fixed by better understanding the execution flow that happens behind the scenes. Most specific to this thread are Gotchas #3 and #6 near the end of this post.
Background
As you know, the $httpProvider has a property called "interceptors", which starts as an empty array and is where one or more interceptors can be stored. An interceptor is an object that has four optional methods: request, requestError, response, and responseError. The documentation says little about these methods, and what it does say is misleading and incomplete. It is not clear when these are called and in what order.
Explanation By Example
As mentioned in other comments/answers, the interceptor methods all end up linked together in a big promise chain. If you aren't familiar with promises, interceptors won't make any sense (and neither will the $http service). Even if you understand promises, interceptors are still a little weird.
Rather than trying to explain the execution flow, I'll show you an example. Let's say that I've added the following three interceptors to my $httpProvider.interceptors array.
When I make a request via $http, the execution flow that happens behind the scenes looks like the following. Note that green arrows indicate that the function resolved, and the red arrows indicate that the function rejected (which will happen automatically if an error is thrown). The labels next to the arrows indicate what value was resolved or rejected.
Wow, that's super complicated! I won't go through it step by step, but I want to point out a few things that might leave a programmer scratching their head.
Notable Bug-Causing Weirdness ("Gotchas")
The first thing to note is that, contrary to popular belief, passing a bad config object to $http() will not trigger a requestError function -- it won't trigger any of the interceptor methods. It will result in a normal old error and execution will stop.
There is no sideways movement in the flow -- every resolve or reject moves execution down the chain. If an error occurs in one of the success handlers (blue), the error handler (orange) in the same interceptor is not called; the one on the next level down is called, if it exists. This leads to gotcha number 3...
If you defined a requestError method in the first interceptor, it will never be called. Unless I'm reading the angularjs library code incorrectly, it seems that such a function is completely unreachable in the execution flow. This was what caused me so much confusion, and it seems it may have been part of the problem in the original question as well.
If the request or requestError methods of the last interceptor reject, the request will not be sent. Only if they resolve will angular actually attempt to send the request.
If the request fails to send OR the response status is not 2XX, it rejects and triggers the first responseError. That means your first responseError method has to be able to handle two different kinds of inputs: If the "send" function failed, the input would be an error; but if the response was a 401, the input would be a response object.
There is no way to break out of the chain once it starts. This also seemed to be part of the problem in the original question. When the last requestError rejects, it skips sending the request, but then the first responseError is immediately called. Execution doesn't stop until the chaining is complete, even if something fails early on.
Conclusion
I assume the author of this thread resolved (no pun intended) their problem long ago, but I hope this helps someone else down the line.
You are raising the responseError because all of your examples have errors in their responses. You can get a request error by trying to send invalid json in your request or improperly formatting your request.

Confusion between jQuery Deferrable, jsDeferred, and just deffering in general

I downloaded a library called jsdeferred to try and help me with some code-flow problems, but I am a little lost, as its examples and ...'documentation' is a little unclear on some things. But as I kept reading and digging, and of course googling everything under the sun, I also found out jQuery has its own Deferred() system. I am linking both here, for proper context.
Link to jsDeferred Library
Link to jQuery.Deferred()
The Problem
I need to find a way to tell the page to "hold on until the last thing is done".
This is what thought jsdeffered did. So part of my question is asking which should I use? jsDeferred or jQuery.Deferred(); and then how to use it as I've outlined below.
The Situation
My scenario is this, in a nutshell, I need to perform the following behavior.
page loads, a view model is defined
This is using kendo ui mvvm to declare my view model, so it is a kendo.data.ObservableObject
an $.ajax call is made to the database to get some default model data
This is where I am getting the most trouble. I need everything to "hold on" until this $.ajax is done. But I don't want to wrap everything in the $.ajax().done(r) if I can help it. That looks/feels very sloppy to me and is kind of confusing at times.
other widgets on the page are rendered, they have respective database queries done through kendo ui Remote DataSource.
These are actually working as intended.
jQuery Validate is wired to the view, with defaults having been set already.
This is also working as intended.
kendo.bind('body', viewModel); is called to perform model binding.
Now this is where I am running into trouble, going back to step 2 where I was making the $.ajax call. What keeps happening is that kendo.bind is fired before the $.ajax completes. I can put it in the $.ajax({}).done(); function, and for this exact one specific page that does work, but there will be many other situations where that isn't suitable.
What I have tried
First, I'll be clear that the jsdeferred documentation is very unclear to me, as running its samples verbatim doesn't actually work. I am continuously told that next is not defined and the like. I eventually figured out that you have to have an implicit Deferred. before you call next the first time.
So here is what I thought would happen...
var viewModel = new kendo.data.ObservableObject({
// various view model properties defined
});
Deferred.define();
next(function() { // let's call this STEP 1
$.ajax({
// data for ajax to controller
}).done(function(result) {
// perform operations with result
});
}).
next(function() { // let's call this STEP 2
$('#dropdownlist_target').kendoDropDownList({
// parameters, remote data source for drop down list, etc.
}).data("kendoDropDownList");
}).
next(function() { // let's call this STEP 3
$('form').validate({
// any extra form validation stuff
});
}).
next(function(){ // let's call this STEP 4
kendo.bind('body', viewModel);
});
I believed that these would each run one, right after the other, when the previous one is finished. But that is not what is happening. STEP 1 is still in the process of fetching while STEP 2, 3 and 4 are running.
This doesn't seem to be any different than the way the code was running without the jsdeferred library. So I am very confused and would absolutely love some help here. I need STEP 1 to be completely finished before STEP 2 fires, basically.
The problem is that next() expects you to return the thing you want it to wait for. In step one, you're not returning anything. jsdeferred is therefore assuming you were performing a synchronous operation (that has already finished), and so it continues with step 2.
Instead, return the jQuery.Deferred() returned from the $.ajax() call. jsdeferred will then wait for that to complete before it executes step 2.
Regardless of this, I'd dump jsdeferred. As you've realised, jQuery has a fully fledged Deferred implementation. I'm not sure what jsdeferred brings to the party.
Using $.ajax().done(r) is not sloppy. Asynchronous behaviour is the core of event driven languages, and JavaScript is one. Embrace it, or you'll go bald very early in life trying to avoid it.
If you revert to jQuery's Deferred implementation, you might like then(), to give you the semantics of next();
$.ajax({
// data for ajax to controller
}).done(function(result) {
// perform operations with result
}).then(function () {
$('#dropdownlist_target').kendoDropDownList({
// parameters, remote data source for drop down list, etc.
}).data("kendoDropDownList");
$('form').validate({
// any extra form validation stuff
});
kendo.bind('body', viewModel);
}).then(function () {
// Note you can chain then()'s as well.
});
You can just use the then method on your $.ajax() result in the same way you're using jsDeferred's next helper. Generally speaking, then is a more flexible method than done. And as Matt noted in his answer, it's a common mistake in promise based programming to forget to return a new promise within the handler, causing it to resolve prematurely with undefined instead of waiting on a the new promise.
$.ajax({ // let's call this STEP 1
// data for ajax to controller
}).
then(function(result) {
// perform operations with result
}).
then(function() { // let's call this STEP 2
$('#dropdownlist_target').kendoDropDownList({
// parameters, remote data source for drop down list, etc.
}).data("kendoDropDownList");
}).
then(function() { // let's call this STEP 3
$('form').validate({
// any extra form validation stuff
});
}).
done(function(){ // let's call this STEP 4
kendo.bind('body', viewModel);
});
Note that in my refactoring, all of those thens will execute immediately in a row, unless a new promise is returned. So you may as well combine them.
then takes a function that either returns a value or a promise, and it returns a new promise. If its function returned a value, the new promise is immediately resolved with that value. If its function returned a promise, then that promise is passed through as the new promise. Note that jQuery's then only works this way as of jQuery versions >=1.8.

Categories

Resources