Асинхронный обмен данными с JavaScript (и не только) без потерь

Программирование - Практика программирования

Все больше и больше появляется разработок с использованием JavaScript в формах 1С. Вместе с тем достаточно интересным является вопрос передачи данных между скриптом JavaScript и программным модулем 1С. Не претендуя на оригинальность, хочу предложить несложный и практичный механизм передачи данных с использованием очереди.

Идея этой публикации родилась после чтения одной очень полезной статьи Javascript и 1С. Кросс-платформенное взаимодействие. В обсуждении этой публикации  была затронута интересная проблема:

... события на вебстранице возникают асинхронно, форма 1С, в общем случае, может какие-то из виртуальных кликов пропустить. Тип переменной interactionVariable лучше сделать строкой и не просто присваивать, а дописывать в неё json очередного сообщения. На стороне формы 1С реализовать очередь, в которую складывать элементы массива из interactionVariable и последовательно обрабатывать.

............

... непонятно, как поведет себя одновременный .pop() у этого стека в 1С, и .push() в javascript, при отсутствии mutex'ов.

...........

... Поведёт себя плохо, решение не идеальное, но вероятность проблем ниже.

Однако существует достаточно простой механизм, лишенный вышеуказанных недостатков.

Пусть имеется два процесса: процесс-источник, посылающий данные, и процесс-приемник, эти данные принимающий. Создадим очередь - некую упорядоченную последовательность данных, обращение к которым возможно по индексу. И для 1С и для JavaScript это может быть простой массив. Помимо очереди, сделаем два указателя на начало и конец очереди: head и tail. При возникновении необходимости процесс-источник помещает данные в хвост очереди и сдвигает указатель tailПроцесс-приемник периодически, асинхронно, проверяет наличие данных в очереди, считывает их и сдвигает указатель head. Таким образом head как бы "догоняет" tail.  Индикатором наличия данных в очереди является неравенство tail <> head. Как видим, каждый из процессов имеет доступ на запись только к своему указателю, что делает ненужным организацию какого-либо конкурентного доступа к указателям, использование семафоров-mutexов и т.п. Доступ к данным в очереди также разделен: процесс-источник помещает порцию данных до того как сдвигает tail, а процесс-источник обратится к этим данным после того как tail будет сдвинут.  Аналогичным образом можно реализовать и систему, в которой получателей будет несколько - каждый процесс-приемник должен работать со своим указателем head, разница между tail и head для каждого получателя будет означать количество еще необработанных им элементов данных.

Практическая реализация этого механизма несложна и может быть реализована в двух вариантах:

  1. Очередь - обычный буфер. В этом случае создается динамический массив, который растет с увеличением количества передаваемых порций данных. Указатели tail и head также увеличиваются. Для того, чтобы не допустить неограниченного роста массива, перед добавлением очередных данных надо сделать проверку: если очередь пуста, т.е. tailhead, то сбрасываем оба указателя в начало массива. Этот вариант подходит в том случае, если процесс-приемник в целом чаще забирает данные из очереди, чем процесс-источник их туда помещает. В противном случае head никогда не "догонит" tail и мы получим неограниченный рост буфера. Но зато этот вариант гарантирует получение всех данных процессом-приемником.
  2. Кольцевой буфер. Этот вариант лучше использовать при недостатке памяти для очереди и/или когда помещение данных происходит чаще их получения. Для этого организуется массив с фиксированным количеством элементов. Когда указатель, увеличиваясь, доходит до конца массива, то его значение меняется на начало этого же массива. При росте очереди, т.е. при увеличении  tail, необходимо проверять чтобы tail не "догнал сделав круг" указатель head. Если это должно произойти, то следует либо сдвинуть оба указателя, потеряв таким образом самые старые данные, либо не помещать новые данные в очередь, потеряв их. В первом случае мы лишаемся преимущества разделенного доступа к указателям очереди. А что касается второго случая -  программисты MS-DOS вспомнят, что именно так организован клавиатурный буфер: по адресу 0040:001E могло быть записано 15 клавиатурных нажатий, а далее происходило "переполнение буфера".

В качестве примера работы рассмотренного механизма создадим обработку, которая будет содержать поле HTML. В поле HTML будем получать координаты указателя мыши при его перемещении и передавать эту информацию в форму 1С. Очередь организуем средствами JavaScript. Данные будем передавать в формате JSON.

Текст поля HTML:

<!DOCTYPE html>
<html style="height: 100%;">
<head>	
<meta http-equiv="X-UA-Compatible" content="IE=9"/>	
<script>

var queue = Array(); // Очередь событий
var queueHead = -1; // Указатель на начало очереди
var queueTail = -1; // Указатель на конец очереди
var totalEventCount = 0; //Количество обработанных событий

//Помещение произвольных данных в очередь
function pushEvent(dataEvent){
	if (queueHead == queueTail && queueHead > -1){ //Если очередь пуста, сбросим указатели
		queueHead = queueTail = -1;
	}
	queue[queueTail + 1] = JSON.stringify(dataEvent);//Преобразуем данные в JSON
	queueTail += 1;
	totalEventCount += 1;
}

//Получение данных очереди по индексу
function getEvent(index){
	return queue[index];
}

//Обработчик события движения указателя мыши
function OnMouseMoveEvent (event) {	
   	//Поместим данные в очередь
	pushEvent({x:event.clientX, y:event.clientY});
	//Отобразим координаты
	document.getElementById("coord").textContent = "X=" + event.clientX + " Y= " + event.clientY + " Total=" + totalEventCount;
	//Уведомим форму 1С
	button1c.click();
}

</script>
</head>

<body style="height: 100%;">
<div id="coord" style="height: 100%;"></div>

//Невидимая кнопка для уведомления формы 1С
<button id="button1c" style="display: none"></button>
</body>
</html>

При движении мыши вызывается обработчик OnMouseMoveEvent, в котором происходит помещение данных о координатах в очередь и уведомление формы 1С об этом событии. Уведомление осуществляется с помощью эмуляции нажатия невидимой кнопки button1c. 

На стороне 1С обработаем событие от поля HTML:

&НаКлиенте
Процедура ХТМЛПриНажатии(Элемент, ДанныеСобытия, СтандартнаяОбработка)
	Если ДанныеСобытия.button.id="button1c" Тогда   		
		ПолучитьИОбработатьДанные();		
	КонецЕсли; 	
КонецПроцедуры

и, для надежности, получение и обработку данных добавим в обработчике ожидания:

&НаКлиенте
Процедура ПриОткрытии(Отказ)
	ПодключитьОбработчикОжидания("ПолучитьИОбработатьДанные",1);
КонецПроцедуры

Процедура получения данных:

&НаКлиенте
Процедура ПолучитьИОбработатьДанные()Экспорт
	ОкноДок=ЭтаФорма.Элементы.ХТМЛ.Документ.parentWindow;
	Если ОкноДок=Неопределено  Тогда   
		ОкноДок=ЭтаФорма.Элементы.ХТМЛ.Документ.defaultView;
	КонецЕсли; 
	
	Пока ОкноДок.queueHead<ОкноДок.queueTail Цикл //Если есть что-то в очереди
		Данные=JSON_В_Объект(ОкноДок.getEvent(ОкноДок.queueHead+1));
		ОкноДок.queueHead=ОкноДок.queueHead+1;
		ВсегоСобытий=ВсегоСобытий+1;
		Сообщить("X="+Данные.x+" Y="+Данные.y+" Всего событий="+ВсегоСобытий); 				
	КонецЦикла; 	
КонецПроцедуры // 

Преобразование полученных данных

&НаСервереБезКонтекста
Функция JSON_В_Объект(СтрокаJSON)	
	Чтение=новый ЧтениеJSON();
	Чтение.УстановитьСтроку(СтрокаJSON);
	Результат=ПрочитатьJSON(Чтение);
	Чтение.Закрыть();		
	Возврат Результат;
КонецФункции //

Конечно же, рассмотренный механизм может быть использован не только в случае взаимодействия JavaScript и 1С. Его можно применять для асинхронного обмена данными между самыми разными процессами, но при соблюдении следующих требований: очередь должна быть постоянно доступна всем участникам обмена, и добавлять данные в очередь может только один участник. 

К статье приложена обработка с вышеописанным примером.

PS: Почему-то не получилось добавить обработку-пример ни для бесплатного скачивания, ни для скачивания за 0 StartMoney. Печаль.

Скачать файлы

Наименование Файл Версия Размер
Пример к статье
.epf 7,95Kb
25.12.16
9
.epf 7,95Kb 9 Скачать

См. также

Комментарии
1. Виталий Чебан (VitaliyCeban) 217 26.12.16 10:25 Сейчас в теме
Учитывая что событие ПриНажатии может быть пропущено, и оно могло быть последним (не поможет обработка предыдущих событий при следующем ПриНажатии, так как его не будет), то относительная гарантия доставки, в итоге, достигается исключительно за счет следующего кода?
&НаКлиенте
Процедура ПриОткрытии(Отказ)
	ПодключитьОбработчикОжидания("ПолучитьИОбработатьДанные",1);
КонецПроцедуры

Относительная, потому что форма может быть закрыта до очередного срабатывания таймера, впрочем, это я уже утрирую )

В любом случае, решение интересное, и существенно лучше его отсутствия, спасибо.
2. Игорь Никик (igo1) 143 26.12.16 13:28 Сейчас в теме
вообще вот пример как работать без обработчика ожидания, можно просто вызвать событие "нажатие", и передать туда данные

http://infostart.ru/public/338126/
3. Виталий Чебан (VitaliyCeban) 217 26.12.16 13:45 Сейчас в теме
(2) Прочитайте более внимательно начало этой публикации.
можно просто вызвать
оно то можно, только есть ненулевая вероятность что в 1С событие ПриНажатии вызвано НЕ будет.
Эту проблему и призвана решить данная публикация.
4. Александр Быков (Alxby) 184 26.12.16 13:48 Сейчас в теме
(3) Спасибо, опередили с ответом:)
5. Александр Быков (Alxby) 184 26.12.16 13:56 Сейчас в теме
(1) Да, при закрытии формы мы можем потерять последние события. Но нам никто не мешает перед закрытием формы их все-таки принудительно получить. К тому же, как мне кажется, основное применение JavaScript - визуальные компоненты интерфейса, данные которых вряд ли нужны после закрытия формы.
6. Сан Саныч (herfis) 116 26.12.16 15:50 Сейчас в теме
"события на вебстранице возникают асинхронно, форма 1С, в общем случае, может какие-то из виртуальных кликов пропустить"
Поясните. По какой причине 1С может пропустить клики. И в каких случаях. Где можно об этом почитать в подробностях.
В документации по "ПриНажатии" ничего такого нет. Т.е. предполагается, что ловиться должны все события.
"события на вебстранице возникают асинхронно" - ну и что с того?
7. Сан Саныч (herfis) 116 26.12.16 15:55 Сейчас в теме
В общем, если пропуски в самом деле происходят, то это недоработка разработчиков 1С. Это они должны были организовывать и обслуживать очереди обрабатываемых событий, если уж задекларировали механизм получения событий от поля HTML документа.
Просто хочется убедиться, что проблема в самом деле существует. Я на самом деле далек от темы, просто интересно.
8. Виталий Чебан (VitaliyCeban) 217 26.12.16 16:45 Сейчас в теме
(7) Поинтересуйтесь у http://infostart.ru/profile/52749/
Самому не приходило сталкиваться с проблемой, но не доверять Евгению, учитывая созданные им решения, у меня оснований нет.
9. Александр Быков (Alxby) 184 26.12.16 16:58 Сейчас в теме
(7) Как бы то ни было, основной целью написания статьи было не решение конкретной проблемы, указанной в примере, а описание способа решения подобных ей проблем, не обязательно связанных с взаимодействием с JavaScript.
10. Сан Саныч (herfis) 116 26.12.16 17:04 Сейчас в теме
(8) ИМХО, если 1С не делает пропусков, скажем, при нескольких событиях HTML с интервалом 1ms, когда в 1С каждое обрабатывается к примеру 5ms (т.е. какая-то очередь все-таки есть), то для большинства практических задач нет смысла обслуживать свою очередь событий. Когда возникнет практическая задача - поставлю эксперимент, раз тут занимаются решением проблем о которых знают по-наслышке.
Задачи Евгения, насколько я понимаю, достаточно специфичны.
11. Сан Саныч (herfis) 116 26.12.16 17:29 Сейчас в теме
(9) Соглашусь. Статью плюсанул.
Сначала зацепился за то, что queueHead пишется из двух процессов (при сбросе очереди), но получается что сброс невозможен одновременно с обработкой события в 1С. Остроумное решение проблемы мьютексов при условии частого обнуления очереди.
12. Александр Быков (Alxby) 184 26.12.16 21:18 Сейчас в теме
(11)Здесь нелишним будет упомянуть, что в JavaScript оператор присваивания имеет ассоциативность справа налево, и
queueHead = queueTail = -1;
эквивалентно
queueTail = -1;
queueHead = -1;
что гарантирует: в процессе обнуления queueHead будет не меньше queueTail и чтение из очереди невозможно.
13. Игорь Никик (igo1) 143 27.12.16 15:20 Сейчас в теме
(3) не встречал таких проблем с пропуском нажатий, но в таком случае я бы решал задачу немного иначе, реализовав гарантированную доставку.

14. Александр Быков (Alxby) 184 27.12.16 16:12 Сейчас в теме
(13) У меня тоже была идея реализовать механизм, похожий на работу TCP (правда для решения другой задачи). Может быть напишете статью о своем варианте?
15. Сан Саныч (herfis) 116 27.12.16 16:29 Сейчас в теме
(13) Каким именно образом и чем он лучше? Статья ведь как раз и рассматривает вариант реализации гарантированной доставки.
16. Петр Базелюк (pbazeliuk) 1274 27.12.16 17:46 Сейчас в теме
(15) Термин "гарантированная доставки" здесь не применим. Объекты жестко связаны, они не могут отдельно работать друг от друга, нет восстановления состояния, например, после падения кластера 1С.

В этом случае, решена проблема пропускания "кликов".

17. Сан Саныч (herfis) 116 27.12.16 18:23 Сейчас в теме
(16) Вы пытаетесь сказать, что под термин "гарантированная доставка" подходят только внешние сервисы очередей? Не думаю, что в (13) именно это подразумевалось :)
В нашем случае "гарантированность" имеет смысл только в рамках жизненного цикла формы.
18. Петр Базелюк (pbazeliuk) 1274 28.12.16 10:39 Сейчас в теме
(17) Сервис очередей не упоминал вроде как. Термин "гарантированная доставка" не должен зависеть от контекста и он не применим точно здесь. Правильней назвать это "fire-and-forget".
19. Игорь Никик (igo1) 143 28.12.16 12:35 Сейчас в теме
(9) Статья очень полезная, вы не расстраивайтесь. Гора комментариев как раз об этом и говорит.
20. Пользователь Инфостарта (infostart user) 15 28.12.16 14:25 Сейчас в теме
21. Сан Саныч (herfis) 116 29.12.16 10:12 Сейчас в теме
(18)
Термин "гарантированная доставка" не должен зависеть от контекста и он не применим точно здесь

Ок. Раз не должен зависеть от контекста, то вы подразумеваете его исчерпывающее определение. Дайте ссылочку, плиз. Я такого не нашел.
Везде зависит от контекста. Например, как по вашему - TCP обеспечивает "гарантированную доставку" или нет?
22. Петр Базелюк (pbazeliuk) 1274 29.12.16 11:49 Сейчас в теме
(21) Он обеспечивает надежную передачу данных, но "негарантированную". Термин "гарантированная доставка" появился как описание паттерна. Действительно, пример, который не связан с очередями, сложно найти.
Оставьте свое сообщение