node package manager

synjs

Build Status # 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.