I use mbostock/queue for queuing few async operation. It is more to rate limit (UI generate few events, where the backend can process it slowly), and also to make sure they are processed sequentially. I use it like
function request(d, cb) {
//some async oper
add.then(function(){
cb(null, "finished ")
})
}
var addQ = queue(1);
addQ.defer(request) //called by few req at higher rates generated by UI
I already uses angular.js $q for async operation. So, do I have to use mbostock/queue, or can I build a queue out of $q (which is in spirit https://github.com/kriskowal/q)
Thanks.
Basic $q Chain Example
Yes you can build a chained queue using Angular's $q! Here is an example that shows you how you could use recursion to create a queue of any length. Each post happens in succession (one after another). The second post will not start until the first post has finished.
This can be helpful when writing to databases. If the database does not have it's own queue on the backend, and you make multiple writes at the same time, you may find that not all of your data is saved!
I have added a Plunkr example to demonstrate this code in action.
$scope.setData = function (data) {
// This array will hold the n-length queue
var promiseStack = [];
// Create a new promise (don't fire it yet)
function newPromise (key, data) {
return function () {
var deferred = $q.defer();
var postData = {};
postData[key] = data;
// Post the the data ($http returns a promise)
$http.post($scope.postPath, postData)
.then(function (response) {
// When the $http promise resolves, we also
// resolve the queued promise that contains it
deferred.resolve(response);
}, function (reason) {
deferred.reject(reason);
});
return deferred.promise;
};
}
// Loop through data creating our queue of promises
for (var key in data) {
promiseStack.push(newPromise(key, data[key]));
}
// Fire the first promise in the queue
var fire = function () {
// If the queue has remaining items...
return promiseStack.length &&
// Remove the first promise from the array
// and execute it
promiseStack.shift()()
// When that promise resolves, fire the next
// promise in our queue
.then(function () {
return fire();
});
};
// Begin the queue
return fire();
};
You can use a simple function to begin your queue. For the sake of this demonstration, I am passing an object full of keys to a function that will split these keys into individual posts, then POST them to Henry's HTTP Post Dumping Server. (Thanks Henry!)
$scope.beginQueue = function () {
$scope.setData({
a: 0,
b: 1,
/* ... all the other letters of the alphabet ... */
y: 24,
z: 25
}).then(function () {
console.log('Everything was saved!');
}).catch(function (reason) {
console.warn(reason);
});
};
Here is a link to the Plunkr example if you would like to try out this code.
The short answer is no, you don't need an extra library. Promise.then() is sufficiently "atomic". The long answer is: it's worth making a queue() function to keep code DRY. Bluebird-promises seems pretty complete, but here's something based on AngularJS's $q.
If I was making .queue() I'd want it to handle errors as well.
Here's an angular service factory, and some use cases:
/**
* Simple promise factory
*/
angular.module('app').factory('P', function($q) {
var P = $q;
// Make a promise
P.then = function(obj) {
return $q.when(obj);
};
// Take a promise. Queue 'action'. On 'action' faulure, run 'error' and continue.
P.queue = function(promise, action, error) {
return promise.then(action).catch(error);
};
// Oook! Monkey patch .queue() onto a $q promise.
P.startQueue = function(obj) {
var promise = $q.when(obj);
promise.queue = function(action, error) {
return promise.then(action).catch(error);
};
return promise;
};
return P;
});
How to use it:
.run(function($state, YouReallyNeedJustQorP, $q, P) {
// Use a $q promise. Queue actions with P
// Make a regular old promise
var myPromise = $q.when('plain old promise');
// use P to queue an action on myPromise
P.queue(myPromise, function() { return console.log('myPromise: do something clever'); });
// use P to queue an action
P.queue(myPromise, function() {
throw console.log('myPromise: do something dangerous');
}, function() {
return console.log('myPromise: risks must be taken!');
});
// use P to queue an action
P.queue(myPromise, function() { return console.log('myPromise: never quit'); });
// Same thing, but make a special promise with P
var myQueue = P.startQueue(myPromise);
// use P to queue an action
myQueue.queue(function() { return console.log('myQueue: do something clever'); });
// use P to queue an action
myQueue.queue(function() {
throw console.log('myQueue: do something hard');
}, function() {
return console.log('myQueue: hard is interesting!');
});
// use P to queue an action
myQueue.queue(function() { return console.log('myQueue: no easy days'); });
Chained Promises
Angular's $q implementation allows you to chain promises, and then handle resolves of those promises according to your own logic. The methods are a bit different than mbostock/queue, but the intent is the same. Create a function that determines how your defered will be resolved (creating a promise), then make these available to a higher level controller/service for specific resolution handling.
Angular uses $q.defer() to return promise objects, which can then be called in the order you wish inside your application logic. (or even skipped, mutated, intercepted, etc...).
I'll throw down some code, but I found this 7 minute video at egghead.io to be the best short demo: https://egghead.io/lessons/angularjs-chained-promises, and it will do a FAR better job of explaining. Thomas (the presenter) builds a very small flight dashboard app that queues up weather and flight data, and processes that queue when a user queries their itenerary. ThomasBurleson/angularjs-FlightDashboard
I will be setting up a smaller demonstration on codepen, using the situation of 'eating at a restaurant' to demonstrate this concept: http://codepen.io/LongLiveCHIEF/pen/uLyHx
Code examples here:
https://gist.github.com/LongLiveCHIEF/4c5432d1c2fb2fdf937d
Related
I am looking to fire a series of functions after a few other functions have run and loaded content to my webpage. I have used setTimeOuts but I understand it's poor practice given there could be a lag in the connection, in which case they would run incorrectly. How do I do functions sequentially without using setTimeOuts.
The avgEmotion and avgSentiment functions are supposed to run once a series of lists are created on the webpage through the newsApiLeft() and newsApiRight() functions.
Thanks so much in advance.
button.addEventListener("click", function () {
var filter = document.getElementById("news_cat");
var filterSource = document.getElementById("news_source");
var category = filter.value;
var source = filterSource.value;
params.sources = source;
params.q = category;
parameters.q = category;
var filterL = document.getElementById("news_sourceL");
var sourceL = filterL.value;
parameters.sources = sourceL;
showContent();
clearTextRight();
clearTextLeft();
newsApiLeft();
newsApiRight();
setTimeout(avgEmotionR, 2000);
setTimeout(avgEmotionL, 2000);
setTimeout(avgSentiment, 2000);
})
The right way to execute a function after another one returns (asynchronous calls) is by using Promises. Make your newsApiLeft() and newsApiRight functions returning a Promise and then call them like follows:
var newsApiLeft = new Promise(function(resolve, reject) {
// do something long running
let everythingWasOK = true;
if (everythingWasOK) {
resolve("I can return a value");
//or just resolve();
} else {
reject(Error("Or error"));
}
});
newsApiLeft.then((returnedData)=>{
avgEmotionL();
//run another function here;
//you can use the returnedData
}, (error)=>{
//optionally handle error
})
You can achieve this using JavaScript promises.
Here i created a Promise A, where I do my logic processing at the comment mentioned below. Then you need to either resolve or reject it based on your logic. If you resolve then() function will be called.
In the first then() function you can nest new promise. Like wise you can nest as many as you want. This way you can make sure promise B will execute only after end of promise A.
var A = new Promise(function(resolve, reject) {
// Do an async task here and then...
if(/* good condition */) {
resolve('Success!');
}
else {
reject('Failure!');
}
});
A.then(function() {
/* do something with the result */
}).catch(function() {
/* error :( */
})
For executing promise A just do A();
You could achieve this using callbacks:
function newsApiLeft(callback) {
// Load stuff
// Then call the callback; the code you want
// to run after the loading is complete
callback();
}
newsApiLeft(function() {
avgEmotion();
avgSentiment();
});
If you're using the same callback for both Left and Right, you could save the callback and use it for both:
var callback = function() {
avgEmotion();
avgSentiment();
}
newsApiLeft(callback);
newsApiRight(callback);
I'm new on AngularJS and JavaScript.
I am getting remote information for each of the elements of an array (cars) and creating a new array (interested prospects). So I need to sync the requests. I need the responses of each request to be added in the new array in the same order of the cars.
I did it first in with a for:
for (a in cars) {
//async request
.then(function () {
//update the new array
});
}
This make all the requests but naturally didn't update the new array.
After seeking in forums, I found this great examples and explanations for returning a intermediate promise and sync all of them.
1. http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
2. http://stackoverflow.com/questions/25605215/return-a-promise-from-inside-a-for-loop
3. http://www.html5rocks.com/en/tutorials/es6/promises/
(#MaurizioIndenmark, #Mark Rajcok , #Michelle Tilley, #Nolan Lawson)
I couldn't use the Promise.resolve() suggested in the second reference. So I had used $q.defer() and resolve(). I guess I have to inject a dependency or something else that I missed. As shown below:
In the Controller I have:
$scope.interestedProspects = [] ;
RequestDetailsOfAsync = function ($scope) {
var deferred = $q.defer();
var id = carLists.map(function (car) {
return car.id;
}).reduce(function (previousValue, currentValue) {
return previousValue.then(function () {
TheService.AsyncRequest(currentValue).then(function (rData) {
$scope.interestedProspects.push(rData);
});
});
}, deferred.resolve());
};
In the Service I have something like:
angular.module('app', []).factory('TheService', function ($http) {
return {
AsyncRequest = function (keyID) {
var deferred = $q.defer();
var promise = authorized.get("somep.provider.api/theService.json?" + keyID).done(function (data) {
deferred.resolve(data);
}).fail(function (err) {
deferred.reject(err);
});
return deferred.promise;
}
}
}
The displayed error I got: Uncaught TypeError: previousValue.then is not a function
I made a jsfiddle reusing others available, so that it could be easier to solve this http://jsfiddle.net/alisatest/pf31g36y/3/. How to wait for AsyncRequests for each element from an array using reduce and promises
I don't know if the mistakes are:
the place where the resolve is placed in the controller function.
the way the reduce function is used
The previousValue and currentValue sometimes are seen by javascript like type of Promise initially and then as a number. In the jsfiddle I have a working example of the use of the reduce and an http request for the example.
Look at this pattern for what you want to do:
cars.reduce(function(promise, car) {
return promise.then(function(){
return TheService.AsyncRequest(car).then(function (rData) {
$scope.details.push(rData);
});
});
}, $q.when());
This will do all the asynchronous calls for every car exactly in the sequence they are in the cars array. $q.all may also be sufficient if the order the async calls are made doesn't matter.
It seems you are calling reduce on an array of ids, but assume in the passed function that you are dealing with promises.
In general, when you want to sync a set of promises, you can use $q.all
You pass an array of promises and get another promise in return that will be resolved with an array of results.
I'm having trouble wrapping my head around promises. I'm using the Google Earth API to do a 'tour' of addresses. A tour is just an animation that lasts about a minute, and when one completes, the next should start.
Here's my function that does a tour:
var tourAddress = function (address) {
return tourService.getLatLong(address).then(function (coords) {
return tourService.getKmlForCoords(coords).then(function (kml) {
_ge.getTourPlayer().setTour(kml);
_ge.getTourPlayer().play();
var counter = 0;
var d = $q.defer();
var waitForTour = function () {
if (counter < _ge.getTourPlayer().getDuration()) {
++counter;
setTimeout(waitForTour, 1000);
} else {
d.resolve();
}
};
waitForTour();
return d.promise;
});
});
}
This seems to work pretty well. It starts the animation and returns a promise that resolves when the animation is complete. Now I have an array of addresses, and I want to do a tour foreach of them:
$scope.addresses.forEach(function (item) {
tourAddress(item.address).then(function(){
$log.log(item.address + " complete");
});
});
When i do this, they all execute at the same time (Google Earth does the animation for the last address) and they all complete at the same time. How do I chain these to fire after the previous one completes?
UPDATE
I used #phtrivier's great help to achieve it:
$scope.addresses.reduce(function (curr,next) {
return curr.then(function(){
return tourAddress(next.address)
});
}, Promise.resolve()).then(function(){
$log.log('all complete');
});
You're right, the requests are done immediately, because calling tourAddress(item.address) does a request and returns a Promise resolved when the request is done.
Since you're calling tourAddress in a loop, many Promises are generated, but they don't depend on each other.
What you want is to call tourAdress, take the returned Promise, wait for it to be resolved, and then call tourAddress again with another address, and so on.
Manually, you would have to write something like :
tourAddress(addresses[0])
.then(function () {
return tourAddress(addresses[1]);
})
.then(function () {
return tourAddress(addresses[2]);
})
... etc...
If you want to do that automatically (you're not the first one : How can I execute array of promises in sequential order?), you could try reducing the list of address to a single Promise that will do the whole chain.
_.reduce(addresses, function (memo, address) {
return memo.then(function (address) {
// This returns a Promise
return tourAddress(address);
});
}, Promise.resolve());
(This is pseudo-code that uses underscore, and bluebird, but it should be adaptable)
It's because you're doing things asynchronously so they'll all run at the same time. This answer should help you rewrite your loop so each address is run after the next one.
Asynchronous Loop of jQuery Deferreds (promises)
I have the following code that causes the two call to Webtrends to be cancelled (ie these two calls did not give a http 200 but a cancelled message in the network tab of the browser) when I call it
mercury.Tracking.logUsage("export", GetSelectedExportType(form));
mercury.Tracking.logUsage('exportchart', mercury.ChartContainer.currentChartUri(), path);
form[0].submit();
I rewrote this in this way to avoid this issue, as it seemed to me that the reason why the calls to Webtrends were being cancelled was because the form submit was making that happen so before calling submit on the form I wait two seconds.
mercury.Tracking.logUsage("export", GetSelectedExportType(form));
mercury.Tracking.logUsage('exportchart', mercury.ChartContainer.currentChartUri(), path);
var submit = function () {
setTimeout(function() {
form[0].submit();
}, 2000);
};
submit();
Question is, is there a better way, using promises or callbacks or whatever to do this?
The logUsage code is
(function ($, window) {
function Tracking() {
}
Tracking.prototype.chartTitle = function () {
return $('#chartNameInfo').text();
};
Tracking.prototype.hostName = function () {
return $('#trackingVars').data('host-name');
};
Tracking.prototype.page = function () {
return $('#trackingVars').data('page');
};
Tracking.prototype.currentUser = function () {
return window.config.userId;
};
Tracking.prototype.logUsage = function (action, resourceUri, actionTargetUri, additionalTags) {
// action: action performed - e.g. create, delete, export
// resourceUri: URI of API resource *on* which action is being performed (required), e.g. /users/current/annotations/{annotation-id}
// actionTargetUri: URI of API resource *to* which action is being performed (optional), e.g. /charts/{chart-id}
if (action.indexOf("DCSext.") < 0) {
action = "DCSext." + action;
}
var jsonString = '{"' + action + '"' + ':"1"}';
var jsonObj = JSON.parse(jsonString);
if (additionalTags == null) {
additionalTags = jsonObj;
}
else {
additionalTags = $.extend({}, additionalTags, jsonObj); //Append two JSON objects
}
var trackingargs = $.extend({
'DCSext.resource-uri': resourceUri,
'DCSext.action-target-uri': actionTargetUri,
'WT.ti': this.chartTitle(),
'DCSext.dcssip': this.hostName(),
'DCSext.em-user-id': this.currentUser(),
dsci_uri: this.page()
}, additionalTags);
try {
WebTrends.multiTrack({ args: trackingargs });
} catch (e) {
console.log(e);
}
};
window.Tracking = new Tracking();
$(function() {
$('body').on('click', 'a[data-tracking-action]', function() {
window.Tracking.logUsage($(this).data('tracking-action'), $(this).data('tracking-resource'));
});
$(document).on('attempted-access-to-restricted-resource', function(event, href) {
window.Tracking.logUsage('unauthorisedResourceAccessUpsell', href.url);
});
});
})(jQuery, window);
With the extra information provided, I think I can now answer your question.
From WebTrends doc, you can add a finish callback to your WebTrends.MultiTrack call.
What you could do:
Tracking.prototype.logUsage = function (action, resourceUri, actionTargetUri, additionalTags) {
...
var finished = $.Deferred();
...
try {
WebTrends.multiTrack({ args: trackingargs, finish: function(){finished.resolve();}});
}
...
return finished;
}
and then in your code:
$.when(mercury.Tracking.logUsage("export", GetSelectedExportType(form)),
mercury.Tracking.logUsage('exportchart', mercury.ChartContainer.currentChartUri(), path))
.done(function(){
form[0].submit();
});
I have not tested this, but I think it should work. Hope it helps.
Explanations:
jQuery.when()
Description: Provides a way to execute callback functions based on one
or more objects, usually Deferred objects that represent asynchronous
events.
Basically, jQuery.when() will take one or more deferreds (which build promises) or promises and will return one promise that fulfills when they all fulfill. From there, we can choose to add handlers using th e .done() or .then() method to our promise, which will be called once or promise is fulfilled . (A promise represents the result of an asynchronous operation).
So, in the code above, I created a new deferred object in your logUsage method, and that method returns the deferred, so you can pass those deferreds to jQuery.when method and when they will be fulfilled (this is why I added the finish callback in your WebTrends.Multitrack call), the handler passed to deferred.done() will be executed.
I hope this is not too confusing, I'm not sure I'm explaining it correctly.
Not trying to steal Antoine's rep. His answer is essentially fine, but the ... sections can be fleshed out far more efficiently than in the question, plus a few other points for consideration.
Tracking.prototype.logUsage = function (action, resourceUri, actionTargetUri, additionalTags) {
// action: action performed - e.g. create, delete, export
// resourceUri: URI of API resource *on* which action is being performed (required), e.g. /users/current/annotations/{annotation-id}
// actionTargetUri: URI of API resource *to* which action is being performed (optional), e.g. /charts/{chart-id}
try {
// you might as well wrap all the preamble in the try{}, just in case it it error-prone
if (action.indexOf("DCSext.") < 0) {
action = "DCSext." + action;
}
//trackingargs can be defined efficiently as follows, avoiding the need for the variable `jsonObj` and the ugly JSON.parse().
var trackingargs = $.extend({
'DCSext.resource-uri': resourceUri,
'DCSext.action-target-uri': actionTargetUri,
'WT.ti': this.chartTitle(),
'DCSext.dcssip': this.hostName(),
'DCSext.em-user-id': this.currentUser(),
'dsci_uri': this.page()
}, additionalTags || {}); // `additionalTags || {}` caters for missing or null additionalTags
trackingargs[action] = 1;//associative syntax gets around the limitation of object literals (and avoids the need for JSON.parse()!!!).
//to keep things tidy, return $.Deferred(fn).promise()
return $.Deferred(function(dfrd) {
WebTrends.multiTrack({
args: trackingargs,
finish: dfrd.resolve //no need for another function wrapper. `$.Deferred().resolve` and `$.Deferred().reject` are "detachable"
});
}).promise();//be sure to return a promise, not the entire Deferred.
} catch (e) {
console.log(e);
//Now, you should really ensure that a rejected promise is always returned.
return $.Deferred.reject(e).promise();//Surrogate re-throw.
}
};
see comments in code
As Tracking.prototype.logUsage can now return a rejected promise, and as you probably don't want .logUsage() failure to inhibit your form submission, you probably want to convert rejected promises to fulfilled.
$.when(
mercury.Tracking.logUsage("export", GetSelectedExportType(form)).then(null, function() {
return $.when();//resolved promise
}),
mercury.Tracking.logUsage('exportchart', mercury.ChartContainer.currentChartUri(), path).then(null, function() {
return $.when();//resolved promise
})
).done(function() {
form[0].submit();
});
It may seem to be an unnecessary complication to return a rejected promise then convert to success, however :
it is good practice to report asycnhronous failure in the form of a rejected promise, not simply log the error and return undefined.
window.Tracking.logUsage() may be called elsewhere in your code, where it is necessary to handle an error as an error.
How do I delay execution of a function until after all of my $resources have resolved? My goal here is to be able to parse though the log array after all $resources have resolved and push a single success notification to the UI instead of pushing one notification per each success.
I've based my code below off of this question angular -- accessing data of multiple http calls - how to resolve the promises. I realize that $scope.promises is empty because item.$save() doesn't return anything but I hope you can see that I'm trying to push the unresolved promise to the promises array.
$scope.save = function () {
$scope.promises = [];
$scope.log = [];
angular.forEach($scope.menuItems, function(item) {
$scope.promises.push(item.$save(function(menu){
debugger; // execution gets here 2nd
console.debug("success");
$scope.log.push(msg: 'success');
}));
}, this);
$q.all($scope.promises).then(function() {
debugger; // execution gets here 1st
console.debug("all promises resolved");
});
};
Since $save does not return a promise, you will need an intermediate one:
angular.forEach($scope.menuItems, function(item) {
var d = $q.defer(); // <--- the intermediate promise
item.$save(
function(menu){
debugger;
console.debug("success");
$scope.log.push(msg: 'success');
d.resolve(menu); // <--- resolving it, optionally with the value
},
function(error){
d.reject(error); // <--- rejecting it on error
}
);
$scope.promises.push(d.promise);
}, this);
By the way, do not forget to throw away the array of promises, or you will keep garbage:
$q.all($scope.promises).then(...).always(function() {
$scope.promises = null;
});
And, if $scope.promises is NOT exposed to the view, it does not need to be in the scope; it can be just a var.