SynJS
SynJS allows to write and execute javascript code in synchronous manner, so you don't have to fight callback hell with promise hell.
What is it
SynJS is a parser and executor of Javascript code, that is written in Javascript. It parses Javascript code, extract individual Javascript operators, wraps them into functions, places them into internal tree-like structure, and then executes them synchronously.
With SynJS you can write code like this:
var res, i=0;
while(i<5) {
setTimeout(function() {
res = 'i='+i;
SynJS.resume(_synjsContext);
},1000);
SynJS.wait();
console.log(res, new Date());
i++;
}
It introduces following 2 statements:
- SynJS.wait(): this operator is used in main logic flow to pause execution of the flow
- SynJS.resume(_synjsContext): this function is used in callback in order to continue execution from the next statement after SynJS.wait()
When executed, the code above will show following in your console:
i=0 Wed Dec 21 2016 11:45:33 GMT-0700 (Mountain Standard Time)
i=1 Wed Dec 21 2016 11:45:34 GMT-0700 (Mountain Standard Time)
i=2 Wed Dec 21 2016 11:45:35 GMT-0700 (Mountain Standard Time)
i=3 Wed Dec 21 2016 11:45:36 GMT-0700 (Mountain Standard Time)
i=4 Wed Dec 21 2016 11:45:37 GMT-0700 (Mountain Standard Time)
Quick Start
In Node.JS:
npm install synjs
global.SynJS = global.SynJS || require('synjs');
In Browser:
<script src="SynJS.js"></script>
Put your synchronous code into function:
function myTestFunction1(paramA,paramB) {
var res, i = 0;
while (i < 5) {
setTimeout(function () {
res = 'i=' + i;
SynJS.resume(_synjsContext);
}, 1000);
SynJS.wait();
console.log(res, new Date());
i++;
}
return "myTestFunction1 finished";
}
Make sure you use SynJS.wait() for synchronizing your main flow with callback flow, and also make sure your callback calls SynJS.resume(_synjsContext) at the end.
Execute your function via SynJS:
SynJS.run(myTestFunction1,null, function (ret) {
console.log('done all:', ret);
});
The result will look like this:
i=0 Sun Dec 25 2016 12:25:41 GMT-0700 (Mountain Standard Time) i=1 Sun Dec 25 2016 12:25:42 GMT-0700 (Mountain Standard Time) i=2 Sun Dec 25 2016 12:25:43 GMT-0700 (Mountain Standard Time) i=3 Sun Dec 25 2016 12:25:44 GMT-0700 (Mountain Standard Time) i=4 Sun Dec 25 2016 12:25:45 GMT-0700 (Mountain Standard Time) done all: myTestFunction1 finished
SynJS Reference
SynJS.run(myTestFunction1,obj, param1, param2 [, param3 etc], callback) (function, to be called to execute function synchronously)
Parameters:
- myTestFunction1: pointer to a function that needs to be executed synchronously
- obj: some object that will be accessed via "this" in myTestFunction1 (could be null)
- param1, param2, etc - any number of parameters
- callback: some function to call once myTestFunction1 is finished.
Returns:
- execution context
SynJS.wait() (operator, to be called from main flow) Waits until SynJS.resume() is called, and then continues with the next statement.
SynJS.wait(numeric_expr) (operator, to be called from main flow)
- numeric_expr: number of milliseconds Waits numeric_expr milliseconds, and then continues with the next statement.
SynJS.wait(non_numeric_expr) (operator, to be called from main flow)
- non_numeric_expr: condition Waits until SynJS.resume() is called, checks if non_numeric_expr evaluates to true, then continues with the next statement. Otherwise continues waiting.
SynJS.resume(context) (function, to be called from callback)
- context: execution context, that needs to be resumed. Execution context can be accessed inside myTestFunction1 via _synjsContext variable.
SynJS supports following types of operators:
- var
- if ... else
- while
- do ... while
- for(;;)
- for(var ;;)
- for(.. in ..)
- for(var .. in ..)
- switch
- break [label]
- continue [label]
- return
Following operators are not yet supported:
- const
- let
- for ... of
- try ... catch
Bonus
SynJS.goto(expr) - evaluates expr into label name and jumps to that label. Label is searched within the scope of current code block and all parent blocks.
How it works
SynJS accepts function pointer as an input parameter, and performs following:
- It parses code of input function, and builds a few internal structures, such as:
- an internal tree structure of operators, that represents code of input function
- hash array with local variables names, that were defined in input function using var statement
- It "compiles" each operator by
- modifying source of operator by changing references to local variables
- creating internal function that contain modified statement code
- It creates execution context that contains local variables, execution stack with program counters, and other information, that is necessary in order to store the latest state, and to resume execution.
- It executes structure of operators (code) against execution context (data).
Step 1: Parsing
In the example above SynJS parses code and recognizes 3 statements:
Statement 1:
var res, i=0;
Statement 2:
while(i<5) {
...
}
Statement 3:
return "myTestFunction1 finished";
Statement 2 starts with while keyword, so SynJS parses condition and body of the cycle:
Condition:
i<5
Body contains few statements itself, so SynJS parses it recursively:
Body Statement 2.1:
setTimeout(function() {
res = 'i='+i;
SynJS.resume(_synjsContext);
},1000);
Body Statement 2.2:
SynJS.wait();
Body Statement 2.3:
console.log(res, new Date());
Body Statement 2.4:
i++;
Step 2. Compiling
Once statement cannot be decomposed any further, SynJS "compiles" it by doing 2 things:
-
modifying code of statement and replacing all references to local vars
-
wrapping statement code into internal function:
function(_synjsContext) { code_of_the_statement }
Hash of local variables is created by parsing all var statements. In our example it would contain 2 names:
- res
- i
This array is used in order to replace all references to local vars with reference to local var hash in _synjsContext. Statement 2.3 above is converted and compiled into following internal function:
function(_synjsContext) {
setTimeout(function() {
_synjsContext.localVars.res = 'i='+_synjsContext.localVars.i;
SynJS.resume(_synjsContext);
},1000);
}
Step 3. Executing
When SynJS.run() is first called, it checks if parameter function is already compiled. If necessary, tree structure that represents code of the function is built and added as _synjsBin property to a function object. Then the execution context is created, and placed into global hash of all running contexts. Once SynJS.run is done, corresponding execution context is deleted.