Hello.
Sometimes on the client side, you need to perform background tasks. The main requirement is that they do not interrupt the work of the entire web application, but quietly in the background communicated with each other, ended and added. The goal of the proposed scheduler is to remove from the developer a headache about such tasks and reduce to a common interface, with which you can gradually expand the range of tasks to be solved.
You can ask, what are the tasks and what for? There are actually a lot of tasks on the client: this is a periodic change of the session identifier (for example, an email client updating the identifier in the background), and automatic scrolling (for example, scrolling the card), moving an object in the graphical interface, and processing multiple requests from the client to the server (for example, information is requested from the server via XHR, many such requests, hence many XHR objects, and this is already a heavy load on the browser), this is a sample of data from the iframe in the case of JSRS based on it. The basis of the proposed scheduler is the setInterval and clearInterval functions. It is around them that everything will spin. But simply the functions themselves are not enough, it is necessary to abstract the very concept of the task and propose an interface. And make a scheduler who will manage tasks.
To implement a class of tasks, we need to introduce the concept of flags, in which we will store the task states.
')
HClass.Define ( "HFlag" , {extend: HCore.Object, static : {
MAX_FLAG_VALUE: 65535
}, props: {
dwFlag: null ,
construct: function (dwFlag) { this .Add (dwFlag); return this ; },
Set: function (dwFlag) { this .dwFlag = dwFlag; },
Get: function () { return this .dwFlag; },
Add: function (dwFlag) { this .dwFlag | = dwFlag; },
Zero: function (dwFlag) { this .dwFlag & = (HFlag.MAX_FLAG_VALUE - dwFlag); },
Check: function (dwFlag) { return !! ( this .dwFlag & dwFlag); },
Swap: function (dwNew, dwOld) { this .Zero (dwOld); this .Add (dwNew); },
Clear: function () { this .Set (0); }
}}); * This source code was highlighted with Source Code Highlighter .
In this class, a standard set of methods for working with flags.
Next comes excerpts from the HSheduler class, namely the methods of adding a task and deleting RunTask — launching the task via setInterval, RemoveTask — deleting via clearInterval:
RunTask: function (pTask) {
pTask.nTaskId = this .nTotalTasks;
this .aTaskHeap [ this .nTotalTasks ++] = pTask;
pTask.nTimerId = setInterval ( "HSheduler.aTaskHeap [" + pTask.nTaskId + "] .Cycle ()" , pTask.nCycleTimeout);
},
RemoveTask: function (pTask) {
clearInterval (pTask.nTimerId);
delete this .aTaskHeap [pTask.nTaskId];
} * This source code was highlighted with Source Code Highlighter .
The main thing here is working with the setInterval and clearInterval functions.
The RunTask function has the string
setInterval ("HSheduler.aTaskHeap [" + pTask.nTaskId + "] .Cycle ()", pTask.nCycleTimeout); where we establish that the task will be called (more precisely the Cycle method, the current task) with its identifier from the heap of tasks, with the interval specified in the task. These are the basic scheduler methods. Let us turn to the task and see what the task should do in general:
1. It must have some kind of input data.
2. Tasks have solution algorithms.
3. Tasks produce a result (it is also possible to realize the possibility of obtaining intermediate results).
Additionally, you must add task states. The task can be:
1. Initialize (preliminary calculation of derived parameters, create objects if needed) - state
SF_Ready .
2. To execute (calculation of the result according to the specified input and derived parameters) - the state
SF_Process .
3. End (by the condition of obtaining or the impossibility of obtaining a result) - the state
SF_Remove .
4. Suspend -
SF_Wait (you can use if intermediate results are not needed and there is an event from the outside that will reset this state, for example, waiting for a response from the server).
5. Skip one cycle -
SF_SkipCycle (analog SF_Wait, but the state is reset automatically).
Using this set of states, you can implement many tasks, and which you can not, then no one bothers to add additional new states. Below are excerpts from the HBaseTask class. The main method from this class is Cycle, it is called via setInterval:
Cycle: function () {
if (! this .pStaticAddress) return ;
if ( this .oStateFlags.Check (HBaseTask.SF_Remove)) {
this .Remove ();
return ;
}
if (! this .oStateFlags.Check (HBaseTask.SF_Wait)) {
if (! this .oStateFlags.Check (HBaseTask.SF_SkipCycle)) {
this .pStaticAddress.apply ( this , this .oTaskParams);
this .nCyclesCount ++;
} else this .oStateFlags.Zero (HBaseTask.SF_SkipCycle);
}
this .oStateFlags.Zero (HBaseTask.SF_SetParams);
} * This source code was highlighted with Source Code Highlighter .
The variable this.pStaticAddress contains the algorithm of the problem, a trite function, it is called every new cycle. The most important thing here is that the algorithm must be executed in the context of the task object so that from it there is access to the methods of the HBaseTask class:
this.pStaticAddress.apply (this, this.oTaskParams);Now, from the task algorithm, you can access the task states (write, read, and check states). In addition, the parameters of the task are passed through apply to the algorithm. Parameters are set via the Params method. Below is the full implementation of the HSheduler scheduler and the HBaseTask base task class:
//
// Sheduler.
//
HClass.Define ( "HSheduler" , { static : {
nTotalTasks: 0,
aTaskHeap: [],
RunTask: function (pTask) {
pTask.nTaskId = this .nTotalTasks;
this .aTaskHeap [ this .nTotalTasks ++] = pTask;
pTask.nTimerId = setInterval ( "HSheduler.aTaskHeap [" + pTask.nTaskId + "] .Cycle ()" , pTask.nCycleTimeout);
},
RemoveTask: function (pTask) {
clearInterval (pTask.nTimerId);
delete this .aTaskHeap [pTask.nTaskId];
},
//
// Base Task.
//
HBaseTask: HClass.Define ( "HBaseTask" , {extend: HProcess, static : {
TF_Nothing: 0,
TF_FireRun: 1,
SF_Null: 0,
SF_Ready: 1,
SF_Process: 2,
SF_SetParams: 4,
SF_SkipCycle: 8,
SF_Remove: 16,
SF_Wait: 32
}, props: {
oTaskFlags: null ,
oStateFlags: null ,
oTaskParams: null ,
nTaskId: null ,
nTimerId: null ,
nCycleTimeout: 1000,
nCyclesCount: 0,
nCyclesLimit: 0,
construct: function (fCode) {
this .oTaskFlags = new HFlag (HBaseTask.TF_Nothing);
this .oStateFlags = new HFlag (HBaseTask.SF_Ready);
this .nCycleTimeout = 1000; // ms.
this .nCyclesCount = 0;
this .nCyclesLimit = 0;
if (fCode) this .Create (fCode);
return this ;
},
Run: function () {HSheduler.RunTask ( this ); },
Remove: function () {HSheduler.RemoveTask ( this ); },
Params: function () {
this .oTaskParams = arguments;
this .oStateFlags.Add (HBaseTask.SF_SetParams);
},
CycleTimeout: function (nCycleTimeout) { this .nCycleTimeout = nCycleTimeout; },
Cycle: function () {
if (! this .pStaticAddress) return ;
if ( this .oStateFlags.Check (HBaseTask.SF_Remove)) {
this .Remove ();
return ;
}
if (! this .oStateFlags.Check (HBaseTask.SF_Wait)) {
if (! this .oStateFlags.Check (HBaseTask.SF_SkipCycle)) {
this .pStaticAddress.apply ( this , this .oTaskParams);
this .nCyclesCount ++;
} else this .oStateFlags.Zero (HBaseTask.SF_SkipCycle);
}
this .oStateFlags.Zero (HBaseTask.SF_SetParams);
},
AddState: function (dwFlag) { this .oStateFlags.Add (dwFlag); },
SwapState: function (dwNew, dwOld) { this .oStateFlags.Swap (dwNew, dwOld); },
GetState: function () { return this .oStateFlags; }
}})
}}); * This source code was highlighted with Source Code Highlighter .
With this class, you can very easily create tasks running in the background and doing something. Here are some simple examples:
// Example of a simple task, it will work in a loop.
var pTask = HBaseTask (function () {alert ( "Hello world!" )}};
pTask.Run ();
// Example of a task receiving parameters.
var pTask = HBaseTask (function (a, b) {alert (a + "" + b);});
pTask.Params (32, 128);
pTask.Run ();
// Example of the task managing states.
var pTask = HBaseTask (function () {
var oState = this .GetState (); // Take the current state.
if (oState.Check (HBaseTask.SF_Ready)) {
// Here we prepare derived variables or create the necessary objects, for example XHR.
this .SwapState (HBaseTask.SF_Process, HBaseTask.SF_Ready);
} else if (oState.Check (HBaseTask.SF_Process)) {
// Here we consider something, and if we get the result, go to the end state
this .SwapState (HBaseTask.SF_Remove, HBaseTask.SF_Process);
}
});
pTask.Run (); * This source code was highlighted with Source Code Highlighter .
But the most important thing is that an abstract concept of a problem has appeared, and it is possible to make derivative, highly specialized tasks. Below is an example of implementing a linear interpolation calculation (can be used to move along the line of an object, for example, to scroll a map from point A to point B). The formula for calculating F (t) = t1 + t * (t2 - t1). For scrolling, it is necessary to obtain an intermediate result, so you will need to add a callback function. Here is the class itself, it is inherited from HBaseTask:
HClass.Define ( "HLerpTask" , {extend: HBaseTask, props: {
fStart: 0, fEnd: 0,
fStep: 0, fT: 0,
fRange: 0,
fCallback: null ,
EvalLerp: function () { return this .fStart + this .fT * this .fRange; },
construct: function (fStart, fEnd, fStep, fCallback) {
this .fStart = fStart;
this .fEnd = fEnd;
this .fStep = fStep;
this .fCallback = fCallback;
this .CycleTimeout (10);
this .Create (function () {
var oState = this .GetState ();
if (oState.Check (HBaseTask.SF_Ready)) {
this .fT = 0;
this .fRange = this .fEnd - this .fStart;
this .SwapState (HBaseTask.SF_Process, HBaseTask.SF_Ready);
} else if (oState.Check (HBaseTask.SF_Process)) {
this .fT + = this .fStep;
if ( this .fT> = 1.0) {
this .fT = 1.0;
this .SwapState (HBaseTask.SF_Remove, HBaseTask.SF_Process);
}
if ( this .fCallback) this .fCallback ( this .EvalLerp ());
}
});
}
}}); * This source code was highlighted with Source Code Highlighter .
Here is an example of use, two calculators are created and intermediate results are written to divs, while the application itself is available ie. user can interact with other functionality:
function GE (sId) { return document.getElementById (sId); }
var pTask = new HLerpTask (0, 100, 0.001, function (fRet) {GE ( "counter1" ) .innerHTML = fRet;});
var pTask2 = new HLerpTask (0, 1000, 0.0005, function (fRet) {GE ( "counter2" ) .innerHTML = fRet;});
pTask.Run ();
pTask2.Run (); * This source code was highlighted with Source Code Highlighter .
You can also do other types of tasks, implementing unique logic necessary for the work of a specialized task in derived classes, but at the same time without departing from the interface of the basic task, which is very important when another developer starts working with your module, he is essentially aware of everything, only need to know what input and output parameters.
Nestoit neglect seemingly inconspicuous at first glance functions like setInterval and clearInterval, because on their basis you can make really interesting decisions.
PS The solutions proposed in this article and in the previous one concerning the dynamic loading of scripts are implemented in the project
www.okarta.ru , this is essentially an experimental project, all logic has been moved to the client, the server does not generate anything from the interface, there are only data requests from the client to the server (SOAP), by the way, the scheduler is great for requests. To download maps and blogs, use the application builder.
PPS If it doesn’t work somewhere, then this is not a problem in the proposed implementations, my task is to suggest entities, they are not at the browser level, if where errors occur, it’s only where native functions are called to work with XML or document elements, styles, etc. All the same, it is necessary to understand that there are different levels of abstraction the lower the level (ie, closer to the native functions), the higher the probability of error due to the fact that nobody likes standards, and vice versa the higher the level of abstraction the lower the probability of errors associated with native functions excluded.