Events in a collection/array - javascript

In my jQuery web app I'm using events pretty much.
My component for doing ajax client requests (the name is "comm") to the server has a queue.
This queue is similar to a Backbone.js collection.
So every time I put a new request object into the collection of "comm", the comm object gets an "add" event and the "worker" method of "comm" looks, if there is currently an ongoing request. If there is no one, the worker method processes the request and finally, when everything works fine, triggers a "complete" event on the request object.
In this comm component only one request at a time is possible. If I add an request object to "comm" and the "add" event gets triggered and the worker method sees, that there is already a request, than nothing happens.
My question is: In this situation, what is the best approach for handling this unprocessed request object IN REGARD TO "AN EVENT INFRASTRUCTURE"?
Until now I had 2 different approaches:
1) I could do the following: If my ongoing request is finished, the worker method can check if there is an unprocessed request object in the collection and process it. This is simple, no event is envolved.
2) Or something different: If the worker method starts due to an "add" event and sees, that there is already an ongoing request, I could implement something like: "I can not respond adeqately to this add event, I place you back. Please trigger yourself in 200 milliseconds again, maybe I have time then."
Maybe somebody already had a similar problem and had a very good solution to this?

I think a good approach to this problem would be to implement a "next" event that is fired on the queue. Each time a "complete" event is triggered, it should fire the next event on the queue which then would see if there's any unprocessed requests and if so picks one according to your logic (I assume FIFO)

If I understand you correct, you want to chain requests or other asynchronous events? Then the promises pattern is an elegant solution.
JQuery already implements it with the Deferred object.
With JQuery 1.8+ request chaining can be as simple as:
$.ajax(...).then(
function() {
return $.ajax(...);
}
).then(
function() {
return $.ajax(...);
}
);
But you also can build a more dynamic and complex architecture around it. For example you could implement your queue such that it always stores the last unresolved promise so that you can attach new handlers to it while it is already active.
Generic Example:
var Queue = function() {
// start with resolved promise
var promise = $.Deferred().resolve();
this.add = function(handler) {
// chain and replace latest with next promise
promise = promise.then(function() {
var deferred = $.Deferred();
handler(deferred);
return deferred;
}).promise();
}
};
var q = new Queue();
q.add(function(deferred) {
setTimeout(function() { alert('handler 1'); deferred.resolve(); }, 1000);
});
q.add(function(deferred) {
setTimeout(function() { alert('handler 2'); deferred.resolve(); }, 1000);
});
JSFiddle demo
Here the handlers get a deferred object as parameter and are responsible to resolve (or reject) it if they are "done". Another, more flexible, possibility would be that the handlers have to create the deferred object by themselves and return its promise, this way you also can use other promises like those which are returned by $.ajax

Related

Adding a Deferred to a non async function

I'm currently working on a function that takes a pretty long time to finish and since I won't be able to make it finish faster and im going to call it from other Scripts I was wondering if there is a method to use something like a promise in that function.
Basically
function longrunning(){
var def = new $.Deferred();
var result = {};
[DO STUFF THAT TAKES A WHILE]
def.resolve();
return def.promise(result);
}
My basic problem is, that since all the stuff thats going on isn't async my promise won't be returned until everything is done, so the function that will later be calling longrunning won't know it's async. But of course if I return the promise before executing all of the code, it won't resolve at all. I hope you're getting what I'm trying to do. Hope someone has an idea. Thanks in advance and
Greetings Chris
Wrapping the code in a $.Deferred (or native promise) won't help even if you do manage to get the promise back to the calling code before doing the long-running work (for instance, via setTimeout). All it would accomplish is making the main UI thread seize up later, soon after longrunning returned the promise, instead of when calling longrunning itself. So, not useful. :-)
If the function in question doesn't manipulate the DOM, or if the manipulations it does can be separated from the long-running logic, this is a good candidate to be moved to a web worker (specification, MDN), so it doesn't get run on the main UI thread at all, but instead gets run in a parallel worker thread, leaving the UI free to continue to be responsive.
longrunning wouldn't do the actual work, it would just postMessage the worker to ask it to do the work, and then resolve your promise when it gets back a message that the work is done. Something along these lines (this is just a code sketch, not a fully-implemented solution):
var pendingWork = {};
var workId = 0;
var worker = new Worker("path/to/web/worker.js");
worker.addEventListener("message", function(e) {
// Worker has finished some work, resolve the Deferred
var d = pendingWork[e.data.id];
if (!d) {
console.error("Got result for work ID " + e.data.id + " but no pending entry for it was found.");
} else {
if (e.data.success) {
d.resolve(e.data.result);
} else {
d.reject(e.data.error);
}
delete pendingWork[e.data.id];
}
});
function longrunning(info) {
// Get an identifier for this work
var id = ++workid;
var d = pendingWork[id] = $.Deferred();
worker.postMessage({id: id, info: info});
return d.promise();
}
(That assumes what the worker sends back is an object with the properties id [the work ID], success [flag], and either result [the result] or error [the error].)
As you can see, there we have longrunning send the work to the worker and return a promise for it; when the worker sends the work back, a listener resolves the Deferred.
If the long-running task does need to do DOM manipulation as part of its work, it could post the necessary information back to the main script to have it do those manipulations on its behalf as necessary. The viability of that naturally depends on what the code is doing.
Naturally, you could use native promises rather than jQuery's $.Deferred, if you only have to run on up-to-date browsers (or include a polyfill):
var pendingWork = {};
var workId = 0;
var worker = new Worker("path/to/web/worker.js");
worker.addEventListener("message", function(e) {
// Worker has finished some work, resolve the Deferred
var work = pendingWork[e.data.id];
if (!work) {
console.error("Got result for work ID " + e.data.id + " but no pending entry for it was found.");
} else {
if (e.data.success) {
work.resolve(e.data.result);
} else {
work.reject(e.data.error);
}
delete pendingWork[e.data.id];
}
});
function longrunning(info) {
return new Promise(function(resolve, reject) {
// Get an identifier for this work
var id = ++workid;
pendingWork[id] = {resolve: resolve, reject: reject};
worker.postMessage({id: id, info: info});
});
}

Force jQuery Deferred to wait until Ajax complete in "then" handler

I have situation where I believe I need to create a Deferred object with a "then" handler, but wait until the "then" handler has completed it's own promise before moving on.
The use case is a record object, and the above function is it's save method. The record object has an attribute called saveQueue, which is set to $.Deferred() on the record's instantiation. The resolve call on saveQueue was supposed to make sure the Deferred there is always executing every new handler attached to it as soon as it could. The idea being that you can call save several times on the record in short succession, but the calls will run one after another, and not overlap.
I am using a Deferred to enqueue Ajax calls, so that one does not run until the previous one call finished. However, from the same method, I want to return a Deferred that can be resolved/rejected by the jQuery Ajax object, like so:
record.saveQueue = $.Deferred();
self.save = function( record ){
var deferredAction = $.Deferred();
deferredAction.then(function() {
return $.post("/example_save_endpoint");
});
record.saveQueue.always(function(){
deferredAction.resolve();
}).resolve();
return deferredAction;
}
However, when I use this code, the deferredAction promise always ends up resolved, presumably because the #then handler is returning a "pending" (and thus non-rejecting) promise. Is there any way to force the Deferred to wait until the Ajax promise is complete before resolving/rejecting? Or is there another, better way to thread this needle?
Your idea might work, but
the queue must not be resolved using .resolve() every time the method is called, instead it should be initialised only with a resolved promise.
to actually queue on the record.saveQueue, it needs to be changed (overwritten) on every method call, to represent the end of the latest request.
And we don't need any deferreds for that, as we can work with the promises that $.post returns.
So use this:
var emptyQueue = $.when(undefined); // an already fulfilled promise as the start
// equivalent: = $.Deferred().resolve().promise();
function startQueue() {
return emptyQueue; // yes, this delibaretely returns a constant, the begin
// of the queue always looks the same (and is never mutated)
}
// every time you create a record, do
record.saveQueue = startQueue();
// and use that in your methods:
this.save = function(record) {
var queuedRequestResult = record.saveQueue.then(function() {
return $.post("/example_save_endpoint");
// ^^^^^^ promises chain :-)
});
// Magic happens here:
record.saveQueue = queuedRequestResult // we swap the previous queue promise for a new
// one that resolves only after the request
.then(startQueue, startQueue); // and make sure it then starts with a fresh
// queue, especially when the request failed
//.then(null, startQueue) is similar, except unnecessarily remembering the last result
return queuedRequestResult;
}
I would probably choose not to do it this way, but a deferred/promise can indeed be used as a queuing device.
You need a slight(?) variation of what you already tried.
self.queue = $.when();//A resolved promise, used to form a queue of functions in a .then() chain.
self.save = function(data) {
var dfrd = $.Deferred();//A Deferred dedicated to this particular save.
self.queue = self.queue.then(function() {
return $.post("/example_save_endpoint", data) //Make the AJAX call, and return a jqXHR to ensure the downstream queue waits for this jqXHR to resolve/reject.
.then(dfrd.resolve, dfrd.reject) //Resolve/reject the Deferred for the caller's benefit
.then(null, function() {
//Force failure down the success path to ensure the queue is not killed by an AJAX failure.
return $.when();//Return a resolved promsie, for the queue's benefit.
});
});
return dfrd.promise();//allow the caller to do something when the AJAX eventually responds
}
For explanation, see comments in code

Adding promise to DomOutline event handler

The asynchronous function I am trying to call is defined by onClick property of DomOutline. For this example, domClickHandler is like callback that receives the selected element.
var iWantThis;
var myDomOutline = DomOutline({
onClick: domClickHandler,
filter: false,
stopOnClick: true,
hideLabel: true
});
var domClickHandler = function(element){
iWantThis = element
console.log("I am the element you've selected: " + element);
};
myDomOutline.start();
console.log("From the outside world: " + iWantThis);
return iWantThis;
DomOutline.start() returns right away and domClickHandler is asynchronous callback. So in this example, the last console.log will return undefined even before I select the element. I have tried trying $.when() on a function that wraps around DomOutline.start() to return promise(), but it didn't make the async call synchronous. What is the way to use promise here so that I could return the DOM object when it has been selected?
I promisified the callback, but I am not sure how this will help me to achieve synchronous return of the array iWantThis
var domClikcHandler= function(element){
var dfd = $.Deferred();
classifiedElements = __classifyChildrenElements(element);
dfd.resolve(classifiedElements);
dfd.then(function(e){
console.log(e);
iWanThis = e;
});
return dfd.promise()
};
Let's take this one step at a time.
What a promise is
A promise an abstraction over the notion of value - it represents a value + time. Operations you perform on it queue and will happen when the time comes.
A promise starts off as pending and then goes through resolution to become either:
fulfilled - meaning it is available for use and chaining.
rejected - meaning that the operation failed and you can attempt to recover from it
In libraries that are not jQuery and in native promises - the transition process as well as the execution of handlers is always asynchronous. This is in order to prevent race conditions in cases it might be asynchronous.
What a promise is not
A promise is not an event emitter - you cannot bind a promise to an event that happens more than once since it represents a single value. The dual of promises that represents multiple values is called an Observable. You cannot bind a promise to a click event.
A promise is not magic - it is usually implemented in JavaScript, it cannot convert asynchronous code to synchronous code, while its then abstracts the notion of sequencing itself - it cannot without language support 'stop' code on asynchronous operation in order for that other language facilities are required like generators (ES6) or async/await (ES7). For most browser users who don't want to go through a compile step promises can make your life easier but not make asynchronous code synchronous.
In general promises use return values and thrown exceptions for well... return values and thrown exceptions. A promise resolves once with a single value just like a value has just one value and once you have obtained it it does not change.
How to use promises
In you case the click handler fundamentally can't be modeled with promises - it requires an event emitter aka observable. What you have with the onBtnClick looks just fine.
In the case of the ready event - assuming you promisified it correctly (calling $.when doesn't help on its own) you need to chain to it:
myDomOutline.start().then(function(){
alert("Ready!");
});
If you want to access it from the outside that's just fine since promises compose:
function startWithData(){
var request = $.ajax(...);
return myDomOutline.start().then(function(){ // note the return
startAnimations();
doAnotherThing();
return request; // return the request
});
}
startWithData().then(function(data){
// ajax done and ready event fired
// access data here
});
As additional reading please see How to return the response from an AJAX call? which is not promise centric but still very relevant.

Creating jQuery Deferred objects using setTimeout

I am having a hard time understanding the value of creating your own Deferred object.
Say you have the following jQuery
function doStuffFirst() {
var dfd = $.Deferred();
// do some stuff here
setTimeout(function() {
dfd.resolve();
},1);
return dfd.promise();
}
function doStuffAfter() {
//do some other stuff
}
$.when(doStuffFirst()).done(doStuffAfter);
I don't actually know that doStuffFirst() has finished, its just waiting some time before firing doStuffAfter()
why is that any better than just going
function doStuffFirst() {
// do some stuff here
}
setTimeout(function() {
//do some other stuff
},1);
You do know that it is finished; but it is useless as it stands, since you're not executing your task asynchronously. The task executes, then the deferred gets created and fired almost immediately. However, if you change to this:
function doStuffFirst() {
var dfd = $.Deferred();
setTimeout(function() {
// do some stuff HERE
dfd.resolve();
},1);
return dfd.promise();
}
then it becomes useful, since it will return immediately, but resolve some time later (whenever the task is done). While you have just one async task, using a deferred is not much different than using a plain callback (just more complex, and prettier, and the dependency goes the other way: callbacks go in, while promises come out of the routine that schedules the task). However, if you have more complex requirements, like having two async tasks which you want to execute simultaneously but wait until both are done, promises are much superior.
Using setTimeout simply executes the code after the given time, whereas promises executes code once the promised task has completed. Promises are used when dealing with things in which completion time is unknown, such as ajax requests. In setTimeout, you know when exactly to execute some code.

AngularJs: Have method return synchronously when it calls $http or $resource internally

Is there a way to wait on a promise so that you can get the actual result from it and return that instead of returning the promise itself? I'm thinking of something similar to how the C# await keyword works with Tasks.
Here is an example of why I'd like to have a method like canAccess() that returns true or false instead of a promise so that it can be used in an if statement. The method canAccess() would make an AJAX call using $http or $resource and then somehow wait for the promise to get resolved.
The would look something like this:
$scope.canAccess = function(page) {
var resource = $resource('/api/access/:page');
var result = resource.get({page: page});
// how to await this and not return the promise but the real value
return result.canAccess;
}
Is there anyway to do this?
In general that's a bad idea. Let me tell you why. JavaScript in a browser is basically a single threaded beast. Come to think of it, it's single threaded in Node.js too. So anything you do to not "return" at the point you start waiting for the remote request to succeed or fail will likely involve some sort of looping to delay execution of the code after the request. Something like this:
var semaphore = false;
var superImportantInfo = null;
// Make a remote request.
$http.get('some wonderful URL for a service').then(function (results) {
superImportantInfo = results;
semaphore = true;
});
while (!semaphore) {
// We're just waiting.
}
// Code we're trying to avoid running until we know the results of the URL call.
console.log('The thing I want for lunch is... " + superImportantInfo);
But if you try that in a browser and the call takes a long time, the browser will think your JavaScript code is stuck in a loop and pop up a message in the user's face giving the user the chance to stop your code. JavaScript therefore structures it like so:
// Make a remote request.
$http.get('some wonderful URL for a service').then(function (results) {
// Code we're trying to avoid running until we know the results of the URL call.
console.log('The thing I want for lunch is... " + results);
});
// Continue on with other code which does not need the super important info or
// simply end our JavaScript altogether. The code inside the callback will be
// executed later.
The idea being that the code in the callback will be triggered by an event whenever the service call returns. Because event driven is how JavaScript likes it. Timers in JavaScript are events, user actions are events, HTTP/HTTPS calls to send and receive data generate events too. And you're expected to structure your code to respond to those events when they come.
Can you not structure your code such that it thinks canAccess is false until such time as the remote service call returns and it maybe finds out that it really is true after all? I do that all the time in AngularJS code where I don't know what the ultimate set of permissions I should show to the user is because I haven't received them yet or I haven't received all of the data to display in the page at first. I have defaults which show until the real data comes back and then the page adjusts to its new form based on the new data. The two way binding of AngularJS makes that really quite easy.
Use a .get() callback function to ensure you get a resolved resource.
Helpful links:
Official docs
How to add call back for $resource methods in AngularJS
You can't - there aren't any features in angular, Q (promises) or javascript (at this point in time) that let do that.
You will when ES7 happens (with await).
You can if you use another framework or a transpiler (as suggested in the article linked - Traceur transpiler or Spawn).
You can if you roll your own implementation!
My approach was create a function with OLD javascript objects as follows:
var globalRequestSync = function (pUrl, pVerbo, pCallBack) {
httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState == 4 && httpRequest.status == 200) {
pCallBack(httpRequest.responseText);
}
}
httpRequest.open(pVerbo, pUrl, false);
httpRequest.send(null);
};
I recently had this problem and made a utility called 'syncPromises'. This basically works by sending what I called an "instruction list", which would be array of functions to be called in order. You'll need to call the first then() to kick things of, dynamically attach a new .then() when the response comes back with the next item in the instruction list so you'll need to keep track of the index.
// instructionList is array.
function syncPromises (instructionList) {
var i = 0,
defer = $q.defer();
function next(i) {
// Each function in the instructionList needs to return a promise
instructionList[i].then(function () {
var test = instructionList[i++];
if(test) {
next(i);
}
});
}
next(i);
return defer.promise;
}
This I found gave us the most flexibility.
You can automatically push operations etc to build an instruction list and you're also able to append as many .then() responses handlers in the callee function. You can also chain multiple syncPromises functions that will all happen in order.

Categories