From c156e5d58c8a15f849b97ea01d87939cd57027e0 Mon Sep 17 00:00:00 2001 From: LaukikOnMac <22937426+LRagji@users.noreply.github.com> Date: Sun, 4 Oct 2020 18:41:18 +0530 Subject: [PATCH 1/2] Added Continuation Callbacks to avoid cpu lock ups --- docs/embedding-extending.md | 24 ++++++++++++ jsonata.d.ts | 1 + src/jsonata.js | 16 ++++++-- test.log | 61 +++++++++++++++++++++++++++++ test/async-continuation-function.js | 38 ++++++++++++++++++ 5 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 test.log create mode 100644 test/async-continuation-function.js diff --git a/docs/embedding-extending.md b/docs/embedding-extending.md index 159df3bf..b8c3363b 100644 --- a/docs/embedding-extending.md +++ b/docs/embedding-extending.md @@ -80,6 +80,30 @@ console.log("Started"); // Prints "Started", then "Finished with 19" ``` +If `continuationCallback()` is supplied, `expression.evaluate()` returns `undefined`, the expression is run asynchronously calling continuationCallback after every step which should resolve to true else it will cancel the execution and the `Error` in `callback` will reflect as operation cancelled. + +```javascript +let stepCounter = 0; +async function continueExecution() { + stepCounter++; + return stepCounter<1000; //PREVENT INFINITE LOOPS +} +function completion(err, results) { + if (err) { + console.error(err); + return; + } + console.log(`Completed in ${stepCounter} steps with result:${results}`); +} +const getBit = jsonata(`($x:= λ($c,$n,$b){ $c=$b?$n%2:$x($c+1,$floor($n/2),$b)};$x(0,number,bitIndex))`); + +getBit.evaluate({ "number": 10000000, "bitIndex": 0 }, undefined, completion, continueExecution); //NORMAL +// Prints Completed in 17 steps with result:0 + +getBit.evaluate({ "number": 10000000, "bitIndex": -1 }, undefined, completion, continueExecution); //INFINITE LOOP +// Prints { code: 'U2020', message: 'Operation cancelled.' } +``` + ### expression.assign(name, value) Permanently binds a value to a name in the expression, similar to how `bindings` worked above. Modifies `expression` in place and returns `undefined`. Useful in a JSONata expression factory. diff --git a/jsonata.d.ts b/jsonata.d.ts index f860a969..3d299ba2 100644 --- a/jsonata.d.ts +++ b/jsonata.d.ts @@ -35,6 +35,7 @@ declare namespace jsonata { interface Expression { evaluate(input: any, bindings?: Record): any; evaluate(input: any, bindings: Record | undefined, callback: (err: JsonataError, resp: any) => void): void; + evaluate(input: any, bindings: Record | undefined, callback: (err: JsonataError, resp: any) => void, continuationCallback: () => Promise): void; assign(name: string, value: any): void; registerFunction(name: string, implementation: (this: Focus, ...args: any[]) => any, signature?: string): void; ast(): ExprNode; diff --git a/src/jsonata.js b/src/jsonata.js index aa40ccf4..cae117cf 100644 --- a/src/jsonata.js +++ b/src/jsonata.js @@ -2006,7 +2006,8 @@ var jsonata = (function() { "D3138": "The $single() function expected exactly 1 matching result. Instead it matched more.", "D3139": "The $single() function expected exactly 1 matching result. Instead it matched 0.", "D3140": "Malformed URL passed to ${{{functionName}}}(): {{value}}", - "D3141": "{{{message}}}" + "D3141": "{{{message}}}", + "U2020": "Operation Cancelled" }; /** @@ -2062,7 +2063,7 @@ var jsonata = (function() { }, '<:n>')); return { - evaluate: function (input, bindings, callback) { + evaluate: function (input, bindings, callback, continuationCallback = function(){return Promise.resolve(true)}) { // throw if the expression compiled with syntax errors if(typeof errors !== 'undefined') { var err = { @@ -2110,7 +2111,16 @@ var jsonata = (function() { if (result.done) { callback(null, result.value); } else { - result.value.then(thenHandler).catch(catchHandler); + continuationCallback().then((proceed) => { + if (proceed === true) { + result.value.then(thenHandler).catch(catchHandler); + } + else { + var err = { code: 'U2020' }; + populateMessage(err); + callback(err, null); + } + }).catch(catchHandler); } }; it = evaluate(ast, input, exec_env); diff --git a/test.log b/test.log new file mode 100644 index 00000000..63f5a6d0 --- /dev/null +++ b/test.log @@ -0,0 +1,61 @@ + +> jsonata@1.8.3 pretest /Users/laukikragji/Documents/Git/Personal/jsonata +> npm run lint + + +> jsonata@1.8.3 lint /Users/laukikragji/Documents/Git/Personal/jsonata +> eslint src + + +> jsonata@1.8.3 test /Users/laukikragji/Documents/Git/Personal/jsonata +> npm run mocha + + +> jsonata@1.8.3 mocha /Users/laukikragji/Documents/Git/Personal/jsonata +> node ./node_modules/istanbul/lib/cli.js cover --report cobertura --report html ./node_modules/mocha/bin/_mocha -- "test/**/*.js" + + + + Invoke JSONata with continuation callback + Get bit value form valid bit index + ✓ should return valid result + Get bit value form invalid bit index + ✓ should return error rejected + +> jsonata@1.8.3 posttest /Users/laukikragji/Documents/Git/Personal/jsonata +> npm run check-coverage && npm run browserify && npm run minify && npm run build-es5 + + +> jsonata@1.8.3 check-coverage /Users/laukikragji/Documents/Git/Personal/jsonata +> istanbul check-coverage -statement 100 -branch 100 -function 100 -line 100 + + +> jsonata@1.8.3 browserify /Users/laukikragji/Documents/Git/Personal/jsonata +> browserify src/jsonata.js --outfile jsonata.js --standalone jsonata + + +> jsonata@1.8.3 minify /Users/laukikragji/Documents/Git/Personal/jsonata +> uglifyjs jsonata.js -o jsonata.min.js --compress --mangle + + +> jsonata@1.8.3 build-es5 /Users/laukikragji/Documents/Git/Personal/jsonata +> npm run mkdir-dist && npm run regenerator && npm run browserify-es5 && npm run minify-es5 + + +> jsonata@1.8.3 mkdir-dist /Users/laukikragji/Documents/Git/Personal/jsonata +> mkdirp ./dist + + +> jsonata@1.8.3 regenerator /Users/laukikragji/Documents/Git/Personal/jsonata +> babel src --out-dir dist --presets=@babel/env + +Successfully compiled 6 files with Babel (2228ms). + +> jsonata@1.8.3 browserify-es5 /Users/laukikragji/Documents/Git/Personal/jsonata +> regenerator --include-runtime polyfill.js > jsonata-es5.js; browserify dist/jsonata.js --standalone jsonata >> jsonata-es5.js + +built Module("polyfill") + +> jsonata@1.8.3 minify-es5 /Users/laukikragji/Documents/Git/Personal/jsonata +> uglifyjs jsonata-es5.js -o jsonata-es5.min.js --compress --mangle + diff --git a/test/async-continuation-function.js b/test/async-continuation-function.js new file mode 100644 index 00000000..fa558a25 --- /dev/null +++ b/test/async-continuation-function.js @@ -0,0 +1,38 @@ +"use strict"; + +var jsonata = require('../src/jsonata'); +var chai = require("chai"); +var expect = chai.expect; +var chaiAsPromised = require("chai-as-promised"); +chai.use(chaiAsPromised); + +var jsonataContinuationPromise = function (expr, data, continuationCallback) { + return new Promise(function (resolve, reject) { + expr.evaluate(data, undefined, function (error, response) { + if (error) reject(error); + resolve(response); + }, continuationCallback); + }); +}; + +describe('Invoke JSONata with continuation callback', function () { + describe('Get bit value form valid bit index', function () { + it('should return valid result', function () { + let stepCounter = 0; + const continuationCallback = () => { stepCounter++; return Promise.resolve(stepCounter < 100); }; + const getBit = jsonata(`($x:= λ($c,$n,$b){ $c=$b?$n%2:$x($c+1,$floor($n/2),$b)};$x(0,number,bitIndex))`); + const data = { "number": 10000000, "bitIndex": 0 }; + expect(jsonataContinuationPromise(getBit, data, continuationCallback)).to.eventually.deep.equal(0); + }); + }); + + describe('Get bit value form invalid bit index', function () { + it('should return error rejected', function () { + let stepCounter = 0; + const continuationCallback = () => { stepCounter++; return Promise.resolve(stepCounter < 100); }; + const getBit = jsonata(`($x:= λ($c,$n,$b){ $c=$b?$n%2:$x($c+1,$floor($n/2),$b)};$x(0,number,bitIndex))`); + const data = { "number": 10000000, "bitIndex": -1 }; + expect(jsonataContinuationPromise(getBit, data, continuationCallback)).to.eventually.be.rejected; + }); + }); +}); From 5360a4466cba2e8367fc6a7ff182f89b0e95a73a Mon Sep 17 00:00:00 2001 From: LaukikOnMac <22937426+LRagji@users.noreply.github.com> Date: Sun, 4 Oct 2020 18:43:51 +0530 Subject: [PATCH 2/2] removed test log --- test.log | 61 -------------------------------------------------------- 1 file changed, 61 deletions(-) delete mode 100644 test.log diff --git a/test.log b/test.log deleted file mode 100644 index 63f5a6d0..00000000 --- a/test.log +++ /dev/null @@ -1,61 +0,0 @@ - -> jsonata@1.8.3 pretest /Users/laukikragji/Documents/Git/Personal/jsonata -> npm run lint - - -> jsonata@1.8.3 lint /Users/laukikragji/Documents/Git/Personal/jsonata -> eslint src - - -> jsonata@1.8.3 test /Users/laukikragji/Documents/Git/Personal/jsonata -> npm run mocha - - -> jsonata@1.8.3 mocha /Users/laukikragji/Documents/Git/Personal/jsonata -> node ./node_modules/istanbul/lib/cli.js cover --report cobertura --report html ./node_modules/mocha/bin/_mocha -- "test/**/*.js" - - - - Invoke JSONata with continuation callback - Get bit value form valid bit index - ✓ should return valid result - Get bit value form invalid bit index - ✓ should return error rejected - -> jsonata@1.8.3 posttest /Users/laukikragji/Documents/Git/Personal/jsonata -> npm run check-coverage && npm run browserify && npm run minify && npm run build-es5 - - -> jsonata@1.8.3 check-coverage /Users/laukikragji/Documents/Git/Personal/jsonata -> istanbul check-coverage -statement 100 -branch 100 -function 100 -line 100 - - -> jsonata@1.8.3 browserify /Users/laukikragji/Documents/Git/Personal/jsonata -> browserify src/jsonata.js --outfile jsonata.js --standalone jsonata - - -> jsonata@1.8.3 minify /Users/laukikragji/Documents/Git/Personal/jsonata -> uglifyjs jsonata.js -o jsonata.min.js --compress --mangle - - -> jsonata@1.8.3 build-es5 /Users/laukikragji/Documents/Git/Personal/jsonata -> npm run mkdir-dist && npm run regenerator && npm run browserify-es5 && npm run minify-es5 - - -> jsonata@1.8.3 mkdir-dist /Users/laukikragji/Documents/Git/Personal/jsonata -> mkdirp ./dist - - -> jsonata@1.8.3 regenerator /Users/laukikragji/Documents/Git/Personal/jsonata -> babel src --out-dir dist --presets=@babel/env - -Successfully compiled 6 files with Babel (2228ms). - -> jsonata@1.8.3 browserify-es5 /Users/laukikragji/Documents/Git/Personal/jsonata -> regenerator --include-runtime polyfill.js > jsonata-es5.js; browserify dist/jsonata.js --standalone jsonata >> jsonata-es5.js - -built Module("polyfill") - -> jsonata@1.8.3 minify-es5 /Users/laukikragji/Documents/Git/Personal/jsonata -> uglifyjs jsonata-es5.js -o jsonata-es5.min.js --compress --mangle -