Possible Async Call in a Conditional - javascript

I'm refactoring some legacy code that I didn't originally write and I've come upon an issue with asynchronous data loading. The first time a particular modal is opened, a bunch of data representing a form object gets loaded. A function then cycles through the inputs of the form and fleshes them out as needed. It looks something like this (extremely simplified):
component.inputs.forEach(function(input) {
if (input.field == 'foo') {
input.cols = 5;
//etc.
}
if (input.field == 'bar') {
DataService.getBars().then(function(data){
data.forEach(function(e){
input.options.push(e.description);
});
};
}
if (input.field == 'baz') {
input.pattern = /regex/;
//etc.
}
});
return component;
The problem, of course, is that if my input.field is 'bar', the code continues running and hits the final return before the async call to DataService is resolved, so the first time the modal is opened, the input.options have not been filled out for 'bar' input.
Is it possible to make the code wait for the promise from the DataService to be resolved before continuing, or is there another way to handle the situation where in most cases the function is synchronous, but has to make an async call in only one case? Or have I shot myself in the foot by including an async call in this big chain of ifs?

One approach is to create a promise and attach it as a property to your returned object.
function getComponent() {
component.inputs.forEach(function(input) {
//create initial promise
var $promise = $q.when(input);
if (input.field == 'foo') {
input.cols = 5;
//etc.
}
if (input.field == 'bar') {
//chain from initial promise
$promise = $promise.then(function () {
//return promise for chaining
return getBarPromise(input);
});
}
//attach promise to input object
input.$promise = $promise;
});
var promises = [];
angular.forEach(inputs, function(input) {
promises.push(input.$promise);
});
//create composite promise
var $promise = $q.all(promises);
//final chain
$promise = $promise.then( function() {
//return component for chaining
return component;
});
//attach promise to component
component.$promise = $promise;
return component;
};
The returned component object will eventually be filled in with the results of the service calls. Functions that need to wait for completion of all the service calls can chain from the attached $promise property.
$scope.component = getComponent();
$scope.component.$promise.then( function (resolvedComponent) {
//open modal
}).catch( function(errorResponse) {
//log error response
});
Because calling the then method of a promise returns a new derived promise, it is easily possible to create a chain of promises. It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs.1

If you want to stay with your existing code structure and make this work, you probably will need to use promises. You can also use javascript's map function. Note: you would need to inject $q into wherever you want to call this function.
function getComponent() {
var deferred = $q.defer(),
deferred2 = $q.defer(),
promises = component.inputs.map(function(input)) {
if (input.field == 'foo') {
input.cols = 5;
deferred2.resolve();
}
else if (input.field == 'bar') {
DataService.getBars().then(function(data) {
data.forEach(function(e){
input.options.push(e.description);
});
deferred2.resolve();
}).catch(function(err)) {
deferred2.reject(err);
});
}
else if (input.field == 'baz') {
input.pattern = /regex/;
deferred2.resolve();
}
return deferred2.promise;
});
$q.all(promises)
.then(function() {
deferred.resolve(component);
}).catch(function(err) {
deferred.reject(err);
});
return deferred.promise;
}
Once each input in component.inputs has been parsed appropriately, then the $q.all block will trigger and you can return your new component object.
Finally, to set your component object, simply do the following:
getComponent().then(function(result)) {
//Set component object here with result
$scope.component = result;
}).catch(function(err) {
// Handle error here
});

Related

getting ".then() is not a function" error when using AngularJS

This is my JS:
self.obj = {}
self.obj.accessErrors = function(data) {
var cerrorMessages = [];
for (prop in data) {
if (data.hasOwnProperty(prop)){
if (data[prop] != null && data[prop].constructor == Object) {
self.obj.fetch[accessErrors](data[prop]);
}
else {
cerrorMessages.push(data[prop]);
}
}
}
return cerrorMessages;
};
self.obj.fetch = {
X: function() {
// do stuff.
},
Y: function(callback) {
$http.get('/yposts/').then(
function(response) {
self.posts = response.data;
callback(self.posts);
},
function(response) {
self.posts = {};
self.obj.accessErrors(response.data).then(function(cerrrorMessages) {
callback(posts, cerrorMessages);
});
}
);
}
};
And I am getting an error pointing to this line:
self.obj.accessErrors(response.data).then(function(cerrrorMessages) {
The error says:
TypeError: self.obj.accessErrors(...).then is not a function
Any idea how to solve this?
self.obj.accessErrors(response.data) does not return a promise so therefore, you can't use promise methods on it.
If you want it to return a promise and you want that promise to reflect when all the fetch() operations are done and those operations are actually async, then you will have to make all your async code into using promises and you will have to combine them all using Promise.all() or the angular equivalent and convert from using callbacks in fetch to just using a promise. Right now, you have a mix which is difficult to program with.
The .then() construction is only needed when using Promise objects - essentially, instead of returning a value, the function returns an object that resolves to a value at some point in the future (which is then passed into the function that you pass to .then().
But you are right in that you need an asynchronous pattern to do this correctly, since fetch.Y is an asynchronous method. A good thing to do would be to create an array of promises at the beginning of your accessErrors function, like so:
var fetchPromises = [];
and then replace self.obj.fetch[accessErrors](data[prop]); with something that calls push on that array to add the promise that fetch returns to it.
Then, instead of returning accessErrors, return Promise.all(fetchPromises).
This will require some fairly significant modification to your code, however - namely, you will need to rewrite it so that it uses the Promise API instead of this callback by itself (which shouldn't be too difficult to do).

Javascript Promises returning strange object

(function() {
"use strict";
var storage = chrome.storage.sync;
var localStorage = null;
function getOutsideScope(property) {
if (localStorage.hasOwnProperty(property)) {
return localStorage[property];
}
}
function fulfill(data) {
return data;
}
function rejected(err) {
log(err);
}
window.app.storage = {
get: function(storageKey, storageProp) {
var promise = new Promise(function(fullfill, reject) {
chrome.storage.sync.get(storageKey, function(data) {
if (data.hasOwnProperty(storageKey)) {
fullfill(data[storageKey]);
} else {
reject(new Error(storageKey + " does not exist in the storage values."));
}
});
});
return promise.then(fulfill, rejected);
},
set: function(storageKey, storageItem) {
},
onChanged: function(fn) {
}
};
})();
So the above is my IIFE wrapper for chrome storage, and well the return is being a pain in the bleep. So I decided to give Promises a try, this is my first time so don't be too rough on me on this. Basically this is what I want to do
var property = app.storage.get("properties");
//property should equal "value"
//except it returns undefined
So adding the promises it does get the value except it returns this
Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "value"}
Am I doing something wrong with Promises I've tried reading HTML5Rocks, the MDN, and video tutorials except it doesn't really mention much of how to return a value. This does NOT work
get:function(storageKey,storageProp) {
chrome.storage.sync.get(storageKey,function(data) {
//for simplicity and no error checking -_-
return data[storageKey];
});
}
The function returns exactly what it is supposed to return: A promise. To get the value once the promise is fulfilled, you add a callback via .then:
app.storage.get("properties").then(function(property) {
// property will be "value"
});
From MDN:
A Promise represents a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers to an asynchronous action's eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of the final value, the asynchronous method returns a promise of having a value at some point in the future.
[...]
A pending promise can become either fulfilled with a value, or rejected with a reason (error). When either of these happens, the associated handlers queued up by a promise's then method are called.

Scoping within Angular service call

I'm confused as to why I cannot get this service call to perform as needed. The console.log's within definitionsService.get promise resolution are what I would expect (the object I'm aiming to return). However the console.log right before I return defs is undefined which, of course, means my returned value is undefined. What am I missing?
function getDefinitions() {
var defs;
definitionsService.get().$promise.then(function(data) {
console.log(data);
defs = data;
console.log(defs);
});
console.log(defs);
return defs;
};
I changed the above to:
function getDefinitions() {
var defs = $q.defer();
definitionsService.get().$promise.then(function(data) {
defs.resovle(data);
});
return defs.promise;
};
per the below answer.
I also changed the way I call this method per the same answer like this:
function detail(account) {
getDefinitions().then(function(definitions) {
var key = angular.isDefined(definitions.ABC[account.code]) ? account.code : '-';
return definitions.ABC[key].detail;
});
}
Then in my controller I'm trying to do the following:
var getAccounts = function() {
playersService.getAccounts({
playerId: playerId
}).$promise.then(function(accounts) {
for (var i = 0; i < accounts.length; i++) {
accounts[i].detail = utilitiesService.detail(accounts[i]);
}
vm.accounts = accounts;
});
};
var init = function() {
getAccounts();
};
init();
My problem is that accounts[i].detail is consistently undefined.
Welcome to the world of asynchronous calls.
If you are making an async call inside getDefinitions (from definitonsService), then you must assume getDefinitions() is async as well. Which means you cannot simply return defs.
When you print defs before returning it, the async call of the service has yet to have been carried out.
defs should also be a promise object. You can then return it as you do, but the method invoking it should also use it with .then(function(defs)...
Or in code form:
function getDefinitions() {
var defs = $q.defer();
definitionsService.get().$promise.then(function(data) {
defs.resovle(data);
});
return defs.promise;
};
And whoever calls getDefinitions() :
getDefinitions().then(function(defs) {
// do something with defs...
}
Answer to question after edit:
The problem is once again with the async nature inside the getAccounts method.
utilitiesService.detail is an async method. So you are in fact assigning promises, not values, to accounts[i].detail.
Since each account will entail an async call, using $q.all seems like a good idea:
var promises = [];
for (var i = 0; i < accounts.length; i++) {
promises.push(utilitiesService.detail(accounts[i]));
}
$q.all(promises).then(function(values)){
// you should have all the account details inside 'values' and can update vm.accounts
}
remember - getting the promise is synchronous. Getting the value that the promise... well, promises to get you - that's asynchronous.
The problem is that you are returning defs before the promise has resolved, while defs === None.
You can solve this by changing your code a bit:
function getDefinitions() {
return definitionsService.get().$promise.then(function(data) {
return data;
});
};

Bluebird Promise: Nested or conditional chains

I use Bluebird Promises for a Node.js application. How can I introduce conditional chain branches for my application? Example:
exports.SomeMethod = function(req, res) {
library1.step1(param)
.then(function(response) {
//foo
library2.step2(param)
.then(function(response2) { //-> value of response2 decides over a series of subsequent actions
if (response2 == "option1") {
//enter nested promise chain here?
//do().then().then() ...
}
if (response2 == "option2") {
//enter different nested promise chain here?
//do().then().then() ...
}
[...]
}).catch(function(e) {
//foo
});
});
};
Apart from not having figured out a working version of this yet, this solution feels (and looks) weird somehow. I got a sneaking suspicion that I am somewhat violating the concept of promises or something like that. Any other suggestions how to introduce this kind of conditional branching (each featuring not one but many subsequent steps)?
Yes, you can do it, just like that. The important thing is just to always return a promise from your (callback) functions.
exports.SomeMethod = function(req, res) {
return library1.step1(param)
// ^^^^^^
.then(function(response) {
… foo
return library2.step2(param)
// ^^^^^^
.then(function(response2) {
if (response2 == "option1") {
// enter nested promise chain here!
return do().then(…).then(…)
// ^^^^^^
} else if (response2 == "option2") {
// enter different nested promise chain here!
return do().then(…).then(…)
// ^^^^^^
}
}).catch(function(e) {
// catches error from step2() and from either conditional nested chain
…
});
}); // resolves with a promise for the result of either chain or from the handled error
};
Just return additional promises from within your .then() handler like I show below. The key is to return a promise from within a .then() handler and that automatically chains it into the existing promises.
exports.SomeMethod = function(req, res) {
library1.step1(param)
.then(function(response) {
//foo
library2.step2(param)
.then(function(response2) { //-> value of response2 decides over a series of subsequent actions
if (response2 == "option1") {
// return additional promise to insert it into the chain
return do().then(...).then(...);
} else if (response2 == "option2") {
// return additional promise to insert it into the chain
return do2().then(...).then(...);
}
[...]
}).catch(function(e) {
//foo
});
});
};

wait for asynchronous call to return when kicked off somewhere else

I've tried to find the answer to this and have started reading about promises / deferred, but when kicked off from somewhere else I don't know how to approach this.
angular.module('myapp.utilities').factory('codetabelService', ['Restangular', function(Restangular) {
var initialized = false;
var listtopopulate1 = [];
var listtopopulate2 = [];
var update = function() {
Restangular.all('codeTabel').getList()
.then(function(codetabellen) {
codetabellen.forEach(function(entry) {
//do some processing on return values
listtopopulate1.push(entry);
listtopopulate2.push(entry);
});
initialized=true;
});
};
return {
initialize: function() {
if (!initialized) {
update();
}
},
getListValuesType1: function() {
//How do I wait here for initialized to become true?
return listtopopulate1;
},
getListValuesType2: function() {
//How do I wait here for initialized to become true?
return listtopopulate2;
}
};
}]);
So what I'm trying to do is cache some values when my single page app starts.
On my root controller I call the initialize method which starts the async call to the backend.
When my views are being loaded and the controller sets the scope values to the result of getListValuesType1() the asynch call is sometimes not yet complete.
Because the async load was not triggered by the controller that called the method getListValuesType1() I'm not sure if promises will work here (I admit, I'm still new to this)
I found out you can put a timer on it, but this didn't seem right. It just feels there's a better solution out there.
Yes you can effectively use promise and promise caching to do this, one way you can achieve this by doing:-
angular.module('myapp.utilities').factory('codetabelService', ['Restangular', '$q', function(Restangular, $q) {
var initialized;//Use this to cache the promise
var listtopopulate1 = [];
var listtopopulate2 = [];
var _initialize = function() {
//If already initialized just return it which is nothing but the promise. This will make sure initialization call is not made
return initialized || (initialized= Restangular.all('codeTabel').getList()
.then(function(codetabellen) {
codetabellen.forEach(function(entry) {
listtopopulate1.push(entry);
listtopopulate2.push(entry);
});
//Note- You could even return the data here
}, function(){
//Just clean up incase call is a failure.
initialized = null;
//Just reject with something if you want to:-
//return $q.reject("SomeError")
}));
};
return {
initialize: function() {
return _initialize(); //Just return promise incase you want to do somthing after initialization
},
getListValuesType1: function() {
return _initialize().then(function(){ //return promise with a chain that resolves to the list
return listtopopulate1;
});
},
getListValuesType2: function() {
return _initialize().then(function(){ //return promise with a chain that resolves to the list
return listtopopulate2;
});
}
};
}]);
And while using it, you could do:-
codetabelService.getListValuesType1().then(function(list){
$scope.list1 = list;
});
With this you can even get rid of the initialize call from the contract and make the ajax call only during the first usage of getListX methods.
promises will work for this. You may need to refactor some things though.
angular.module('myapp.utilities').factory('codetabelService', ['Restangular', function(Restangular) {
var initialized = false;
var listtopopulate1 = [];
var listtopopulate2 = [];
var update = function() {
return Restangular.all('codeTabel').getList()
.then(function(codetabellen) {
codetabellen.forEach(function(entry) {
//do some processing on return values
listtopopulate1.push(entry);
listtopopulate2.push(entry);
});
initialized=true;
});
};
return {
initialize: function() {
if (!initialized) {
this.updatePromise = update();
}
},
getListValuesType1: function() {
//How do I wait here for initialized to become true?
return this.updatePromise.then(function() {
// you'll want to refactor the callee to handle a promise here
return listtopopulate1;
});
},
getListValuesType2: function() {
return this.updatePromise.then(function(){
// you'll want to refactor the callee to handle a promise here
//How do I wait here for initialized to become true?
return listtopopulate2;
});
//How do I wait here for initialized to become true?
}
};
}]);

Categories