Node-RED:Руководство пользователя/Написание функций

Материал из Онлайн справочника
Перейти к навигацииПерейти к поиску

Перевод: Максим Кузьмин
Проверка/Оформление/Редактирование: Мякишев Е.А.


Написание функций[1]

Нода «Function» позволяет применять JavaScript-код к проходящим через нее сообщениям.

Эти сообщения приходят в ноду «Function» в виде объекта под названием «msg». Как правило, у него есть свойство «msg.payload», содержащее тело сообщения.

Другие ноды могут добавить в сообщение собственные свойства (более подробно об этом читайте в документации к этим нодам).

Написание функции

Код, вставленный в ноду «Function» – это тело функции. Самая простая функция просто возвращает сообщение как оно есть:

return msg;

Если функция возвращает «null», то никакого сообщения дальше не пойдет, и поток завершится.

Функция всегда должна возвращать объект «msg». Если она вернет число или строку, это выдаст ошибку.

Возвращаемое сообщение не обязательно должно быть тем же объектом, который был на входе. Функция может создать на основе входного сообщения совершенно новый объект. Например:

var newMsg = { payload: msg.payload.length };
return newMsg;

Примечание: Как правило, при конструировании нового объекта «msg» свойства входящего объекта «msg» всегда претерпевают изменения. Это нарушает работу некоторых потоков – например, потоков с нодами «http in» или «http response», которым на протяжении всего потока нужны свойства «msg.req» и «msg.res».

Отправка сообщений на несколько выходных портов

В меню редактирования ноды «Function» можно, помимо прочего, поменять количество выходных портов. Если у ноды больше одного выходного порта, то функция может вернуть не одно сообщение, а массив сообщений для отправки на разные выходные порты в зависимости от определенных условий.

К примеру, функция ниже устроена таким образом, что если в свойстве «Topic» сообщения значится «banana», то это сообщение будет отправлено на второй выходной порт, а не на первый.

if (msg.topic === "banana") {
   return [ null, msg ];
} else {
   return [ msg, null ];
}

Код ниже отправит исходное сообщение как есть на первый выходной порт, а сообщение, содержащее размер «payload» – на второй выходной порт.

var newMsg = { payload: msg.payload.length };
return [msg, newMsg];

Отправка нескольких сообщений

Нода «Function» может вернуть несколько сообщений в виде массива сообщений. Если выходной порт отправляет несколько сообщений, то последующие ноды будут получать эти сообщения по очереди – в том порядке, в котором функция их возвращала.

Код ниже отправит сообщения «msg1», «msg2» и «msg3» на первый выходной порт, а сообщение «msg4» будет отправлено на второй выходной порт.

var msg1 = { payload:"первое – на выходной порт 1" };
var msg2 = { payload:"второе – на выходной порт 1" };
var msg3 = { payload:"третье – на выходной порт 1" };
var msg4 = { payload:"четвертое – на выходной порт 2" };
return [ [ msg1, msg2, msg3 ], msg4 ];

Код ниже разбивает свойство «payload» полученного сообщения на два отдельных слова и возвращает по отдельному сообщению для каждого из этих слов:

var outputMsgs = [];
var words = msg.payload.split(" ");
for (var w in words) {
    outputMsgs.push({payload:words[w]});
}
return [ outputMsgs ];

Асинхронная отправка сообщений

Если функции нужно выполнить асинхронное действие перед отправкой сообщения, то в конце она не сможет вернуть сообщение.

Поэтому вам нужно будет воспользоваться функцией node.send() – чтобы передать ей сообщение/сообщения, которые нужно отправить. Например:

doSomeAsyncWork(msg, function(result) {
    node.send({payload:result});
});
return;

Кроме того, если вы собрались воспользоваться асинхронной функцией, то вам, возможно, нужно будет удалить все необслуженные запросы и закрыть все соединения, пока вы снова не выполните развертку этого потока. Это можно сделать при помощи обработчика событий «close».

node.on('close', function() {
    // Удаляем весь асинхронный код:
    // отключаем соединения и так далее
});

Логирование событий

Если вам нужно с помощью ноды «Function» выполнить логирование в терминал, для этого можно воспользоваться одной из следующих функций:

node.log("Something happened");
     //  "Что-то произошло"
node.warn("Something happened you should know about");
     //  "Произошло что-то, о чем вы должны знать"
node.error("Oh no, something bad happened");
     //  "О нет, случилось что-то ужасное!"

Функции warn() и error() также будут отправлять сообщения на вкладку «Debug» боковой панели.

Нужны более подробные логи? Воспользуйтесь функциями trace() и debug(). Но если ваш инструмент для чтения логов не настроен на то, чтобы считывать информацию этих уровней логирования (TRACE и DEBUG), то вы эту информацию, соответственно, и не увидите.

Обработка ошибок

Если функция наткнется на ошибку, останавливающую работу текущего потока, то ничего не вернет. Чтобы активировать ноду «Catch», находящуюся в той же вкладке, эта функция должна вызвать node.error(), вторым параметром которой служит исходное сообщение.

node.error("ошибка!", msg);

Хранение данных

Нода «Function» может сохранять данные не только в объект «msg», но и в нужный контекст.

Более подробно о контекстах Node-RED читайте в этой статье.

В ноде «Function» можно использовать три предопределенные переменные, дающие доступ к нужному контексту:

  • «context» – локальный контекст ноды
  • «flow» – контекст потока
  • «global» – глобальный контекст

Во фрагментах кода ниже используется контекст «flow», но их можно применить и к двум другим контекстам – «context» и «global».

Примечание

Эти предопределенные переменные – инструментарий ноды «Function». Если вы создаете собственную ноду, ознакомьтесь с этим руководством (в нем объясняется, как получить доступ к нужному контексту).

Есть два режима доступа к контексту – синхронный и асинхронный. Встроенные контексты Node-RED поддерживают оба этих режима. Некоторые хранилища данных могут поддерживать только асинхронный режим и, если попытаться получить к ним доступ синхронно, они выдадут ошибку.

Чтобы прочесть значение из контекста, воспользуйтесь следующей функцией:

var myCount = flow.get("count");

Чтобы записать значение в контекст:

flow.set("count", 123);

Код ниже считает, сколько раз была запущена нода «Function»:

// Если счетчика еще нет, инициализируем его со значением «0»:
var count = context.get('count')||0;
count += 1;
// Сохраняем измененное значение обратно:
context.set('count',count);
// Делаем это значение частью выходящего объекта «msg»:
msg.count = count;
return msg;

Запись/считывание нескольких значений

Начиная с версии 0.19 в Node-RED стало возможно одновременно сохранять/считывать несколько значений. Например, считывание теперь выполняется вот так:

// Node-RED 0.19 или новее:
var values = flow.get(["count", "colour", "temperature"]);
// values[0] – это значение «count»
// values[1] – это значение «colour»
// values[2] – это значение «temperature»

А запись вот так:

// Node-RED 0.19 или новее:
flow.set(["count", "colour", "temperature"], [123, "red", "12.5"]);

В этом случае все недостающие значения будут выставлены на «null».

Асинхронный доступ к данным контекста

Если данные контекста требуют асинхронного доступа, то в функции get() и set() нужно будет добавить дополнительный параметр – функцию обратного вызова.

// Считывание одного значения:
flow.get("count", function(err, myCount) { ... });

// Считывание нескольких значений:
flow.get(["count", "colour"], function(err, count, colour) { ... })

// Сохранение одного значения:
flow.set("count", 123, function(err) { ... })

// Сохранение нескольких значений:
flow.set(["count", "colour", [123, "red"], function(err) { ... })

Первый параметр функции обратного вызова («err») задается, только если при доступе к контексту выскакивает ошибка.

Асинхронная версия для кода, считающего, сколько раз была запущена нода «Function», будет выглядеть следующим образом:

context.get('count', function(err, count) {
    if (err) {
        node.error(err, msg);
    } else {
        // Если счетчика еще нет, инициализируем его со значением «0»:
        count = count || 0;
        count += 1;
        // Сохраняем измененное значение обратно:
        context.set('count',count, function(err) {
            if (err) {
                node.error(err, msg);
            } else {
                // Делаем сообщение частью выходящего объекта «msg»:
                msg.count = count;
                // Отправляем сообщение:
                node.send(msg);
            }
        });
    }
});

Сохранение нескольких значений в контекст

Начиная с версии 0.19 в Node-RED в контекст стало возможно сохранять сразу несколько значений. К примеру, стало возможно сохранять данные и в память, и в локальную файловую систему.

Для этого в функциях get() и set() нужно будет задать дополнительный параметр. В коде ниже это «storeName», но вам нужно будет вписать вместо него название нужного хранилища.

// Считывание значения (синхронный режим):
var myCount = flow.get("count", storeName);

// Считывание значения (асинхронный режим):
flow.get("count", storeName, function(err, myCount) { ... });

// Сохранение значения (синхронный режим):
flow.set("count", 123, storeName);

// Сохранение значения (асинхронный режим):
flow.set("count", 123, storeName, function(err) { ... })

Глобальный контекст

Вы можете заранее – прямо при запуске Node-RED – загрузить нужные данные в глобальный контекст. Это задается в главном файле настроек «settings.js» в свойстве «functionGlobalContext». Эту функцию можно использовать для загрузки дополнительных модулей для того, чтобы затем использовать их в ноде «Function».

Добавление статуса

Нода «Function» также поддерживает добавление статуса – аналогично другим нодам. Чтобы задать статус, вызовите функцию node.status(). Например:

node.status({fill:"red",shape:"ring",text:"Отключено"});
node.status({fill:"green",shape:"dot",text:"Подключено"});
node.status({text:"Просто текстовый статус"});
node.status({});   // очистка статуса

Более подробно о параметрах этой функции читайте тут.

Изменения статуса можно определять при помощи ноды «Status».

Загрузка дополнительных модулей

Нода «Function» не поддерживает прямую загрузку дополнительных модулей. Они должны быть загружены в файл «settings.js» и добавлены в свойство «functionGlobalContext».

К примеру, встроенный модуль «os» можно сделать доступным для всех функций, добавив в файл «settings.js» следующее:

functionGlobalContext: {
    osModule:require('os')
}

В результате к этому модулю можно будет обратиться внутри функции следующим образом:

global.get('osModule')

Модули, загружаемые из файла настроек, должны быть установлены в ту же директорию, где находится файл настроек. У большинства пользователей это скорее всего будет пользовательская папка, используемая по умолчанию: «~/.node-red»:

cd ~/.node-red
npm install название_стороннего_модуля

Справочник по API ноды «Function»

В ноде «Function» можно использовать следующие объекты и функции:

  • Объект «node»
    • node.id – ID ноды «Function» (добавлено в версии 0.19)
    • node.name – название ноды «Function» (добавлено в версии 0.19)
    • node.log() – логирование сообщения
    • node.warn() – логирование сообщения уровня WARN
    • node.error() – логирование сообщения уровня ERROR
    • node.debug() – логирование сообщения уровня DEBUG
    • node.trace() – логирование сообщения уровня TRACE
    • node.on() – регистрация прослушивателя событий
    • node.status() – обновление статуса ноды
    • node.send() – отправка сообщения
  • Объект «context»
    • context.get() – считывание свойства из контекста ноды
    • context.set() – запись свойства в контекст ноды
    • context.keys() – возвращение списка всех ключей их контекста ноды
    • context.flow() – то же самое, что и «flow»
    • context.global – то же самое, что и «global»
  • Объект «flow»
    • flow.get() – считывание свойства из контекста потока
    • flow.set() – запись свойства в контекст потока
    • flow.keys() – возвращение списка всех ключей из контекста потока
  • Объект «global»
    • global.get() – считывание свойства из глобального контекста
    • global.set() – запись свойства в глобальный контекст
    • global.keys() – возвращение списка всех ключей из глобального контекста
  • RED
    • RED.util.cloneMessage() – безопасное клонирование объекта сообщения, чтобы его можно было использовать повторно

Другие модули и функции

В коде ноды «Function» также можно использовать нижеследующие модули и функции:

  • Buffer – модуль «Buffer» из Node.js
  • console – модуль «console» из Node.js (для логирования лучше пользоваться node.log)
  • util – модуль «util» из Node.js
  • setTimeout() и clearTimeout() – функции JavaScript для работы с таймаутами
  • setInterval() и clearInterval() – функции JavaScript для работы с интервалами
Примечание

Нода «Function» при остановке или повторной развертке автоматически удаляет все незаконченные таймауты и таймеры интервалов.

См.также

Внешние ссылки