Task Implementation


If a task fails, it can be retried automatically or manually from the dashboard. So we recommend dividing your workflow into tasks that each have a unique outcome (typically a simple http call). For example, if you want to publish a message to Slack and send an email, use two separate tasks (to avoid publishing to Slack twice if the first attempt to email fails and the task is retried).

There is nothing you can not do with a task - in particular, connect any API using its sdk or its endpoints.

A task is described in node.js in a file required in the Agent boot file:

  • it must contain a handle function
  • it may contain a onErrorRetryDelay function
  • it may contain a options function

# Handle Function

The simplest form of a task is an object with a handle function, where the work is done:

module.exports.handle = async function (...input) {
    ...
};

The handle function must return a Promise. It is used to determine when the work is done, and possibly if there was an error in the case of a rejected promise. This can be easily achieved by prefixing the function with the async keyword.

# onErrorRetryDelay function

When a non-catched error is thrown, a task can be retried automatically after a delay specified through an onErrorRetryDelay function into your task.

The onErrorRetryDelay function receives the error as its first parameter and returns a positive number representing the delay in seconds to wait before the next try.

Access the execution context of the task using this.context property, and implement any retry strategy we need.

Here is an example of a task which will automatically be retried at most 3 times, increasing the delay time between each try:

module.exports.onErrorRetryDelay(error) {
  // The retry index starts at 1 and increases by one for every retry.
  // This can be used to to increase the time between each attempt.
  const n = this.context.retryIndex;
  if (n > 3) {
      return false;
  }

  return n * 60;
};

Here is an example of an exponential-backoff strategy - widely used when calling APIs:

module.exports.onErrorRetryDelay(error) {
  const n = this.context.retryIndex;
  const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

  return n <= 12 ? 5 * rand(0, 2 ** n) : false;
}

When a task has a configured automatic retry, it will still be displayed as an error on the dashboard and you will still have the option to manually retry it on the dashboard. This would allow you to quickly retry a task rather than wait for the automatic retry.

A task can be retried a max of 100 'tries' using automatic retry. When this limit is reached, the task will still be displayed in the list of errors on the dashboard and you will have the option to retry it manually. When a task is manually retried, the automatic retry count restarts.

# Options function

We can provide task processing options through the options function.

module.exports.options = function (...input) {
    return {
      // after 10 minutes this task will be considered a failure
      maxProcessingTime: 600
   }
};

Currently, the options must return an object with key:

  • maxProcessingTime: duration in seconds after which, the task is considered timed-out. Zenaton has no way to know if a timed-out task should be retried or not. So when a task times-out, you should receive(depending on your settings) an alert and can then make the decision to manually retry the task - or not.

Fine-Tuning MaxProcessingTime

It can be annoying to receive too many 'false positive' alerts. So consider increasing the maxProcessingTime value if you receive too many alerts.

# Calling an API from a task

In order to implement any API from a task: just include the SDK of the third party in your Zenaton project or your favorite http client such as axios. Example:

const axios = require("axios");
const quotesUrl = "https://raw.githubusercontent.com/BolajiAyodeji/inspireNuggets/master/src/quotes.json";

module.exports.handle = async function (...input) {
    return axios.get(quotesUrl);
};