Hey guys, im not well versed in dealing with asynchronous design patterns, and im having a problem writing a script that does two async data fetches.
Im using Dojo.data.api.Read.Fetch() to make two fetch() calls from seperate databases. The reulsts come back asynchronously. However, I have to cross reference the results, so i want my script to continue once BOTH async fetches are complete. I dont know how to do this, and therein lies the problem.
I am aware of the fetch's onComplete field and how to use it, BUT the best case solution i see there is to call the second fetch in the onComplete of the first fetch. I would like to do these fetches at the same time. Is there a way to do this?
Here's the current structure of my program for illustration purposes:
this.dict1.fetch({query:"blahblahblah", onComplete: function(items) { something here? }});
this.dict2.fetch({query:"blahblahbleh", onComplete: function(items) { or maybe something here? }});
this.orMaybeDoSomethingAfterBothFetches()
Any help would be greatly appreciated!
You could create dojo.Deferreds for each of the fetches and then use dojo.DeferredList and add the deferreds to it - see here. This solution allows you to take advantage of adding 'n' functions to the list of functions you want to call. It also takes advantage of all the dojo.Deferred's callback and errBack functionality.
var fetch1 = new dojo.Deferred();
fetch1.addCallback(this.dict1.fetch...);
var fetch2 = new dojo.Deferred();
fetch2.addCallback(this.dict2.fetch...);
var allFuncs = new dojo.DeferredList([fetch1, fetch2]);
var doStuffWhenAllFuncsReturn = function() {...};
allFuncs.addCallback(doStuffWhenAllFuncsReturn);
// this is a variation of a function I have answered quite a few similar questions on SO with
function collected(count, fn){
var loaded = 0;
var collectedItems = [];
return function(items){
collectedItems = collectedItems.concat(items);
if (++loaded === count){
fn(collectedItems);
}
}
}
var collectedFn = collected(2, function(items){
//do stuff
});
this.dict1.fetch({query:"blahblahblah", onComplete: collectedFn);
this.dict2.fetch({query:"blahblahbleh", onComplete: collectedFn);
An alternative solution is
var store = {
exec: function(){
if (this.items1 && this.items2) {
// do stuff with this.items1 and this.items2
}
}
};
this.dict1.fetch({query:"blahblahblah", onComplete: function(items) {
store.items1 = items;
store.exec();
});
this.dict2.fetch({query:"blahblahbleh", onComplete: function(items) {
store.items2 = items;
store.exec();
});
Related
I have tried asking this question directly on github but there does not seem to be much movement in this project anymore. It would be great if someone on SO has an idea. Is it possible to return a promise in the data function? I have tried the following and it does not seem to work. The issue is that I am trying to make an ajax call within the data-function, which expects a result/data array. Of course I cannot do this when making an asynchronous ajax call.
var ms = $('#mycombo').magicSuggest({minChars: 2, data : function(q) {
return someAPI.findSuggestions(q, currentLang).then(function(response) {
if(!_.isEmpty(response.data.suggestions)) {
_.each(response.data.suggestions, function(suggestion) {
if (suggestion.id && suggestion.label) {
data.push({ id: suggestion.id, name: suggestion.label });
}
});
}
});
return data;
}});
If there is an alternative way of solving this, I would be very grateful for your help.
Thanks in advance.
Michael
For those interested, I have managed to find a solution to the problem. As posted on github (https://github.com/nicolasbize/magicsuggest/issues/281) you need to use the keyup event instead of setting the data property during initialization. So it now looks something like this:
var ms = $('#mycombo').magicSuggest({minChars: 2});
$(ms).on('keyup', function(e, m, v) {
// ... get data via ajax and call "ms.setData(data)" in the response callback ...
// ... you can use m.getRawValue() to get the current word being typed ...
ms.setData(data);
}
This will cause an ajax call to be fired after every key press, so you may want to improve this by adding some kind of a delay or something.
I've also done it this way:
const suggester: any = divElem.magicSuggest({
...more properties here...
data: (query) => {
if (query) {
this.myService.mySearch(query).take(1).subscribe((list) => {
suggester.setData(list);
});
}
return [];
},
...more properties here...
});
Where mySearch(query) returns:
Observable<MyObject[]>
Ill post the code in a second. I am making a call to a service to pull data back. I get the data in an array and each one needs to make another call to another service. so I set my code up like so:
Services.getHelo({
assetSurfaceId: $scope.assetSurfaceId
}).then(function (resp) {
delete resp["$promise"];
delete resp["$resolved"];
$scope.entity.helo = resp;
for (var i = 0; i < $scope.entity.helo.length; i++) {
heloCall($scope.entity.helo, i);
initHelo($scope.entity.helo, i);
}
});
After I delete the promise and $resolved I start my for loop to call to my other functions. the initHelo function just builds a list for me and gets the vars ready for the next call into it. the heloCall makes the other calls i need.
Here is the helocall code:
var heloCall = function (r, i) {
jQuery("#helo").mask("Loading Surface Asset Helos...");
Services.getStatus({
toEntityId: r[i].assetHeloId,
toEntityTypeId: widget_consts.ASSET_HELICOPTER
}).then(function (s) {
delete s["$promise"];
delete s["$resolved"];
$scope.entity.helo[i].status = [];
$scope.entity.helo[i].status = s;
if (s[0].statusLkupShortDesc === "PMC" || s[0].statusLkupShortDesc === "NMC") {
Services.getReason({
toEntityId: s[0].statusId
}).then(function (reason) {
delete reason["$promise"];
delete reason["$resolved"];
if (reason) {
$scope.entity.helo[i].status.reason = [];
$scope.entity.helo[i].status.reason = reason;
initHelo($scope.entity.helo, i);
}
});
Services.getComment({
toEntityId: r[i].assetHeloId
}).then(function (info) {
delete info["$promise"];
delete info["$resolved"];
if (info) {
$scope.entity.helo[i].status.remark = {};
$scope.entity.helo[i].status.remark = info;
initHelo($scope.entity.helo, i);
}
});
}
initHelo($scope.entity.helo, i);
jQuery("#helo").unmask();
});
};
I do a check after the status call is made to make sure it is a status that Would have reasons with it and if it is i make the calls i need.
My issue is this only works when it wants to. I am not sure what i screwed up. I have been trying to get it to work right all day.
It has worked many times for me but I need it to work all the time.
Is there a better way to do this?
I actually have another call in this same file that is set up the same way and works each time with no issues
Any advice would be greatly appreciated
I figured it out. I needed to run a check to make sure that the length of the array returned from the service was not 0. it would cause issues depending on witch call was returned first. after I put this check it in runs fine.
I am sure I am missing something obvious but I can't seem to make heads or tails of this problem. I have a web page that is being driven by javascript. The bindings are being provided by Knockout.js, the data is coming down from the server using Breeze.js, I am using modules tied together with Require.js. My goal is to load the html, load the info from Breeze.js, and then apply the bindings to show the data to the user. All of these things appear to be happening correctly, just not in the correct order which is leading to weird binding errors. Now on to the code.
I have a function that gets called after the page loads
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then(
applyBindings(vm)
);
}
This should call activate, wait for activate to finish, then apply bindings....but it appears to be calling activate, not waiting for it to finish and then runs applybindings.
activate -
function activate() {
logger.log('Frames Admin View Activated', null, 'frames', false);
return datacontext.getAllManufacturers(manufacturers)
.then(function () {
manufacturer(manufacturers()[0]);
}).then(function () {
datacontext.getModelsWithSizes(modelsWithSizes, manufacturers()[0].manufacturerID())
.then(datacontext.getTypes(types));
});
}
datacontext.getAllManufacturers -
var getAllManufacturers = function (manufacturerObservable) {
var query = entityQuery.from('Manufacturers')
.orderBy('name');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (manufacturerObservable) {
manufacturerObservable(data.results);
}
log('Retrieved [All Manufacturer] from remote data source',
data, true);
}
};
datacontext.getModelsWithSizes -
var getModelsWithSizes = function (modelsObservable, manufacturerId) {
var query = entityQuery.from('Models').where('manufactuerID', '==', manufacturerId)
.orderBy('name');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (modelsObservable) {
for (var i = 0; i < data.results.length; i++) {
datacontext.getSizes(data.results[i].sizes, data.results[i].modelID());
// add new size function
data.results[i].addNewSize = function () {
var newValue = createNewSize(this.modelID());
this.sizes.valueHasMutated();
return newValue;
};
}
modelsObservable(data.results);
}
log('Retrieved [Models With Sizes] from remote data source',
data, false);
}
};
Any help on why this promise isn't working would be appreciated, as would any process to figure it out so I can help myself the next time I run into this.
A common mistake when working with promises is instead of specifying a callback, you specify the value returned from a callback:
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then( applyBindings(vm) );
}
Note that when the callback returns a regular truthy value (number, object, string), this should cause an exception. However, if the callback doesn't return anything or it returns a function, this can be tricky to locate.
To correct code should look like this:
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then(function() {
applyBindings(vm);
});
}
I've got this code currently:
handleSubmit: function(e)
{
var to_bucket = this.$('.transaction_bucket').val();
// Move all the transactions for this bucket to the selected bucket
window.app.model.active_transactions.each(
function(transaction)
{
transaction.set({bucket_id: to_bucket});
transaction.save();
}
);
this.model.destroy({success: function() { window.app.model.buckets.fetch();}});
}
How can I modify this so that the destroy only triggers once all the _.each ajax transactions happen? If I had one previous ajax request, I would just use the success: parameter, but I can't do that here.
What's the right way to do this in backbone?
model.save return the xhr object used in the request. With jQuery 1.5, these objects are deferred objects you can use to build a synchronization mechanism.
For example,
var to_bucket = this.$('.transaction_bucket').val(),
calls=[],
mdestroy=this.model.destroy;
window.app.model.active_transactions.each(function (transaction) {
transaction.set({bucket_id: to_bucket});
calls.push(transaction.save());
});
$.when.apply($, calls).then(function () {
mdestroy({success: function () {window.app.model.buckets.fetch();}});
});
I have no experience with backbone, but I would approach this problem like so:
Get the number of active_transactions.
On transaction.save(), check the number of processed transactions (in the success and/or error callback), if it matches the number of active_transactions, then destroy the model.
One possible solution would be to create a custom API method that took the transactions as parameters and did the job on the server side. This would reduce https requests and increase performance as well.
Just keep track of the number of transactions already processed and trigger the destroy in the last callback like so:
handleSubmit: function(e)
{
var to_bucket = this.$('.transaction_bucket').val();
var remainingTransactions = window.app.model.active_transactions.length;
var self = this;
window.app.model.active_transactions.each(
function(transaction)
{
transaction.save({bucket_id: to_bucket}, {
success: function(){
remainingTransactions -= 1;
if(remainingTransactions < 1) {
self.model.destroy({success: function() { window.app.model.buckets.fetch();}});
}
}
});
}
);
}
Asynchronous callbacks are great but when one callback depends on the result of another I have callbacks with api calls that have callbacks, and so on.
apiCall(function () { apiCall(function () { apiCall(function () ...
I can name the callback functions instead of including them inline. That looks prettier and has less nesting but I do not find it any easier to read.
Here is an example. I need to query the local sqlite database, use the result to query a server, then use the response to update the local database.
function sync() {
db.transaction(
function (transaction) {
execute(transaction, 'SELECT max(server_time) AS server_time FROM syncs;', [],
function (transaction, results) { // Query results callback
var t = results.rows.item(0).server_time;
$.post('sync.json', { last_sync_time: (t || '1980-01-01') },
function (data) { // Ajax callback
db.transaction(
function(transaction) {
$(data.thing).each(function () {
var thing = new Thing(this.thing);
thing.insert(transaction);
});
});
});
});
});
}
Is there a way to untagle this (other than naming the callbacks)?
I think you're too quick to discard un-nesting things by naming your functions rather than writing them inline. This is pretty much the only way to clean up that mess.
Instead of:
do_a(
function () {
// more nesting...
}
);
Use names to provide a bit of clarity and purpose to each function:
function on_a_complete() {
}
do_a(on_a_complete);
I think you answered your own question. Naming your callbacks is really the only way to clean this up anymore. Something like:
execute(transaction, 'SELECT max(server_time) AS server_time FROM syncs;', [],handleLocalResults, errorHandler);
handleLocalResults = function (transaction, results)...
handleServerResults = func...
Naming the callbacks is one thing you can do, but the other thing that you need to do is to have non-overlapping SQL transactions. (1)
Your first transaction should be like this:
// The whole thing starts here
db.transaction(selectTimeCB, null, ajaxPost);
The transaction starts with the callback to select the time, and when the transaction is complete, make a call to the ajaxPost operation.
// Initial transaction to get server_time
var selectTimeCB = function(t) {
var query = 'SELECT max(server_time) AS server_time FROM syncs';
t.executeSql(query, [], postLastSyncCB);
};
// This saves the results from the above select, and nothing else.
var server_time;
var postLastSyncCB = function(t, results) {
server_time = results.rows.item(0).server_time;
};
var ajaxPost = function() {
$.post('sync.json', { last_sync_time: (server_time || '1980-01-01') }, nextDbTransaction);
};
If you have overlapped SQL transactions, that can really kill the performance of the database. I recently did a test of 200 mixed transactions on a database with a bit over 500 rows, and found that keeping the transactions separate reduced a run time of over 90 seconds to 3-5 seconds.