I wonder what is a good pattern to use when you could have multiple xmlhttprequests that are part of different processes like (check login, fetch tooltip and display, show sub records/open details).
Your input on my code so far is more than welcome so are some good articles for reference on asynchronyous handling of processes.
Here is what I got so far trying to use a mediator and trying to define a sequence of events to be triggered by the mediator and initiated by a worker for a certain process
var mediator={
events:[],
// bind functions to events, optionally once only if this function doesn't
// need to handle the event during the lifetime of the application
addListener:function(name,processor,onceOnly){
if(!mediator.events[name]){
mediator.events[name]=new Array({
processor:processor,
once: onceOnly ? true : false
});
return;
}
mediator.events[name].push({
processor:processor,
once: onceOnly ? true : false
});
},
trigger:function(name,data){
var i=0;//check if mediator[name] exist
for(i=0;i<mediator.events[name].length;i++){
try{
mediator.events[name][i].processor(data);
// problem is when a use once handler is the 3rd in the chain and the
// second handler fails then the 3rd is never removed
// could trigger an error here that has a cleaner listner
}finally{
if(mediator.events[name][i].once){
mediator.remove(name,mediator.events[name][i]);
}
}
}
},
// removing listener from event
remove:function(name,event){
for(var i=0;i<mediator.events[name].length;i++){
if(mediator.events[name][i]==event){
mediator.events[name].splice(i,1);
return;
}
}
},
// used to provide an event chain through data that will execute a certain
// process
triggerNext:function(data){
// some checks on data
mediator.trigger(data.events[data.index++],data);
}
}
// possible response parsers
var parser=function(type){
var parseLogin=function(data){
console.log(data);
// should call triggerNext here for the worker to be notified.
}
if(type=="loginParser"){
return parseLogin;
}
}
// connects and triggers next
var connector=function(){
this.response="";
this.commObject=null;
this.connect=function(data){
$.get(data.url, function(res) {
data.commObject=this;//maybe you'd like to inpect it
data.response=res;
mediator.triggerNext(data);
});//trigger fail event if failed
};
}
// example of initiating a process
$("document").ready(function(){
//add all listeners that are used during the entire execution
// of the application here
var p=parser("loginParser");
mediator.addListener("checkLogin",p);
//the following is a temporary listener, this code would be in
// a worker object initLogin function.
var c=new connector();
mediator.addListener("connect",c.connect,true);
// the data determines what process will be invoked
// this could be in a worker.initLogin function
var data={
processType:"SendLoginAndCheck",
url:"test.html",
post:"",//could check in the connector.connect to see if post is set
events:["connect","checkLogin"],
//there is no worker.afterLogin but the 3rd event could be finishprocess
//and a worker object's function can be called to process that
index:0
}
//start the process
mediator.triggerNext(data);
});
Related
I'm working on the "Approve All" button. The process here is when I click "Approve All," each individual "Approve" button will be triggered as "click" all at once, and then it will send POST requests to the controller. However, when I clicked Approve All button, there was a race condition causing the controller returns Error 500: Internal server error. I have tried using JS setTimeout() with value 1500*iter, but when the iterator gets higher, for example at i = 100, then it would take 1500*100 => 150000ms (150s). I hope that explains the problem clearly. Is there a way to prevent such a case?
Here is my code, I'm using JQuery:
let inspection = $this.parents("li").find("ul button.approve"); // this will get all 'approve' button to be clicked at once
inspection.each((i,e)=>{
(function () {
setTimeout(function () {
$(e).data("note",r);
$(e).click();
}, 1500 * i); // this acts like a queue, but when i > 100, it takes even longer to send POST requests.
})(this,i,e,r);
});
// then, each iteration will send a POST request to the controller.
$("#data-inspection ul button.approve").on("click", function() {
// send POST requests
});
Any help would be much appreciated. Thank you.
That 500 error may also be the server crashing from being unable to process all the requests simultaneously.
What I'd recommend is using an event-driven approach instead of setTimeout. Your 1500ms is basically a guess - you don't know whether clicks will happen too quickly, or if you'll leave users waiting unnecessarily.
I'll demonstrate without jQuery how to do it, and leave the jQuery implementation up to you:
// use a .js- class to target buttons your buttons directly,
// simplifying your selectors, and making them DOM agnostic
const buttonEls = document.querySelectorAll('.js-my-button');
const buttonsContainer = document.querySelector('.js-buttons-container');
const startRequestsEvent = new CustomEvent('customrequestsuccess');
// convert the DOMCollection to an array when passing it in
const handleRequestSuccess = dispatchNextClickFactory([...buttonEls]);
buttonsContainer.addEventListener('click', handleButtonClick);
buttonsContainer.addEventListener(
'customrequestsuccess',
handleRequestSuccess
);
// start the requests by dispatching the event buttonsContainer
// is listening for
buttonsContainer.dispatchEvent(startRequestsEvent);
// This function is a closure:
// - it accepts an argument
// - it returns a new function (the actual event listener)
// - the returned function has access to the variables defined
// in its outer scope
// Note that we don't care what elements are passed in - all we
// know is that we have a list of elements
function dispatchNextClickFactory(elements) {
let pendingElements = [...elements];
function dispatchNextClick() {
// get the first element that hasn't been clicked
const element = pendingElements.find(Boolean);
if (element) {
const clickEvent = new MouseEvent('click', {bubbles: true});
// dispatch a click on the element
element.dispatchEvent(clickEvent);
// remove the element from the pending elements
pendingElements = pendingElements.filter((_, i) => i > 0);
}
}
return dispatchNextClick;
}
// use event delegation to mitigate adding n number of listeners to
// n number of buttons - attach to a common parent
function handleButtonClick(event => {
const {target} = event
if (target.classList.contains('js-my-button')) {
fetch(myUrl)
.then(() => {
// dispatch event to DOM indicating request is complete when the
// request succeeds
const completeEvent = new CustomEvent('customrequestsuccess');
target.dispatchEvent(completeEvent);
})
}
})
There are a number of improvements that can be made here, but the main ideas here are that:
one should avoid magic numbers - we don't know how slowly or quickly requests are going to be processed
requests are asynchronous - we can determine explicitly when they succeed or fail
DOM events are powerful
when a DOM event is handled, we do something with the event
when some event happens that we want other things to know about, we can dispatch custom events. We can attach as many handlers to as many elements as we want for each event we dispatch - it's just an event, and any element may do anything with that event. e.g. we could make every element in the DOM flash if we wanted to by attaching a listener to every element for a specific event
Note: this code is untested
Does socket.io ignore\drop them?
The reason why Im asking this is the following.
There is a client with several states. Each state has its own set of socket handlers. At different moments server notifies the client of state change and after that sends several state dependent messages.
But! It takes some time for the client to change state and to set new handlers. In this case client can miss some msgs... because there are no handlers at the moment.
If I understand correctly unhandled msgs are lost for client.
May be I miss the concept or do smth wrong... How to hanle this issues?
Unhandled messages are just ignored. It's just like when an event occurs and there are no event listeners for that event. The socket receives the msg and doesn't find a handler for it so nothing happens with it.
You could avoid missing messages by always having the handlers installed and then deciding in the handlers (based on other state) whether to do anything with the message or not.
jfriend00's answer is a good one, and you are probably fine just leaving the handlers in place and using logic in the callback to ignore events as needed. If you really want to manage the unhandled packets though, read on...
You can get the list of callbacks from the socket internals, and use it to compare to the incoming message header. This client-side code will do just that.
// Save a copy of the onevent function
socket._onevent = socket.onevent;
// Replace the onevent function with a handler that captures all messages
socket.onevent = function (packet) {
// Compare the list of callbacks to the incoming event name
if( !Object.keys(socket._callbacks).map(x => x.substr(1)).includes(packet.data[0]) ) {
console.log(`WARNING: Unhandled Event: ${packet.data}`);
}
socket._onevent.apply(socket, Array.prototype.slice.call(arguments));
};
The object socket._callbacks contains the callbacks and the keys are the names. They have a $ prepended to them, so you can trim that off the entire list by mapping substring(1) onto it. That results in a nice clean list of event names.
IMPORTANT NOTE: Normally you should not attempt to externally modify any object member starting with an underscore. Also, expect that any data in it is unstable. The underscore indicates it is for internal use in that object, class or function. Though this object is not stable, it should be up to date enough for us to use it, and we aren't modifying it directly.
The event name is stored in the first entry under packet.data. Just check to see if it is in the list, and raise the alarm if it is not. Now when you send an event from the server the client does not know it will note it in the browser console.
Now you need to save the unhandled messages in a buffer, to play back once the handlers are available again. So to expand on our client-side code from before...
// Save a copy of the onevent function
socket._onevent = socket.onevent;
// Make buffer and configure buffer timings
socket._packetBuffer = [];
socket._packetBufferWaitTime = 1000; // in milliseconds
socket._packetBufferPopDelay = 50; // in milliseconds
function isPacketUnhandled(packet) {
return !Object.keys(socket._callbacks).map(x => x.substr(1)).includes(packet.data[0]);
}
// Define the function that will process the buffer
socket._packetBufferHandler = function(packet) {
if( isPacketUnhandled(packet) ) {
// packet can't be processed yet, restart wait cycle
socket._packetBuffer.push(packet);
console.log(`packet handling not completed, retrying`)
setTimeout(socket._packetBufferHandler, socket._packetBufferWaitTime, socket._packetBuffer.pop());
}
else {
// packet can be processed now, start going through buffer
socket._onevent.apply(socket, Array.prototype.slice.call(arguments));
if(socket._packetBuffer.length > 0) {
setTimeout(socket._packetBufferHandler,socket._packetBufferPopDelay(), socket._packetBuffer.pop());
}
else {
console.log(`all packets in buffer processed`)
socket._packetsWaiting = false;
}
}
}
// Replace the onevent function with a handler that captures all messages
socket.onevent = function (packet) {
// Compare the list of callbacks to the incoming event name
if( isPacketUnhandled(packet) ) {
console.log(`WARNING: Unhandled Event: ${packet.data}`);
socket._packetBuffer.push(packet);
if(!socket._packetsWaiting) {
socket._packetsWaiting = true;
setTimeout(socket._packetBufferHandler, socket._packetBufferWaitTime, socket._packetBuffer.pop());
}
}
socket._onevent.apply(socket, Array.prototype.slice.call(arguments));
};
Here the unhandled packets get pushed into the buffer and a timer is set running. Once the given amount of time has passed, if starts checking to see if the handlers for each item are ready. Each one is handled until all are exhausted or a handler is missing, which trigger another wait.
This can and will stack up unhandled calls until you blow out the client's allotted memory, so make sure that those handlers DO get loaded in a reasonable time span. And take care not to send it anything that will never get handled, because it will keep trying forever.
I tested it with really long strings and it was able to push them through, so what they are calling 'packet' is probably not a standard packet.
Tested with SocketIO version 2.2.0 on Chrome.
Given the code above:
binaryServer = BinaryServer({port: 9001});
binaryServer.on('connection', function(client) {
console.log("new connection");
client.on('stream', function(stream, meta) {
console.log('new stream');
stream.on('data', function('data'){
//actions
stream.on('end', function() {
//actions
});
});
});
I can say that client inherits the features of binaryServer. So if I make console.log(client.id) in the events of stream I can see, which client generate the given event. Now I want to know if every single event is exclusive of one client, in other words I want to know if data happens for every single client (that generates data) and no data event will be generated while the actions is happening.
You're registering a listener to the "connection" event which can happen within binaryServer. When a "connection" event happens, the registered listener will receive an argument, which you choose to call client. client in this case is an object, and doesn't inherit features of binaryServer.
"data" happens for every client, but will have unique results for each clientsince you register an event listener for every client.
If two events are triggered after each other, the callback function of the first event will be called, and after that the second events callback function will be called. See the following example code:
var event = new Event('build');
var i = 0;
// Listen for the event.
document.addEventListener('build', function (e) {
console.log(i++);
}, false);
// Dispatch the event.
document.dispatchEvent(event);
document.dispatchEvent(event);
JSFiddle (watch console)
Information about JavaScript inheritance
Information about JavaScript event loop
In a Angular.js and Socket.io App, I want to show a loading before sending an image via Socket.io. I write this code:
When a button clicked this code runs, first i want to run startLoading function and after that I want to send image:
$scope.startLoading(function(){
$scope.socket.emit('sendImg', {
data: $scope.newImg,
});
});
and this is my startLoading function:
$scope.startLoading = function (callback) {
//DO SOME STUFF LIKE ENABLING LOADING ANIMATION
$scope.animation =true; //binded to UI
$scope.errors = false; //binded to UI
callback(); //it seems this code runs before above lines
};
But it seems callback() line runs before first two lines and because of that, my loading appears after sending of image to the server! why? i change callback line to a timeout like this and it works fine but is this a good solution? i dont think! what i have to do for a standard code?
$scope.startLoading = function (callback) {
//DO SOME STUFF LIKE ENABLING LOADING ANIMATION
$scope.animation =true; //binded to UI
$scope.errors = false; //binded to UI
$timeout(function(){callback();}, 1000);
};
Actually, code runs sequentially but calling callback and sending image freezes page and because of that, my loading appears after freezing ends. but i need before freezing, loading starts
Your code really does run sequentially, but the first two lines don't change the UI immediately.
When you assign some value to a scope variable, it's just that, a variable assignment. It doesn't trigger any events. Angular will only update the UI later, when it evaluates the bindings and finds the change. So here is what happens:
$scope.startLoading = function (callback) {
// Presumably this is called from some event from Angular,
// so all this is run in an $apply, in an Angular "context".
// But this is still "plain" javascript, so the next two lines
// are just plain variable assignments.
$scope.animation =true;
$scope.errors = false;
callback(); // This function does its thing, then returns
// When this function returns, Angular will evaluate all of its
// bindings, will find that the above values have changed,
// and will update the DOM.
};
For details, see the "Integration with the browser event loop" section in the dev guide.
What you want is to ensure that the DOM is updated before your callback runs. I think there is nothing wrong with using $timeout for this.
There might be a better/nicer way, but I haven't found it yet...
So it would become something like this:
$scope.startLoading = function (callback) {
$scope.animation =true; // at this point, these are just
$scope.errors = false; // plain variable assignments
$timeout(callback); // schedule callback to run later
// After this returns, Angular will evaluate its bindings,
// and update the DOM, so if $scope.animation and $scope.errors
// are bound to something, they can trigger some visible change.
// callback will be called in the next $digest cycle, so _after_
// the DOM has been updated
};
(There is no need to specify a value for the timeout if you only want to run it in the next "tick". Also, there is no need to wrap callback, it can be directly passed to $timeout.)
Hope this helps!
I've got a sequence of Javascript function calls in a function I have defined to be executed when a web doc is ready. I expected them to be executed in sequence, as one ends the next begins, but the behaviour I see doesn't match up with that.
Additionally there is manipulation of the graphical components going on in between the calls (for example, I add in a checkpoint time to draw on a div on the page inbetween each of the mentioned calls) but those redraws aren't happening in sequence... they all happen at once.
I'm a bit of a n00b with the whole javascript-in-the-browser thing, is there an obvious mistake I'm making, or a good resource to go find out how to do this stuff?
Update - sample
// called onReady()
function init() {
doFirstThing();
updateDisplayForFirstThing();
doSecondThingWithAjaxCall();
updateDisplayForSecondThing();
...
reportAllLoaded();
}
IE won't update the display until the current script is finished running. If you want to redraw in the middle of a sequence of events, you'll have to break your script up using timeouts.
If you post some code we can help refactor it.
edit: here's a general pattern to follow.
function init() {
doFirstThing();
updateDisplayForFirstThing();
}
function updateDisplayForFirstThing() {
// existing code
...
// prepare next sequence
var nextFn = function() {
// does this method run async? if so you'll have to
// call updateDisplayForSecondThing as a callback method for the
// ajax call rather than calling it inline here.
doSecondThingWithAjaxCall();
updateDisplayForSecondThing();
}
setTimeout(nextFn, 0);
}
function updateDisplayForSecondThing() {
// existing code
...
// prepare next sequence
var nextFn = function() {
// continue the pattern
// or if you're done call the last method
reportAllLoaded();
}
setTimeout(nextFn, 0);
}
This can be fixed for many cases by using callbacks, especially with AJAX calls -- for example:
function doFirstThing(fn){
// doing stuff
if(typeof fn == 'function') fn();
}
function updateDisplayForFirstThing(){
// doing other stuff
}
function init(){
doFirstThing(updateDisplayForFirstThing);
}
Another option is to use return values:
function doFirstThing(fn){
// doing stuff
if(x) return true;
else return false;
}
function updateDisplayForFirstThing(){
// doing other stuff
return true;
}
function init(){
if(doFirstThing()){ updateDisplayForFirstThing(); }
}
setting timeouts to step through your code is not really a good way to fix this problem because you'd have to set your timeouts for the maximum length of time each piece of code could possibly take to execute.
However, you may still sometimes need to use a setTimeout to ensure the DOM has properly updated after certain actions.
If you end up deciding that you would like some JavaScript threading, check out the still being drafted Web Workers API. Browser support is hit and miss though the API is implemented in most modern web browsers.
Question: exactly how did you go about determining when the "doc is ready"? The DOMContentLoaded event isn't supported in IE I'm fairly certain... if you're in need of waiting for your document to load in its entirety you could use something like this:
var onReady = function(callback) {
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", callback, false);
return true;
} else if (document.attachEvent) {
var DOMContentLoaded = function() {
if (document.readyState === "complete") {
document.detachEvent("onreadystatechange", DOMContentLoaded);
onReady();
}
};
return true;
}
};
Then of course you'll need to develop a setTimeout testing for some flags state indicating the page is loaded upon completion before continuing the execution of the rest of your code... that or any number of other methods...
Or you could just include the script at the bottom of your body...
I'm just rambling though until you have some code to show us?