---
updatedAt: 2025-06-02T21:38:51.000Z
agentTools:
  projectIndex: https://developers.evrythng.com/llms.txt
---

# Example Script Patterns

By itself, a Reactor script is a blank slate. It's up to the solution developer how best to use it. To get the most out of Reactor, start with a good understanding of the data being collected by Platform resources as part of the solution and how applying transformations or integrating with more inputs/output systems can bring additional insight. Combining items of Thng, action, or product data is one common way to do this.

The examples below detail some example Reactor script architectural patterns. Using one of these patterns as a template can give some benefits in areas such as maintainability, modularity, reusability, and so on.

***

## Single File

The simplest Reactor script pattern is a single file (called `main.js`) with a simple `package.json` file declaring `dependencies`. This is the default when creating a Reactor script through the EVRYTHNG Dashboard, as shown below. You can find the simple example script featured below in the [*Example Scripts*](https://developers.evrythng.com/reference/reactor#section-example-scripts) section of the Reactor API Reference.

![990](https://files.readme.io/07e740e-single-file_deps.png "single-file_deps.png")

The advantages of this pattern are that the code is easily accessible, visible, and can be shared among many account Operators. However, any implementation that's more than trivial complexity doesn't suit this limited view. The [multi-file bundle](https://developers.evrythng.com/docs/reactor-walkthrough-example-script-patterns#section-multi-file-bundle) pattern can help alleviate these problems.

> 📘 Note
>
> The `package.json` file must contain at least `evrythng-extended`, otherwise the script will not execute. If the script is originally created from the Dashboard, this is already included and must not be removed. You can confirm this by clicking ‘Show dependencies’ on the application page.

***

## Using Promise Chains

With the requirement for calling the global `done()` function to receive any logs (which can be vital to debugging), we recommend you implement the Reactor script using a chain of promises. This is also valuable in terms of catching and propagating errors and brings benefits to all Reactor script developers.

The best way to benefit from this is to finish the chain with a `.catch()` and `.then()` to finish the script in a consistent way. To handle both native `Error` and API errors, it's recommended to use the `catch()` example shown below:

```javascript main.js
const readThngs = () => app.thng().read();

function onActionCreated(event) {
  readThngs()
    .then(thngs => logger.info(`Read ${thngs.length} Thngs`))
    .catch(e => logger.error(e.message || e.errors[0]))
    .then(done);
}
```

The same can be achieved using `async`/`await` if using Node 10:

```javascript
const readThngs = () => app.thng().read();

const onActionCreated = async (event) => {
  try {
    const thngs = await readThngs();
    logger.info(`Read ${thngs.length} Thngs`);
  } catch (e) {
    logger.error(e.message || e.errors[0]);
  }
  
  done();
};
```

As seen above, the evrythng.js SDK makes this pattern very easy to follow, since virtually all of the resources and operation methods return promises. If an error occurs, it's safely logged and `done()` is called as required.

Besides updating a single resource, this pattern can also perform several resource updates at once, including any other asynchronous dependencies, and ensure they are all resolved before the end of the script is reported. For example, read some IDs from a Thng’s `customFields` and give them all the ‘owned’ tag:

```javascript main.js
const readOwnedThngs = async (thngId) => {
  const thng = await app.thng(thngId).read();

  // Create 'ids' filter from list of 'owned' Thng IDs
  const params = { ids: thng.customFields.owned.join(',') };
  return app.thng().read({ params });
};

const updateThngTags = (thngs) => {
  const update = { tags: ['Owned'] };
  
  // Update all Thngs using a list of promises
  const promises = thngs.map(thng => thng.update(update));
  return Promise.all(promises);
}

// @filter(onActionCreated) action.type=_setOwnedTag
const onActionCreated = async (event) => {
  try {
    const thngs = await readOwnedThngs(event.action.thng);
    await updateThngTags(thngs);
    logger.info('Thngs tags updated!');
  } catch (e) {
    logger.error(e.message || e.errors[0]);
  }
  
  done();
};
```

Another example is in the case where a resource update must wait for an external resource. For instance, updating a Thng description using the [quotes.rest API](http://quotes.rest/). The example below does this in a manner that uses a promise to incorporate the external asynchronous request as part of the single promise chain, and any errors it throws are caught and logged safely.

> 📘 Note
>
> Remember to add `request` to `package.json` to fulfill this dependency.

```javascript main.js
const request = require('request');

const QOTD_URL = 'http://quotes.rest/qod.json?category=inspire';

// Handle web request using a promise
const getQuote = () => new Promise((resolve, reject) => {
  // Fetch the quote
  request.get(QOTD_URL, (err, response, body) => {
    if(err) {
      reject(err);
      return;
    }

    const quotes = JSON.parse(body).contents.quotes;
    resolve(`"${quotes[0].quote}" -- ${quotes[0].author}`);
  });
});

const updateThng = (thngId, quote) => 
  app.thng(thngId).update({ description: quote });

// @filter(onActionCreated) action.type=_setQuote
const onActionCreated = async (event) => {
  try {
    const quote = await getQuote();
    await updateThng(event.action.thng, quote);
  } catch (e) {
    logger.error(e.message || e.errors[0]);
  }
  
  done();
};
```
```json package.json
{
  "dependencies": {
    "evrythng-extended": "^4.1.0",
    "request": "^2.83.0"
  }
}
```

After creating an action on a Thng with action type `_setQuote`, the Thng’s `description` now contains a suitably inspiring quote:

```json Updated Thng with Quote
{
  "id": "UHSP3aayq3PYh6wRaDRccqTh",
  "createdAt": 1511952386181,
  "updatedAt": 1511952959407,
  "name": "QuoteThng",
  "description": "\"Life is 10% what happens to us and 90% how we react to it.\" -- Dennis P. Kimbro",
  "properties": {},
  "identifiers": {}
}
```

***

## Multi-file Bundle

If a single-file Reactor script becomes too large to easily maintain in a single file, it's recommended to split it into multiple files, and upload it as a `.zip` bundle instead. This upload can be done easily through the Dashboard or the API.

Besides moving specific modules of functionality into separate files (such as external integration code, or some static data collections), a useful pattern to use is to create file handlers for each action type of property key the script will encounter. This is especially useful if the use case makes use of many of these and allows easy use of promises to facilitate calling the global `done()` function once execution is finished.

An example bundle file structure is shown below:

```
bundle.zip/
  package.json
  main.js
  actions/
    registerUser.js
    updateUser.js
    removeUser.js
```

In this pattern, the main event handler `onActionCreated()` is located in `main.js` and uses the `type` of the action created to run the appropriate file handler. This allows for an easily expandable solution by simply adding more file handlers for new action types. For example:

```javascript main.js
const HANDLERS = {
  _registerUser: require('./actions/registerUser'),
  _updateUser: require('./actions/updateUser'),
  _unregisterUser: require('./actions/unregisterUser')
};

const handleAction = (action) => {
  const handler = HANDLERS[action.type];
  if (!handler) {
    throw new Error(`No handler for action type ${action.type}`);
  }

  return handler(action); 
};

const onActionCreated = async (event) => {
  try {
    await handleAction(event.action);
  } catch (e) {
    logger.error(e.message || e.errors[0]);
  }
  
  done();
};
```

and the separated handler files that handles all actions of that `type`:

```javascript registerUser.js
module.exports = (action) => {
  logger.info('New user!');
  
  // Update Application User tags
  const payload = { tags: ['Participating'] };
  return app.user(action.user).update(payload);
};
```
```javascript updateUser.js
module.exports = (action) => {
  logger.info('User updated!');
  
  // Update Application User custom fields
  const payload = { customFields: { appVersion: 2 } };
  return app.user(action.user).update(payload);
};
```
```javascript unregisterUser.js
module.exports = (action) => {
  logger.info('User unregistered!');
  
  // Update Application User tags
  const payload = { tags: [] };
  return app.user(action.user).update(payload);
};
```

***

## Rules in the Thng

A more advanced but user-configurable pattern is to specify some simple (or complex) rules on each individual Thng itself. This is useful if each Thng has its own custom behavior, often a result of their owner’s preferences. An example of this could be a custom schedule for a smart device to be automatically turned on or off, which is specified for each unique device.

These preferences can be stored in Thng  `customFields` and read by a generalized Reactor script that simply processes these values and performs updates to the Thng accordingly. This means that each Thng can have its own behavior when triggering the Reactor script and can be modified by users, without requiring further updates to the script itself.

### Example Script - Compute Rules

A simple and reusable example rule processing script is shown below, and includes a few different arithmetic operators. The rules themselves are read from Thng `customFields.rules` array and must include the following fields:

* `inputs`  - a pair of existing Thng `properties` keys that are the operands of the operator
* `operator` - a key from the `OPERATORS` available in the script (see below) that's the arithmetic operator on the `inputs`.
* `output` - the property key where the result is stored.

For example, to set a Thng property `temp_c_is_safe`, the `lessThan` operator is specified, and will use the `temp_c` and `max_temp_c` as input values. So if the `temp_c` is less than `max_temp_c`, `temp_c_is_safe` will be set to true. This could represent a smart thermostat performing monitoring for the user.

```json Example Thng with Rules
{
  "name": "Thng with Rules",
  "description": "A Thng with rules in customFields",
  "customFields": {
    "rules": [
      {
        "inputs": [ "temp_c", "max_temp_c" ],
        "operator": "lessThan",
        "output": "temp_c_is_safe"
      }
    ]
  }
}
```

The generalized Reactor script itself is triggered by a Thng property update (such as an increase in `temp_c`) and filtered accordingly with Reactor filters. The `compute()` function is called, which processes the Thng’s `customFields` to extract any rules, and then performs the property updates specified by those rules.

The example `OPERATORS` shown can be extended further by the reader, as long as the input property values are Numbers. A further exercise could be to generalize the operators beyond just numbers.

```javascript Example Rule Script
// ------------------------------- Compute Rules -------------------------------

const OPERATORS = {
  add: items => items[0] + items[1],
  subtract: items => items[0] - items[1],
  multiply: items => items[0] * items[1],
  divide: items => items[0] / items[1],
  equals: items => items[0] === items[1],
  lessThan: items => items[0] < items[1]
};

const compute = async (thng) => {
  // Any rules?
  const { customFields } = thng;
  if (!(customFields && customFields.rules)) {
    return;
  }

  // Process all rules as promises
  const promises = customFields.rules.map((rule) => {
    // Map input property names to their actual values
    const inputs = rule.inputs.map(key => parseFloat(thng.properties[key]));
    
    // Perform the computation
    const result = OPERATORS[rule.operator](inputs);
    logger.info(`${result} << ${JSON.stringify(rule)}`);
    
    // Store the output property value
    return app.thng(thng.id).property(rule.output).update(result);
  });

  return Promise.all(promises);
};

// ------------------------------ Reactor Events -------------------------------

// @filter(onThngPropertiesChanged) propertyChangeNew.temp_c=*
const onThngPropertiesChanged = async (event) => {
  try {
    await compute(event.thng);
  } catch (e) {
    logger.error(e.message || e.errors[0]);
  }
  
  done();
};
```

***

## Conclusion

In this section you have learned about some example Reactor script patterns that promote maintainability, separation of concerns, and catching any errors that may be thrown. Some ideas here can be easily reused as starting points for future use-cases and integrations, as well as to build upon to improve your understanding.

In the next section, we walk through building a simple single-file Reactor script from scratch that forms the basis of a simple scan campaign involving accumulating insights and metadata, while using two different Reactor script event types.