AngularJS API call inside getter function - javascript

I've inherited a project in AngularJS with some structural issues. It uses an API call to fetch some settings from an endpoint but sometimes the application attempts to use these settings before they're loaded resulting in an 'undefined' error.
I attempted to solve this by using a getter. The idea was that when some method accesses the settings object, it would check if they were already loaded in and if not, fetch them from the API.
I've got it working after a while but there's a problem with this idea. When multiple methods access the settings at the same time (when loading the page) the API is being called multiple times because the first call hasn't come back to override the internal _settings yet.
vm._settings = null;
Object.defineProperty(vm,'settings',{get: function(){
if(vm._settings == null){
settings_service.get().$promise.then(function success(response) {
vm._settings = response;
console.log(vm._settings);
console.log("returning in");
return vm._settings;
}, function error(err) {
handle_api_error(ngNotify, err);
});
}
else return vm._settings;
}});
I can't figure out a way to let the other attempts to execute the getter wait until the first call returns and use that result instead. I'm not sure if I should use await, promises or something else. Thanks in advance

You want to debounce these requests. You could use an existing library or just implement your own.
app.factory('debounce', function($timeout) {
return function(callback, interval) {
var timeout = null;
return function() {
$timeout.cancel(timeout);
timeout = $timeout(function () {
callback.apply(this, args);
}, interval);
};
};
});

Related

How to save a promise object as a $scope variable in Angular JS

Constants.getContants is a promise which will get all the constants that are used in my application. I would like to save this to a $scope variable so that I can access it anywhere in the controller or application. Now, whenever I need to access it, I need to repeat the call and do the operation there itself.
Even If I try to save it in the $scope it will not be available outside the corresponding handler. How to solve this issue.
Following is the code that I'm using:
Constants.getConstants().then(function (AppContants) {
$scope.responseCount = AppContants.data.serverUrl+AppContants.data.appId
console.log($scope.responseCount);
//$scope.$apply();
});
console.log($scope.responseCount);
Here AJAX call is going out of sync also. I know that actions need to performed inside the handler function so that we can be sure that the intended action is executed only after a successful AJAX call. I need to use these variables outside the function. I tried $scope.$apply() operation as well. It didn't help. Is there a way to solve this? Thanks in advance.
Constants.getConstants().then(function(response)
{
$scope.responseCount = response.data;
}, function(error)
{
console.log(error);
});
And in your service you should have something like
this.getConstants= function($username){
var endpoint = "url";
return $http({
method: 'get',
url: endpoint
});
};
In your case the 2nd Console.Log executes just after placing the AJAX call. It does not wait for the AJAX to respond as it is a asynchronous call.
You can only be able to use '$scope.responseCount' property after the AJAX cal is resolved.
As a workaround you can:
Place this call to fetch constants at the time of application startup and save the constants in some service (shared service).
Do your operation in the 'then' block of this AJAX call.
Here's the thing. When you are Constants.getConstants() it return response as promise. since javascript asynchronous it does not wait until response return. It just keep on executing. That's why the console outside the then function display undefined.
workaround is you can add a function inside promise and put you operations inside that function
Constants.getConstants().then(function(AppContants) {
$scope.responseCount = AppContants.data.serverUrl + AppContants.data.appId
console.log($scope.responseCount);
sampleFunc()
});
function sampleFunc() {
// do your oprations here
console.log($scope.responseCount);
}
You can cache the promise in a service:
app.service("ConstantCache", function(Constants) {
var promiseCache;
this.getPromise = function() {
if ( promiseCache } {
return promiseCache;
} else {
promiseCache = Constants.getConstants();
return promiseCache;
};
};
this.trashCache = function() {
promiseCache = null;
};
});
Then the cached promise can be used in any controller as many times as desired:
ConstantCache.getPromise().then(function(AppContants) {
$scope.responseCount = AppContants.data.serverUrl + AppContants.data.appId
console.log($scope.responseCount);
sampleFunc()
});

Using $.ajaxStop() to determine when page is finished loading in CasperJS

So far in my tests written in CasperJS, I've been using waitForSelector() on page-specific elements to determine if a page has fully loaded (including all the async ajax requests). I was hoping to come up with a more standard way of waiting for page load and was wondering if the following was possible?
Inject as clientscript the following (include.js)
$(document).ajaxStop(function() {
// Do something
})
Description of ajaxStop according to jquery api: Register a handler to be called when all Ajax requests have completed.
Define a casper.waitForLoad function that when called would wait for the "something" in above code block
Use the function in several parts of the test.
Also any tips on the // Do Something part would also be appreciated :) I was thinking about using the window.callPhantom feature in phantomJS but I'm reading that it's not officially supported in casperjs.
I would do something like this in include.js:
(function(){
window._allAjaxRequestsHaveStopped = false;
var interval;
$(document).ajaxStop(function() {
if (interval) {
clearInterval(interval);
interval = null;
}
interval = setTimeout(function(){
window._allAjaxRequestsHaveStopped = true;
}, 500);
});
$(document).ajaxStart(function() {
window._allAjaxRequestsHaveStopped = false;
if (interval) {
clearInterval(interval);
interval = null;
}
});
})();
This sets a (hopefully unique) variable to the window object that can be later retrieved. This also waits a little longer incase there is another request after the previous batch ended.
In CasperJS you would probably do something like the following to wait for the change in the request status. This uses adds a new function to the casper object and uses casper.waitFor() internally to check the change.
casper.waitForAjaxStop = function(then, onTimeout, timeout){
return this.waitFor(function(){
return this.evaluate(function(){
return window._allAjaxRequestsHaveStopped;
});
}, then, onTimeout, timeout);
};
And use it like this:
casper.start(url).waitForAjaxStop().then(function(){
// do something
}).run();
or this:
casper.start(url).thenClick(selector).waitForAjaxStop().then(function(){
// do something
}).run();

Passing data between controllers in angular while waiting for promise [duplicate]

I want to implement a dynamic loading of a static resource in AngularJS using Promises. The problem: I have couple components on page which might (or not, depends which are displayed, thus dynamic) need to get a static resource from the server. Once loaded, it can be cached for the whole application life.
I have implemented this mechanism, but I'm new to Angular and Promises, and I want to make sure if this is a right solution \ approach.
var data = null;
var deferredLoadData = null;
function loadDataPromise() {
if (deferredLoadData !== null)
return deferredLoadData.promise;
deferredLoadData = $q.defer();
$http.get("data.json").then(function (res) {
data = res.data;
return deferredLoadData.resolve();
}, function (res) {
return deferredLoadData.reject();
});
return deferredLoadData.promise;
}
So, only one request is made, and all next calls to loadDataPromise() get back the first made promise. It seems to work for request that in the progress or one that already finished some time ago.
But is it a good solution to cache Promises?
Is this the right approach?
Yes. The use of memoisation on functions that return promises a common technique to avoid the repeated execution of asynchronous (and usually expensive) tasks. The promise makes the caching easy because one does not need to distinguish between ongoing and finished operations, they're both represented as (the same) promise for the result value.
Is this the right solution?
No. That global data variable and the resolution with undefined is not how promises are intended to work. Instead, fulfill the promise with the result data! It also makes coding a lot easier:
var dataPromise = null;
function getData() {
if (dataPromise == null)
dataPromise = $http.get("data.json").then(function (res) {
return res.data;
});
return dataPromise;
}
Then, instead of loadDataPromise().then(function() { /* use global */ data }) it is simply getData().then(function(data) { … }).
To further improve the pattern, you might want to hide dataPromise in a closure scope, and notice that you will need a lookup for different promises when getData takes a parameter (like the url).
For this task I created service called defer-cache-service which removes all this boiler plate code. It writted in Typescript, but you can grab compiled js file. Github source code.
Example:
function loadCached() {
return deferCacheService.getDeferred('cacke.key1', function () {
return $http.get("data.json");
});
}
and consume
loadCached().then(function(data) {
//...
});
One important thing to notice that if let's say two or more parts calling the the same loadDataPromise and at the same time, you must add this check
if (defer && defer.promise.$$state.status === 0) {
return defer.promise;
}
otherwise you will be doing duplicate calls to backend.
This design design pattern will cache whatever is returned the first time it runs , and return the cached thing every time it's called again.
const asyncTask = (cache => {
return function(){
// when called first time, put the promise in the "cache" variable
if( !cache ){
cache = new Promise(function(resolve, reject){
setTimeout(() => {
resolve('foo');
}, 2000);
});
}
return cache;
}
})();
asyncTask().then(console.log);
asyncTask().then(console.log);
Explanation:
Simply wrap your function with another self-invoking function which returns a function (your original async function), and the purpose of wrapper function is to provide encapsulating scope for a local variable cache, so that local variable is only accessible within the returned function of the wrapper function and has the exact same value every time asyncTask is called (other than the very first time)

Javascript: Properly Globalizing a Websocket Send Function

I am working with a WebSocket and trying to be able to send socket data at anytime from throughout my application. When I attempt to access the send command from within another function, I am receiving:
Uncaught InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable.
This only is occuring when I call a function, this is how I am setting up my websocket:
Main.socket = (function() {
var socket = new WebSocket("ws://server:port");
socket.onopen = function() {
console.log("Socket has been opened!");
}
function send() {
socket.send('test');
}
return {
socket: socket,
send: send
}
})();
I am able to call the function globally, and also when I console.log Main.socket from within a function it is able to see the socket. But when I call the send function I get that error.
Here is an alternative solution to waiting for the web socket connection to come online, replace your call to :
function send() {
web_socket.send('test');
}
with this :
function send(msg) {
wait_for_socket_connection(socket, function() {
socket.send(msg);
});
};
function wait_for_socket_connection(socket, callback){
setTimeout(
function(){
if (socket.readyState === 1) {
if(callback !== undefined){
callback();
}
return;
} else {
console.log("... waiting for web socket connection to come online");
wait_for_socket_connection(socket,callback);
}
}, 5);
};
The problem is that the socket has not been opened yet. WebSocket.send cannot be used until the asynchronous onopen event occurs.
While using setTimeout (for a long enough duration) "should work", the correct way to deal with asynchronous JavaScript programming is to treat program flow as a sequence of dependent events.
In any case, here is a small example showing how to use a jQuery Deferred Object which (as of jQuery 1.8 isn't broken and honors the Promises/A contract):
Main.socket = (function($) {
var socket = new WebSocket("ws://server:port");
// Promise will be called with one argument, the "send" function for this
// socket.
var readyPromise = $.Deferred();
socket.onopen = function() {
console.log("Socket has been opened!");
readyPromise.resolve(socket.send)
}
return readyPromise;
})(jQuery);
Then later, in the code that uses this little module:
Main.socket.then(function (send) {
// This will only be called after `Promise.resolve` is called in the module
// which will be called in the `WebSocket.onopen` callback.
send("Hello world!");
})
// This code may or may not execute before the `then` function above
// depending upon the state the Promise/Deferred Object.
// However, we can get consistent program flow by using `then`-chaining
// of promises.
Of course you don't have to use Promises - callbacks will work just fine, although I prefer the unified contract/framework of Promises - and you can use whatever names or structure is most fitting.
Also, note that it might not be good to have a single WebSocket for the entire page lifecycle as this won't correctly handle disconnect and recovery scenarios.

Better way to detect when a variable != undefined when async request is sent

Given the following:
var doThings = (function ($, window, document) {
var someScopedVariable = undefined,
methods,
_status;
methods = {
init: function () {
_status.getStatus.call(this);
// Do something with the 'someScopedVariable'
}
};
// Local method
_status = {
getStatus: function () {
// Runs a webservice call to populate the 'someScopedVariable'
if (someScopedVariable === undefined) {
_status.setStatus.call(this);
}
return someScopedVariable;
},
setStatus: function () {
$.ajax({
url: "someWebservice",
success: function(results){
someScopedVariable = results;
}
});
}
};
return methods;
} (jQuery, window, document));
The issue is clear, this is an async situation were I would like to wait until someScopedVariable is not undefined, then continue.
I thought of using jQuery's .when() -> .done() deferred call but I cant seem to get it to work. I've also thought of doing a loop that would just check to see if its defined yet but that doesnt seem elegant.
Possible option 1:
$.when(_status.getStatus.call(this)).done(function () {
return someScopedVariable;
});
Possible option 2 (Terrible option):
_status.getStatus.call(this)
var i = 0;
do {
i++;
} while (formStatusObject !== undefined);
return formStatusObject;
UPDATE:
I believe I stripped out too much of the logic in order to explain it so I added back in some. The goal of this was to create an accessor to this data.
I would suggest to wait for the complete / success event of an ajax call.
methods = {
init: function () {
_status.getStatus.call(this);
},
continueInit: function( data ) {
// populate 'someScopedVariable' from data and continue init
}
};
_status = {
getStatus: function () {
$.post('webservice.url', continueInit );
}
};
You cannot block using an infite loop to wait for the async request to finish since your JavaScript is most likely running in a single thread. The JavaScript engine will wait for your script to finish before it tries to call the async callback that would change the variable you are watching in the loop. Hence, a deadlock occurrs.
The only way to go is using callback functions throughout, as in your second option.
I agree with the other answer about using a callback if possible. If for some reason you need to block and wait for a response, don't use the looping approach, that's about the worst possible way to do that. The most straightforward would be use set async:false in your ajax call.
See http://api.jquery.com/jQuery.ajax/
async - Boolean Default: true By default,
all requests are sent asynchronously
(i.e. this is set to true by default).
If you need synchronous requests, set
this option to false. Cross-domain
requests and dataType: "jsonp"
requests do not support synchronous
operation. Note that synchronous
requests may temporarily lock the
browser, disabling any actions while
the request is active.

Categories

Resources