From f9a04253c4838f20c413fe4ebcaab30912bcd5ef Mon Sep 17 00:00:00 2001 From: Florian Hotze Date: Sat, 9 Dec 2023 17:41:25 +0100 Subject: [PATCH 1/2] [actions] ScriptExecution: Support passing params to createTimer in setTimeout-style Signed-off-by: Florian Hotze --- README.md | 40 ++++++++++------------------------------ actions.js | 26 +++++++++++++++++--------- test/actions.spec.js | 28 ++++++++++++++-------------- types/actions.d.ts | 14 ++++++++++---- types/actions.d.ts.map | 2 +- 5 files changed, 52 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index c5b773377..7afec1560 100644 --- a/README.md +++ b/README.md @@ -295,34 +295,11 @@ setTimeout(() => { myVar = 'Hello mutation!'; // When the timer runs, it will log "Hello mutation!" instead of "Hello world!" ``` -If you need to pass some variables to the timer but avoid that they can get mutated, use a function generator or pass those variables as parameters to `setTimeout`/`setInterval`. +If you need to pass some variables to the timer but avoid that they can get mutated, pass those variables as parameters to `setTimeout`/`setInterval` or `createTimer`. -**Pass variables using a function generator** +**Pass variables as parameters to `setTimeout`/`setInterval` or `createTimer`** -This allows you to pass variables to the timer's callback function during timer creation. -The variables can NOT be mutated after the timer function generator was used to create the callback function. - -```javascript -// Function generator for the timer's callback function -function cbFuncGen (myVariable) { - return function () { - console.info(`Timer expired with variable value = "${myVariable}"`); - }; -} - -var myVar = 'Hello world!'; - -// Schedule a timer that expires in ten seconds -setTimeout(cbFuncGen(myVar), 1000); - -myVar = 'Hello mutation!'; // When the timer runs, it will log "Hello world!" -``` - -This also works for timers created with [`actions.ScriptExecution.createTimer`](#createtimer). - -**Pass variables as parameters to `setTimeout`/`setInterval`** - -Another and more user friendly-way to avoid having variables mutated is to pass those variables as parameters to `setTimeout`/`setInterval`. +Another and more user friendly-way to avoid having variables mutated is to pass those variables as parameters to `setTimeout`/`setInterval`: ```javascript var myVar = 'Hello world!'; @@ -335,6 +312,8 @@ setTimeout((myVariable) => { myVar = 'Hello mutation!'; // When the timer runs, it will log "Hello world!" ``` +This also works for timers created with [`actions.ScriptExecution.createTimer`](#createtimer). + ### Paths For [file based rules](#file-based-rules), scripts will be loaded from `automation/js` in the user configuration directory. @@ -681,16 +660,17 @@ When using `createTimer`, please read [Accessing Variables](#accessing-variables ##### `createTimer` ```javascript -actions.ScriptExecution.createTimer(time.ZonedDateTime instant, function callback); +actions.ScriptExecution.createTimer(time.ZonedDateTime zdt, function functionRef, any param1, /* ... */ paramN); -actions.ScriptExecution.createTimer(string identifier, time.ZonedDateTime instant, function callback); +actions.ScriptExecution.createTimer(string identifier, time.ZonedDateTime zdt, function functionRef, any param1, /* ... */ paramN); ``` `createTimer` accepts the following arguments: - `string` identifier (optional): Identifies the timer by a string, used e.g. for logging errors that occur during the callback execution. -- [`time.ZonedDateTime`](#timetozdt) instant: Point in time when the callback should be executed. -- `function` callback: Callback function to execute when the timer expires. +- [`time.ZonedDateTime`](#timetozdt) zdt: Point in time when the callback should be executed. +- `function` functionRef: Callback function to execute when the timer expires. +- `*` param1, ..., paramN: Additional arguments which are passed through to the function specified by `functionRef`. `createTimer` returns an openHAB Timer, that provides the following methods: diff --git a/actions.js b/actions.js index 96ed536fb..bf3dde894 100644 --- a/actions.js +++ b/actions.js @@ -223,26 +223,34 @@ class ScriptExecution { /** * Schedules a function for later execution. * - * @param {string} identifier an optional identifier - * @param {time.ZonedDateTime} instant the point in time when the code should be executed - * @param {function} closure the code block to execute + * @example + * actions.ScriptExecution.createTimer(time.toZDT().plusSeconds(10), (foo, bar) => { + * console.log(foo + bar); + * }, 'Hello', 'openHAB'); + * + * @param {string} identifier an optional identifier, e.g. user for logging + * @param {time.ZonedDateTime} zdt the point in time when the callback function should be executed + * @param {function} functionRef callback function to execute when the timer expires + * @param {...*} params additional arguments which are passed through to the function specified by `functionRef` * @returns {*} a native openHAB Timer */ - static createTimer (identifier, instant, closure) { + static createTimer (identifier, zdt, functionRef, ...params) { // Support method overloading as identifier is optional - if (typeof identifier === 'string' && closure != null) { + if (typeof identifier === 'string' && functionRef != null) { + const callbackFn = () => functionRef(...params); // Try to access the createTimer method of ThreadsafeTimers try { - return ThreadsafeTimers.createTimer(identifier, instant, closure); // eslint-disable-line no-undef + return ThreadsafeTimers.createTimer(identifier, zdt, callbackFn); // eslint-disable-line no-undef } catch { - return JavaScriptExecution.createTimer(identifier, instant, closure); + return JavaScriptExecution.createTimer(identifier, zdt, callbackFn); } } else { + const callbackFn = () => zdt(functionRef, ...params); // Try to access the createTimer method of ThreadsafeTimers try { - return ThreadsafeTimers.createTimer(identifier, instant); // eslint-disable-line no-undef + return ThreadsafeTimers.createTimer(identifier, callbackFn); // eslint-disable-line no-undef } catch { - return JavaScriptExecution.createTimer(identifier, instant); + return JavaScriptExecution.createTimer(identifier, callbackFn); } } } diff --git a/test/actions.spec.js b/test/actions.spec.js index 430b0fc58..55346815c 100644 --- a/test/actions.spec.js +++ b/test/actions.spec.js @@ -26,40 +26,40 @@ describe('actions.js', () => { it('delegates createTimer to ThreadsafeTimers.', () => { const identifier = 'timer-1'; - const instant = {}; - const closure = () => null; + const zdt = {}; + const functionRef = (foo) => foo; - ScriptExecution.createTimer(identifier, instant, closure); + ScriptExecution.createTimer(identifier, zdt, functionRef, 'prop1'); - expect(ThreadsafeTimers.createTimer).toHaveBeenCalledWith(identifier, instant, closure); + expect(ThreadsafeTimers.createTimer).toHaveBeenCalled(); expect(JavaScriptExecution.callScript).not.toHaveBeenCalled(); jest.clearAllMocks(); - ScriptExecution.createTimer(identifier, instant); + ScriptExecution.createTimer(zdt, functionRef, 'prop1'); - expect(ThreadsafeTimers.createTimer).toHaveBeenCalledWith(identifier, instant); + expect(ThreadsafeTimers.createTimer).toHaveBeenCalled(); expect(JavaScriptExecution.callScript).not.toHaveBeenCalled(); }); it('falls back to Java ScriptExecution, when ThreadsafeTimers throws error.', () => { const identifier = 'timer-1'; - const instant = {}; - const closure = () => null; + const zdt = {}; + const functionRef = (foo) => foo; ThreadsafeTimers.createTimer.mockImplementation(() => { throw new Error(); }); - ScriptExecution.createTimer(identifier, instant, closure); + ScriptExecution.createTimer(identifier, zdt, functionRef); - expect(ThreadsafeTimers.createTimer).toHaveBeenCalledWith(identifier, instant, closure); - expect(JavaScriptExecution.createTimer).toHaveBeenCalledWith(identifier, instant, closure); + expect(ThreadsafeTimers.createTimer).toHaveBeenCalled(); + expect(JavaScriptExecution.createTimer).toHaveBeenCalled(); jest.clearAllMocks(); - ScriptExecution.createTimer(identifier, instant); + ScriptExecution.createTimer(zdt, functionRef); - expect(ThreadsafeTimers.createTimer).toHaveBeenCalledWith(identifier, instant); - expect(JavaScriptExecution.createTimer).toHaveBeenCalledWith(identifier, instant); + expect(ThreadsafeTimers.createTimer).toHaveBeenCalled(); + expect(JavaScriptExecution.createTimer).toHaveBeenCalled(); }); }); diff --git a/types/actions.d.ts b/types/actions.d.ts index 8b7af70f3..1dac01d80 100644 --- a/types/actions.d.ts +++ b/types/actions.d.ts @@ -170,12 +170,18 @@ export class ScriptExecution { /** * Schedules a function for later execution. * - * @param {string} identifier an optional identifier - * @param {time.ZonedDateTime} instant the point in time when the code should be executed - * @param {function} closure the code block to execute + * @example + * actions.ScriptExecution.createTimer(time.toZDT().plusSeconds(10), (foo, bar) => { + * console.log(foo + bar); + * }, 'Hello', 'openHAB'); + * + * @param {string} identifier an optional identifier, e.g. user for logging + * @param {time.ZonedDateTime} zdt the point in time when the callback function should be executed + * @param {function} functionRef callback function to execute when the timer expires + * @param {...*} params additional arguments which are passed through to the function specified by `functionRef` * @returns {*} a native openHAB Timer */ - static createTimer(identifier: string, instant: time.ZonedDateTime, closure: Function): any; + static createTimer(identifier: string, zdt: time.ZonedDateTime, functionRef: Function, ...params: any[]): any; /** * Schedules a function (with argument) for later execution * diff --git a/types/actions.d.ts.map b/types/actions.d.ts.map index 20b481732..2e2652e43 100644 --- a/types/actions.d.ts.map +++ b/types/actions.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../actions.js"],"names":[],"mappings":"AA2CA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAuE;AAEvE;;;;;;;;;;;;;;;;;;GAkBG;AACH,2BAA6E;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,4BAA+E;AAE/E;;;;;;;;;;;GAWG;AACH,uBAAqE;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,uBAAqE;AAErE;;;;;;;;;;;;;GAaG;AACH,6BAAyE;AAEzE;;;;;;;;;;GAUG;AACH,uBAAqE;AAErE;;;;;;;;;;;;GAYG;AACH;IACE;;;;OAIG;IACH,8BAFW,MAAM,QAIhB;IAED;;;;;;;OAOG;IACH,+BALW,MAAM,WACN,KAAK,aAAa,0BAqB5B;IAED;;;;;;;;;OASG;IACH,2CANW,MAAM,WACN,KAAK,aAAa,qCAa5B;CACF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,4BAA+E;AAE/E;;;;;;;;;;;GAWG;AACH,gCAA+E;AAE/E;;;;;;;;;;;GAWG;AACH;IACE;;;;;;;OAOG;IACH,uBALW,MAAM,MACN,MAAM,SACN,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;;OAQG;IACH,0BANW,MAAM,MACN,MAAM,SACN,MAAM,GACJ,MAAM,CAUlB;CACF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAuE;AAEvE;;;;;;;;;;;;;;;GAeG;AACH,mCAAuB;;yBArXV,OAAO,eAAe,EAAE,aAAa;;AAmZ3C,sEAAyD;AAUhD,+EAA+D"} \ No newline at end of file +{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../actions.js"],"names":[],"mappings":"AA2CA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAuE;AAEvE;;;;;;;;;;;;;;;;;;GAkBG;AACH,2BAA6E;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,4BAA+E;AAE/E;;;;;;;;;;;GAWG;AACH,uBAAqE;AAErE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,uBAAqE;AAErE;;;;;;;;;;;;;GAaG;AACH,6BAAyE;AAEzE;;;;;;;;;;GAUG;AACH,uBAAqE;AAErE;;;;;;;;;;;;GAYG;AACH;IACE;;;;OAIG;IACH,8BAFW,MAAM,QAIhB;IAED;;;;;;;;;;;;;OAaG;IACH,+BANW,MAAM,OACN,KAAK,aAAa,gDAwB5B;IAED;;;;;;;;;OASG;IACH,2CANW,MAAM,WACN,KAAK,aAAa,qCAa5B;CACF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,4BAA+E;AAE/E;;;;;;;;;;;GAWG;AACH,gCAA+E;AAE/E;;;;;;;;;;;GAWG;AACH;IACE;;;;;;;OAOG;IACH,uBALW,MAAM,MACN,MAAM,SACN,MAAM,GACJ,MAAM,CAIlB;IAED;;;;;;;;OAQG;IACH,0BANW,MAAM,MACN,MAAM,SACN,MAAM,GACJ,MAAM,CAUlB;CACF;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAuE;AAEvE;;;;;;;;;;;;;;;GAeG;AACH,mCAAuB;;yBA7XV,OAAO,eAAe,EAAE,aAAa;;AA2Z3C,sEAAyD;AAUhD,+EAA+D"} \ No newline at end of file From 867353f74d7953a5ed2e642c0b1ed25c94a6920d Mon Sep 17 00:00:00 2001 From: Florian Hotze Date: Sat, 9 Dec 2023 18:04:57 +0100 Subject: [PATCH 2/2] README fixes Signed-off-by: Florian Hotze --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 7afec1560..1b3f28ddc 100644 --- a/README.md +++ b/README.md @@ -295,11 +295,8 @@ setTimeout(() => { myVar = 'Hello mutation!'; // When the timer runs, it will log "Hello mutation!" instead of "Hello world!" ``` -If you need to pass some variables to the timer but avoid that they can get mutated, pass those variables as parameters to `setTimeout`/`setInterval` or `createTimer`. +If you need to pass some variables to the timer but avoid that they can get mutated, pass those variables as parameters to `setTimeout`/`setInterval` or `createTimer`: -**Pass variables as parameters to `setTimeout`/`setInterval` or `createTimer`** - -Another and more user friendly-way to avoid having variables mutated is to pass those variables as parameters to `setTimeout`/`setInterval`: ```javascript var myVar = 'Hello world!';