Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[actions] ScriptExecution: Support passing params to createTimer in setTimeout-style #311

Merged
merged 2 commits into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 8 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,34 +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, 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** <!-- markdownlint-disable-line MD036 -->

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`** <!-- markdownlint-disable-line MD036 -->

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!';
Expand All @@ -335,6 +309,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.
Expand Down Expand Up @@ -681,16 +657,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:

Expand Down
26 changes: 17 additions & 9 deletions actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
Expand Down
28 changes: 14 additions & 14 deletions test/actions.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});

Expand Down
14 changes: 10 additions & 4 deletions types/actions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
2 changes: 1 addition & 1 deletion types/actions.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading