Consuming Multiple .NET Web Services from Javascript in Parallel - javascript

I have two ASMX web services consumed from javascript. The first service performs a long operation and updates a database table on its progress. The second service polls that same database table in order to report the progress to the end user via a progress bar.
My problem is that the long process seems to be blocking the polling service. When I log the activity of the javascript, it seems to be requesting the long service correctly, and then starts to request the polling service once a second asynchronously (note: the long process is asynch as well). Both request types either use setInterval or setTimeout which shouldn't halt the browser. Yet when I look at the activity of the javascript, none of the responses from the polling requests return until the long process completes. So it seems the long process is blocking the polling requests until it's done.
Here's the nitty gritty:
JavaScript:
var percentComplete = 0;
setTimeout(function ()
{
MyWebService.CreateBulkOrder(serverVariable, function (result, eventArgs)
{
percentComplete = 100;
completeOperation(result);
});
}, 0);
var intID = setInterval(function ()
{
if (percentComplete < 100)
{
MyWebService.GetStatus(serverVariable, callback);
}
else
{
clearInterval(intID);
}
}, 1000);
Service Code (VB.NET - Note: code is changed to make it generic)
<System.Web.Script.Services.ScriptService()>
<System.Web.Services.WebService(Namespace:="http://mydns.com/webservices")>
<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)>
<ToolboxItem(False)>
Public Class MyWebServices
Inherits System.Web.Services.WebService
<WebMethod(EnableSession:=True)>
Public Function GetStatus(serverVariable As Integer) As Object
Dim currentPage As Integer = 0
Dim totalPages As Integer = Math.Ceiling(CType(If(Session("Number of Records"), Double) / CType(ConstantsCommon.TOTAL_PER_PAGE, Double))
Using clientDB As ClientDataContext = FunctionsOrderMgmt.ClientConnectionReadOnly
Dim repeatPageQuery = From repeatPage In clientDB.RepeatPages
Where repeatPage.KEY = serverVariable
Select repeatPage
Dim repeatPageData = repeatPageQuery.SingleOrDefault()
If repeatPageData Is Nothing Then
currentPage = 0
Else
currentPage = If(repeatPageData.REPEAT_PAGE, 0)
End If
Return New With {.TotalPages = totalPages, .CurrentPage = currentPage}
End Using
End Function
<WebMethod(EnableSession:=True)>
Public Function CreateBulkOrder(serverVariable As Integer) As Boolean
If Not TestsPass Then
Return False
End If
Try
'Do stuff that takes a long time
Catch ex As Exception
Return False
End Try
Return True
End Function
End Class

Add 'OneWay = true' to the CreateBulkOrder Webmethod otherwise it will be waiting for a response.
http://msdn.microsoft.com/en-us/library/system.web.services.protocols.soapdocumentmethodattribute.oneway(v=vs.71).aspx

Related

JavaScript function blocks web socket & causes sync issue & delay

I have a web socket that receives data from a web socket server every 100 to 200ms, ( I have tried both with a shared web worker as well as all in the main.js file),
When new JSON data arrives my main.js runs filter_json_run_all(json_data) which updates Tabulator.js & Dygraph.js Tables & Graphs with some custom color coding based on if values are increasing or decreasing
1) web socket json data ( every 100ms or less) -> 2) run function filter_json_run_all(json_data) (takes 150 to 200ms) -> 3) repeat 1 & 2 forever
Quickly the timestamp of the incoming json data gets delayed versus the actual time (json_time 15:30:12 vs actual time: 15:31:30) since the filter_json_run_all is causing a backlog in operations.
So it causes users on different PC's to have websocket sync issues, based on when they opened or refreshed the website.
This is only caused by the long filter_json_run_all() function, otherwise if all I did was console.log(json_data) they would be perfectly in sync.
Please I would be very very grateful if anyone has any ideas how I can prevent this sort of blocking / backlog of incoming JSON websocket data caused by a slow running javascript
function :)
I tried using a shared web worker which works but it doesn't get around the delay in main.js blocked by filter_json_run_all(), I dont thing I can put filter_json_run_all() since all the graph & table objects are defined in main & also I have callbacks for when I click on a table to update a value manually (Bi directional web socket)
If you have any ideas or tips at all I will be very grateful :)
worker.js:
const connectedPorts = [];
// Create socket instance.
var socket = new WebSocket(
'ws://'
+ 'ip:port'
+ '/ws/'
);
// Send initial package on open.
socket.addEventListener('open', () => {
const package = JSON.stringify({
"time": 123456,
"channel": "futures.tickers",
"event": "subscribe",
"payload": ["BTC_USD", "ETH_USD"]
});
socket.send(package);
});
// Send data from socket to all open tabs.
socket.addEventListener('message', ({ data }) => {
const package = JSON.parse(data);
connectedPorts.forEach(port => port.postMessage(package));
});
/**
* When a new thread is connected to the shared worker,
* start listening for messages from the new thread.
*/
self.addEventListener('connect', ({ ports }) => {
const port = ports[0];
// Add this new port to the list of connected ports.
connectedPorts.push(port);
/**
* Receive data from main thread and determine which
* actions it should take based on the received data.
*/
port.addEventListener('message', ({ data }) => {
const { action, value } = data;
// Send message to socket.
if (action === 'send') {
socket.send(JSON.stringify(value));
// Remove port from connected ports list.
} else if (action === 'unload') {
const index = connectedPorts.indexOf(port);
connectedPorts.splice(index, 1);
}
});
Main.js This is only part of filter_json_run_all which continues on for about 6 or 7 Tabulator & Dygraph objects. I wante to give an idea of some of the operations called with SetTimeout() etc
function filter_json_run_all(json_str){
const startTime = performance.now();
const data_in_array = json_str //JSON.parse(json_str.data);
// if ('DATETIME' in data_in_array){
// var milliseconds = (new Date()).getTime() - Date.parse(data_in_array['DATETIME']);
// console.log("milliseconds: " + milliseconds);
// }
if (summary in data_in_array){
if("DATETIME" in data_in_array){
var time_str = data_in_array["DATETIME"];
element_time.innerHTML = time_str;
}
// summary Data
const summary_array = data_in_array[summary];
var old_sum_arr_krw = [];
var old_sum_arr_irn = [];
var old_sum_arr_ntn = [];
var old_sum_arr_ccn = [];
var old_sum_arr_ihn = [];
var old_sum_arr_ppn = [];
var filtered_array_krw_summary = filterByProperty_summary(summary_array, "KWN")
old_sum_arr_krw.unshift(Table_summary_krw.getData());
Table_summary_krw.replaceData(filtered_array_krw_summary);
//Colour table
color_table(filtered_array_krw_summary, old_sum_arr_krw, Table_summary_krw);
var filtered_array_irn_summary = filterByProperty_summary(summary_array, "IRN")
old_sum_arr_irn.unshift(Table_summary_inr.getData());
Table_summary_inr.replaceData(filtered_array_irn_summary);
//Colour table
color_table(filtered_array_irn_summary, old_sum_arr_irn, Table_summary_inr);
var filtered_array_ntn_summary = filterByProperty_summary(summary_array, "NTN")
old_sum_arr_ntn.unshift(Table_summary_twd.getData());
Table_summary_twd.replaceData(filtered_array_ntn_summary);
//Colour table
color_table(filtered_array_ntn_summary, old_sum_arr_ntn, Table_summary_twd);
// remove formatting on fwds curves
setTimeout(() => {g_fwd_curve_krw.updateOptions({
'file': dataFwdKRW,
'labels': ['Time', 'Bid', 'Ask'],
strokeWidth: 1,
}); }, 200);
setTimeout(() => {g_fwd_curve_inr.updateOptions({
'file': dataFwdINR,
'labels': ['Time', 'Bid', 'Ask'],
strokeWidth: 1,
}); }, 200);
// remove_colors //([askTable_krw, askTable_inr, askTable_twd, askTable_cny, askTable_idr, askTable_php])
setTimeout(() => { askTable_krw.getRows().forEach(function (item, index) {
row = item.getCells();
row.forEach(function (value_tmp){value_tmp.getElement().style.backgroundColor = '';}
)}); }, 200);
setTimeout(() => { askTable_inr.getRows().forEach(function (item, index) {
row = item.getCells();
row.forEach(function (value_tmp){value_tmp.getElement().style.backgroundColor = '';}
)}); }, 200);
color_table Function
function color_table(new_arr, old_array, table_obj){
// If length is not equal
if(new_arr.length!=old_array[0].length)
console.log("Diff length");
else
{
// Comparing each element of array
for(var i=0;i<new_arr.length;i++)
//iterate old dict dict
for (const [key, value] of Object.entries(old_array[0][i])) {
if(value == new_arr[i][key])
{}
else{
// console.log("Different element");
if(key!="TENOR")
// console.log(table_obj)
table_obj.getRows()[i].getCell(key).getElement().style.backgroundColor = 'yellow';
if(key!="TIME")
if(value < new_arr[i][key])
//green going up
//text_to_speech(new_arr[i]['CCY'] + ' ' +new_arr[i]['TENOR']+ ' getting bid')
table_obj.getRows()[i].getCell(key).getElement().style.backgroundColor = 'Chartreuse';
if(key!="TIME")
if(value > new_arr[i][key])
//red going down
table_obj.getRows()[i].getCell(key).getElement().style.backgroundColor = 'Crimson';
}
}
}
}
Potential fudge / solution, thanks Aaron :):
function limiter(fn, wait){
let isCalled = false,
calls = [];
let caller = function(){
if (calls.length && !isCalled){
isCalled = true;
if (calls.length >2){
calls.splice(0,calls.length-1)
//remove zero' upto n-1 function calls from array/ queue
}
calls.shift().call();
setTimeout(function(){
isCalled = false;
caller();
}, wait);
}
};
return function(){
calls.push(fn.bind(this, ...arguments));
// let args = Array.prototype.slice.call(arguments);
// calls.push(fn.bind.apply(fn, [this].concat(args)));
caller();
};
}
This is then defined as a constant for a web worker to call:
const filter_json_run_allLimited = limiter(data => { filter_json_run_all(data); }, 300); // 300ms for examples
Web worker calls the limited function when new web socket data arrives:
// Event to listen for incoming data from the worker and update the DOM.
webSocketWorker.port.addEventListener('message', ({ data }) => {
// Limited function
filter_json_run_allLimited(data);
});
Please if anyone knows how websites like tradingview or real time high performance data streaming sites allow for low latency visualisation updates, please may you comment, reply below :)
I'm reticent to take a stab at answering this for real without knowing what's going on in color_table. My hunch, based on the behavior you're describing is that filter_json_run_all is being forced to wait on a congested DOM manipulation/render pipeline as HTML is being updated to achieve the color-coding for your updated table elements.
I see you're already taking some measures to prevent some of these DOM manipulations from blocking this function's execution (via setTimeout). If color_table isn't already employing a similar strategy, that'd be the first thing I'd focus on refactoring to unclog things here.
It might also be worth throwing these DOM updates for processed events into a simple queue, so that if slow browser behavior creates a rendering backlog, the function actually responsible for invoking pending DOM manipulations can elect to skip outdated render operations to keep the UI acceptably snappy.
Edit: a basic queueing system might involve the following components:
The queue, itself (this can be a simple array, it just needs to be accessible to both of the components below).
A queue appender, which runs during filter_json_run_all, simply adding objects to the end of the queue representing each DOM manipulation job you plan to complete using color_table or one of your setTimeout` callbacks. These objects should contain the operation to performed (i.e: the function definition, uninvoked), and the parameters for that operation (i.e: the arguments you're passing into each function).
A queue runner, which runs on its own interval, and invokes pending DOM manipulation tasks from the front of the queue, removing them as it goes. Since this operation has access to all of the objects in the queue, it can also take steps to optimize/combine similar operations to minimize the amount of repainting it's asking the browser to do before subsequent code can be executed. For example, if you've got several color_table operations that coloring the same cell multiple times, you can simply perform this operation once with the data from the last color_table item in the queue involving that cell. Additionally, you can further optimize your interaction with the DOM by invoking the aggregated DOM manipulation operations, themselves, inside a requestAnimationFrame callback, which will ensure that scheduled reflows/repaints happen only when the browser is ready, and is preferable from a performance perspective to DOM manipulation queueing via setTimeout/setInterval.

Firebase cloud function returns 304 error and then restarts

I've been stuck on this for months. I have removed some minor details from the function but nothing major. I have this https cloud function that ends a session and then uses endTime and startTime to calculate bill which is then returned to the client.
startTime is retrived from the realtime firebase database (which the session starter function put there).
My code snippet:
exports.endSession = functions.https.onRequest(async (req, res) => {
console.log("endSession() called.")
if(req.method == 'GET'){
bid = req.query.bid
session_cost = req.query.sessioncost
}else{
bid = req.body.bid
session_cost = req.body.sessioncost
}
start_time_ref = admin.database().ref("/online_sessions/").child(bid).child("start_time")
start_time_snapshot = await start_time_ref.once('value')
console.log("start_time_snapshot: "+start_time_snapshot.val())
start_time_snapshot = moment(start_time_snapshot.val(), 'dddd MMMM Do YYYY HH:mm:ss Z');
endDateTime = getDateTime()
console.log("startTime: " + start_time_snapshot.toString())
console.log("endTime: " + endDateTime.toString())
hour_difference = getHourDifference(start_time_snapshot, endDateTime)
bill = ride_cost * Math.ceil(hour_difference)
console.log("bill: "+bill)
var s_phone
sSessionlinks_ref = admin.database().ref('/sSessionlinks/')
sSessionlinks_snapshot = await sSessionlinks_ref.once('value')
sSessionlinks_snapshot.forEach((sid)=>{
if(sid.val() == bid){
s_phone = sid.key
}
})
s_fcm_token_ref = admin.database().ref("/s/").child(s_phone).child("FCM")
s_fcm_token_snapshot = await s_fcm_token_ref.once('value')
try{ // telling another client that session has ended.
await admin.messaging().send({
data: {
type: "sessionCompleted",
bill: bill.toString()
},
token: s_fcm_token_snapshot.val()
})
}catch(error){
}
//deleting this session from online sessions
online_session_ref = admin.database().ref('/online_sessions/').child(bid)
await online_session_ref.remove()
//puting this session as available
available_session_ref = admin.database().ref('/available_sessions/')
json = {}
json[bid] = s_phone
await available_session_ref.update(json) // session made available
res.status(200).send(bill.toString())
// here it *sometimes* returns 304 and then restarts but since i've already removed online_session_ref I cannot get startTime again because its removed with online_sessions so it fails.
// return
})
When its first called. It does all the calculations correctly but responds with a 304. So (I think the client) resends the request and the function is called again but since session is destroyed so it cannot calculate startTime.
Why is it that when its first called, even though all the calculations happen correctly it returns a 304 and not a 200? This problem doesn't happen all the time. It usually happens when this function is called after a long time but I'm not certain with that. I don't know what causes this.
Helper functions I've used:
function getHourDifference(s, e){
return moment.duration(e.diff(s)).asHours()
}
function getDateTime(){
d = moment.utc().utcOffset('+0530')
return d
}
When function end first time the text payload is Function execution took 794 ms, finished with status code 304
When it runs the second time (where it cannot get startTime cause its been removed in first run. There shouldn't be a second run in the first place.), the payload text is Function execution took 234 ms, finished with status code 200 (its 200 but return NaN becuase it cannot do calculation without startTime.
EDIT:
As some of you asked me to tell how the the function is being called:
Its being called from and android app using Volley. The parameters are assured to not be null. The code segment to call that function is:
// Setting the button's listeners.
endSessionButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
progressDialog = new SweetAlertDialog(getContext(), SweetAlertDialog.PROGRESS_TYPE);
progressDialog.getProgressHelper().setBarColor(Color.parseColor("#A5DC86"));
progressDialog.setTitleText("Ending session...");
AlertDialog endDialog = new AlertDialog.Builder(getContext()).create();
endDialog.setTitle("End Session?");
Log.e("sessioncost", String.valueOf(session_cost));
endDialog.setButton(Dialog.BUTTON_POSITIVE, "Yes", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
progressDialog.show();
// Instantiate the RequestQueue.
RequestQueue queue = Volley.newRequestQueue(getContext());
String url = "https://us-central1-something-something.cloudfunctions.net/endSession?bid=" + bid + "&sessioncost=" + session_cost;
Log.e("end sesion button", url);
// Request a string response from the provided URL.
StringRequest endSessionRequest = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
#Override
public void onResponse(final String response) {
progressDialog.dismiss();
Toast.makeText(getContext(), "Session Completed", Toast.LENGTH_LONG).show();
progressDialog = new SweetAlertDialog(getContext(), SweetAlertDialog.SUCCESS_TYPE);
progressDialog.getProgressHelper().setBarColor(Color.parseColor("#A5DC86"));
progressDialog.setTitleText("Session Completed: Bill");
progressDialog.setContentText("Please pay ?" + response + " to s.");
progressDialog.setCancelable(false);
progressDialog.show();
changeState('1');
bill_in_paise = Float.parseFloat(response) * 100;
Log.e("bill", bill_in_paise.toString());
progressDialog.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
#Override
public void onClick(SweetAlertDialog sweetAlertDialog) {
sweetAlertDialog.dismiss();
Intent intent = new Intent(getContext(), PaymentActivity.class);
intent.putExtra("amt", bill_in_paise.toString());
startActivityForResult(intent, REQUEST_CODE);
}
});
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Toast.makeText(getContext(), error.toString(), Toast.LENGTH_LONG).show();
}// onErrorResnponse - END
});
// Add the request to the RequestQueue. Cuz volley is asyc af.
queue.add(endSessionRequest);
// VOLLEY REQUEST - END
}
});
endDialog.setButton(Dialog.BUTTON_NEGATIVE, "No", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(getContext(), "Session not cancelled. " + which, Toast.LENGTH_LONG).show();
}
});
endDialog.show();
}
}
}); // endSessionButton onclick - end
UPDATE: #tuledev helped fix the 304 with a work around but the problem is still here. Even when the status code is 200 the cloud function is somehow called again and I get a NaN bill. At this point I don't know what's causing this.
The 304 status comes because the response is the same as the previous. Firebase cloud responses 304 and the client will get the cached data.
For preventing 304 status, we can return the value + uid of the bill, or something make the response diff from the previous.
As OP and I discussed, the 304 is solved but the problem's still here. So I think the problem comes from the client side.
Hope somebody can HELP him.
Edit: OP here, I changed the client code to using okhttp instead of volley and after testing for 2 days everything seems fine. Tuledev's answer did fix 304 but even after 200 the problem persisted. Just use okhttp instead of volley.

SQLDependency firing multiple times when more than 1 connection made

I'm in the process of developing an appointment schedule for our company in ASP.NET (MVC Razor). I'm using SQLDependency so the pages can update via the service broker. We currently have 15 service centres, and each of them may have 1 to 4 computers where they can take calls and insert/modify/delete appointments. Our customers also have the option to take an online appointment with our online appointment tool, so this is why we need it to be dynamic and constantly refresh so we don't book 2 customers at the same time.
So far, everything is working fine. The page constantly refreshes when needed and we were two days away of launching our new tool. Except, when making tests in a production environment (we're switching our current tool to a new server, so we're testing it before going live with it), we realized that when 2 connections are made to the website, the SQLDependency act crazy. It will double the notifications everytime (once the first time, twice at the second notification, 4, 8, 16, 32, etc... so it escalates quickly) and so, it just becomes useless as it refreshes the partial view too many times. I was wondering if I was missing something. Here's some snippet of code :
First, the javascript call :
$(function () {
// Declare a proxy to reference the hub.
var notifications = $.connection.rendezVousHub;
// Create a function that the hub can call to broadcast messages.
notifications.client.updateAgenda = function () {
getAllRdv()
};
// Start the connection.
$.connection.hub.start().done(function () {
getAllRdv();
}).fail(function (e) {
alert(e);
});
});
The C# function that have the SQLDependency call and command
public IEnumerable<RendezVousModels> GetAllRdv(DateTime date, int whsCode)
{
_date = date;
_whsCode = whsCode;
string query = #"[dbo].[InfosAgenda]";
var rdv = new List<RendezVousModels>();
using (var connection = new SqlConnection(_connString))
{
connection.Open();
using (var command = new SqlCommand(query, connection))
{
command.Notification = null;
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#Date", date.ToShortDateString());
command.Parameters.AddWithValue("#WhsCode", whsCode);
dependancy = new SqlDependency(command);
dependancy.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
var reader = command.ExecuteReader();
while (reader.Read())
{
rdv.Add(item: new RendezVousModels
{
// set infos into Model
});
}
}
}
return rdv;
}
The onChange event function :
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
SqlDependency dependency = sender as SqlDependency;
dependancy.OnChange -= dependency_OnChange;
if (e.Type == SqlNotificationType.Change)
RendezVousHub.sendAgenda();
GetAllRdv(_date, _whsCode);
}
I'm running out of ideas here. Maybe they all need different connections? If so, how should I proceed?
Thanks

Using javascript timers with signalR events to check for new data

I have this table, with a set of rows, each using a unique connection to signalR. This allows me to update several rows at the same time with unique content.
The way it works is that a service bus provides the messagehub with new values and a uniqe id to go with that value, every time a remote unit transmits a new message.
At this point i'd like to run a check every 10 seconds to see if the webserver still gets a message from the unit, which transmits this as long as it is alive. In other words, if there's more than 10 seconds since the last time SignalR gave me a value, this would indicate that the connection to the remote unit is lost. (Not to be mistaken with SignalR losing its connection)
As I have a lot of units (rows) in my table, I was wondering if a javascript timer for each row would be sufficient for this check, or is there a better way of doing this? If so, do I do this in my connector script or in my html?
A single timer firing every 10 seconds and scanning all your signalr connections should work fine.
Ok, so I figured this out in another way, letting my messagehandler take care of the task of distributing messages at the correct time:
public class AsxActivityAliveEventMessageHandler : IHandleMessages<AsxActivityAliveEvent>
{
private const double INTERVAL = 10000;
public static bool AsxConnected { get; set; }
private static Dictionary<String, TagTimer> _connectionTimers = new Dictionary<string, TagTimer>();
public void Handle(AsxActivityAliveEvent message)
{
AsxConnected = true;
NotifyClients(message);
TagTimer timer;
if (_connectionTimers.ContainsKey(message.ConveyanceId))
{
timer = _connectionTimers[message.ConveyanceId];
if (timer != null)
{
timer.Stop();
timer.Elapsed -= timer_Elapsed;
_connectionTimers.Remove(message.ConveyanceId);
}
}
timer = new TagTimer
{
Interval = INTERVAL,
Tag = message
};
timer.Elapsed += timer_Elapsed;
_connectionTimers.Add(message.ConveyanceId, timer);
timer.Start();
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
var timer = sender as TagTimer;
if (timer != null)
{
timer.Stop();
timer.Elapsed -= timer_Elapsed;
}
AsxConnected = false;
if (timer != null)
{
NotifyClients(timer.Tag as AsxActivityAliveEvent);
}
}
static void NotifyClients(AsxActivityAliveEvent message)
{
var messageHub = GlobalHost.ConnectionManager.GetHubContext<MessageHub>();
var conveyanceId = message.ConveyanceId;
// Removed some vars and notify's as they're not relevant to this example
messageHub.Clients.Group(message.ConveyanceId).notifyAlive(AsxConnected, conveyanceId);
}
}
internal class TagTimer : Timer
{
public object Tag { get; set; }
}
}

Concept - Designing a collapsible queue for asynchronous resources

I've noticed that the size of a file requested will effect how long the response takes for ajax calls. So if I fire 3 ajax GET requests for files of varying size, they may arrive in any order. What I want to do is guarantee the ordering when I append the files to the DOM.
How can I set up a queue system so that when I fire A1->A2->A3. I can guarantee that they are appeneded as A1->A2->A3 in that order.
For example, suppose A2 arrives before A1. I would want the action to wait upon the arrival and loading of A1.
One idea is to create a status checker using a timed callback as such
// pseudo-code
function check(ready, fund) {
// check ready some how
if (ready) {
func();
} else {
setTimeout(function () {
check(ready, fund);
}, 1); // check every msec
}
}
but this seems like a resource heavy way, as I fire the same function every 1msec, until the resources is loaded.
Is this the right path to complete this problem?
status checker using a 1msec-timed callback - but this seems like a resource heavy way; Is this the right path to complete this problem?
No. You should have a look at Promises. That way, you can easily formulate it like this:
var a1 = getPromiseForAjaxResult(ressource1url);
var a2 = getPromiseForAjaxResult(ressource2url);
var a3 = getPromiseForAjaxResult(ressource3url);
a1.then(function(res) {
append(res);
return a2;
}).then(function(res) {
append(res);
return a3;
}).then(append);
For example, jQuery's .ajax function implements this.
You can try something like this:
var resourceData = {};
var resourcesLoaded = 0;
function loadResource(resource, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
var state = this.readyState;
var responseCode = request.status;
if(state == this.DONE && responseCode == 200) {
callback(resource, this.responseText);
}
};
xhr.open("get", resource, true);
xhr.send();
}
//Assuming that resources is an array of path names
function loadResources(resources) {
for(var i = 0; i < resources.length; i++) {
loadResource(resources[i], function(resource, responseText) {
//Store the data of the resource in to the resourceData map,
//using the resource name as the key. Then increment the
//resource counter.
resourceData[resource] = responseText;
resourcesLoaded++;
//If the number of resources that we have loaded is equal
//to the total number of resources, it means that we have
//all our resources.
if(resourcesLoaded === resources.length) {
//Manipulate the data in the order that you desire.
//Everything you need is inside resourceData, keyed
//by the resource url.
...
...
}
});
}
}
If certain components must be loaded and executed before (like certain JS files) others, you can queue up your AJAX requests like so:
function loadResource(resource, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
var state = this.readyState;
var responseCode = request.status;
if(state == this.DONE && responseCode == 200) {
//Do whatever you need to do with this.responseText
...
...
callback();
}
};
xhr.open("get", resource, true);
xhr.send();
}
function run() {
var resources = [
"path/to/some/resource.html",
"path/to/some/other/resource.html",
...
"http://example.org/path/to/remote/resource.html"
];
//Function that sequentially loads the resources, so that the next resource
//will not be loaded until first one has finished loading. I accomplish
//this by calling the function itself in the callback to the loadResource
//function. This function is not truly recursive since the callback
//invocation (even though it is the function itself) is an independent call
//and therefore will not be part of the original callstack.
function load(i) {
if (i < resources.length) {
loadResource(resources[i], function () {
load(++i);
});
}
}
load(0);
}
This way, the next file will not be loaded until the previous one has finished loading.
If you cannot use any third-party libraries, you can use my solution. However, your life will probably be much easier if you do what Bergi suggested and use Promises.
There's no need to call check() every millisecond, just run it in the xhr's onreadystatechange. If you provide a bit more of your code, I can explain further.
I would have a queue of functions to execute and each of them checks the previous result has completed before executing.
var remoteResults[]
function requestRemoteResouse(index, fetchFunction) {
// the argument fetchFunction is a function that fetches the remote content
// once the content is ready it call the passed in function with the result.
fetchFunction(
function(result) {
// add the remote result to the list of results
remoteResults[index] = result
// write as many results as ready.
writeResultsWhenReady(index);
});
}
function writeResults(index) {
var i;
// Execute all functions at least once
for(i = 0; i < remoteResults.length; i++) {
if(!remoteResults[i]) {
return;
}
// Call the function that is the ith result
// This will modify the dom.
remoteResults[i]();
// Blank the result to ensure we don't double execute
// Store a function so we can do a simple boolean check.
remoteResults[i] = function(){};
}
}
requestRemoteResouse(0, [Function to fetch the first resouse]);
requestRemoteResouse(1, [Function to fetch the second resouse]);
requestRemoteResouse(2, [Function to fetch the thrid resouse]);
Please note that this is currently O(n^2) for simplicity, it would get faster but more complex if you stored an object at every index of remoteResults, which had a hasRendered property. Then you would only scan back until you found a result that had not yet occurred or one that has been rendered.

Categories