Доступный JavaScript на прикладном примере создания страницы/сайта с действиями пользователей голоса в реальном времени


ava.png

В прошлом посте мы с вами начали разбирать нашу тестовую страничку https://vikx.tumblr.com/ на которой видим живой фид событий на голосе:

Подключаемся к ноде wss://ws.golos.io через блокнот. API голоса.

В этой статье мы углубимся в суть ответов от голоса

Мы уже создали основные функции и стали получать данные от голоса, остановившись на этапе обработки полученных данных, а именно - json.
https://ru.wikipedia.org/wiki/JSON

Фильтрация данных

Отложим нашу готовую страницу из прошлого поста и создадим новую с минимум кода и максимум наглядности.
Код страницы тут http://pastebin.com/DALX7mkU
Если вы умеете пользоваться консолью браузера - вы можете просто вставить в консоль часть кода со страницы:
`var socket = new WebSocket('wss://ws.golos.io'), lastblock, startblock;
socket.onopen = function(event) {
setInterval(function() {
socket.send(JSON.stringify({
id: 1,
method: 'get_dynamic_global_properties',
'params': []
})); lastblock++;

}, 3000);

};
fixblock = true;
socket.onmessage = function(event) {
console.log(event.data); // Вывод данных в консоль
};

при этом не важно на каком сайте вы находитесь - в консоли вы увидите обновляемый каждые 3 секунды лог блоков голоса. Войти в консоль можно нажав правую кнопку мыши на любом сайте > просмотреть код элемента > в открывшемся окне найти console 
console2.gif
В консоли вам будет очень удобно делать моментальную, живую отладку вашего скрипта, но если вам удобнее работать в html документе - скопируйте html код с pastebin в него и далее редактируйте - я буду давать примеры для консоли и для документа. 

На примере скрина мы рассмотрим задействованный JavaScript
Javascript
Подробное описание функций было в прошлом посте, потому описание будет лаконичным:

4-6 строчка объявление переменных. где `socket` = наш метод и адрес подключения, а `lastblock, ` и `startblock;` наши глобальные переменные, которые мы заполним изнутри будущих функций позднее, просто запомните, что они есть.

6-15 строчка сокет-подключение к wss://ws.golos.io и отправка запроса `get_dynamic_global_properties` каждые 3000 миллисекунд, что равно сроку генерации нового блока данных. Если чаще - будут дубли, если реже - потери данных. Потому 3000 ms. 

20-22 строчка функция, которая будет выполняться при ответе от голоса. Поскольку мы задаем "вопрос" каждые 3 секунды, ответ мы получаем так же - каждые 3 секунды (если только не произошла ошибка подключения. В прошлом посте описано как ее обработать) Внутрь функции мы поместили наш `console.log(event.data)` который выводит в консоль ответы от голоса.

## Что нам отвечает голос
На наш главный вопрос `get_dynamic_global_properties` голос ответил в консоль:

{
"id": 1,
"result": {
"id": "2.0.0",
"head_block_number": 3102441,
"head_block_id": "002f56e9303a9584e309561de5269ccc034695ba",
"time": "2017-02-03T08:27:42",
"current_witness": "synergen",
"total_pow": 148367,
"num_pow_witnesses": 97,
"virtual_supply": "96370622.101 GOLOS",
"current_supply": "96090382.166 GOLOS",
"confidential_supply": "0.000 GOLOS",
"current_sbd_supply": "497761.875 GBG",
"confidential_sbd_supply": "0.000 GBG",
"total_vesting_fund_steem": "95498639.220 GOLOS",
"total_vesting_shares": "449108864834.014080 GESTS",
"total_reward_fund_steem": "72481.120 GOLOS",
"total_reward_shares2": "984136553898610486608041048565",
"sbd_interest_rate": 1000,
"sbd_print_rate": 10000,
"average_block_size": 224,
"maximum_block_size": 65536,
"current_aslot": 3107354,
"recent_slots_filled": "340282365653287863235145205935065006079",
"participation_count": 127,
"last_irreversible_block_num": 3102424,
"max_virtual_bandwidth": "5986734968066277376",
"current_reserve_ratio": 20000,
"vote_regeneration_per_day": 40
}
}

Что нам делать с этими общими и глобальными данными, что бы получить желаемые данные на пользователей.
Нам нужно получить номер последнего блока, который содержит данные активности - он у нас указан в ответе как `last_irreversible_block_num`

Для этого мы добавим переменную внутрь ранее созданной функции обработчика ответа
`startblock = data.result.last_irreversible_block_num;`

socket.onmessage = function(event) {
data = JSON.parse(event.data); // полученные данные преобразуем в объекты
startblock = data.result.last_irreversible_block_num; // Та самая переменная, которую мы добавили
console.log(startblock); // Укажем переменную в лог консоли, что бы проверить все ли верно
};

Тут очень важно, что вы бы понимали, как указать в переменную конкретные данные из массива которым ответил голос. Если вы умеете обращаться с Json - некоторая часть текста ниже для вас будет очевидной.
Для тех, кто с json не знаком:
Мы указываем своеобразный путь к значению `last_irreversible_block_num` : 
`data.result.значение_этой_строки`
Вспомним как выглядит ответ от голоса и переведем его в примитивный ответ с ФИО

{
"id":123 ,
"ФИО": {
"Фамилия": "Васечкин",
"Имя": "Петя"
}
}

Что бы записать в переменную только фамилию мы сделаем так:
`переменная = data.ФИО.фамилия;`

Вот именно по такому принципу мы записали в переменную:
`startblock = data.result.last_irreversible_block_num;`
Позднее будут вложенные объекты, что немного сложнее.

Таким образом мы записали себе перемененную с номером последнего блока голоса. И раз уж мы тут, возьмем на заметку еще несколько значений из ответа:
`total_vesting_shares `
`total_vesting_fund_steem`
`vesting_shares`
В будущем мы сможем спрашивать голос данные аккаунтов и по формуле 
` total_vesting_fund_steem( vesting_shares / total_vesting_shares )` вычислять текущую силу голоса.

Ну о пока мы выводим в консоль

socket = new WebSocket('wss://ws.golos.io');
socket.onopen = function(event) {
setInterval(function() {
socket.send(JSON.stringify({
id: 1,
method: 'get_dynamic_global_properties',
'params': []
}));

}, 3000);

};
fixblock = true;
socket.onmessage = function(event) {
data = JSON.parse(event.data); // Полученные данные конвертируем в удобный формат
startblock = data.result.last_irreversible_block_num;
console.log(startblock);
};

И получаем ответ из переменной `startblock` - где у нас номера блоков
numdoubletr.gif
И вот гадство! Там попадаются дубли номеров блоков, а это значит, что у нас, на нашей страничке тоже будут дубли одни и тех же данных. Причина вот в чем:
Следует понимать, что поскольку мы спрашиваем голос о чем-то каждые 3 секунды, то и отвечать он нам будет так же каждые три секунды. Ну почти 3...
Мы посылаем запрос на глобальные данные каждые три секунды, однако скорость соединения может быть низкой, могут быть обрывы и ошибки на нодах, как следствие данные о блоках приходят не в соответствии 3-м секундам, а немного хаотиченй. 

Исправляем дубли блоков и данных так:
Идея в том, что бы получить стартовый блок, а после, не запрашивать каждый раз по новой значение  блока и надеяться , что авось , блок не опоздает и уложится в 3 секунды. А четко задать алгоритм, при котором номер блока будет увеличиваться на +1 ровно с интервалом 3000мс
Делаем так: 
Внутри нашей интервальной 3-х секундной функции ` setInterval(function() { ` мы добавляем переменную  `lastblock++;`, таким образом каждые 3 секунды значение переменной будет увеличено на +1.
Далее мы вспоминаем про нашу таинственную `fixblock = true;` - мы задаем эту переменную глобально, не внутри, а за пределами функции
 `socket.onmessage = function(event) {` 
И потом уже внутри onmessage делаем условие

// Выполнять если fixblock = true (мы указали это за пределами onmessage, что он true):
if (fixblock) {
// Получаем номер реального блока на начало запуска скрипта и записываем в переменную:
startblock = data.result.last_irreversible_block_num;

// записываем значение созданной переменную в переменную lastblock
lastblock = startblock;
fixblock = false; //теперь переводим условие в false
}

Работает это так - скрипт выполнятся последовательно:
* Отправляем запрос `get_dynamic_global_properties`
* Видим что `fixblock = true` 
* Выполняем условие если `fixblock = true` запишем в `lastblock` номер последнего блока
* Переводим  `fixblock` в состояние `false`
* Через 3 секунды снова отправляем запрос
* Но видим, что  `fixblock = false` и не запишем в `lastblock` номер последнего блока.
* А внутри функции с отправкой запроса выполним  `lastblock++;`, что точно по интервалу увеличит номер блока на 1.
Теперь никаких дублей и пропусков - четкий номер нового блока каждые 3000 ms!

Но сам по себе номер нам ни о чем не скажет, нам нужно получить данные с блока под номером N. Для этого делаем вот что:

Внутри `socket.onmessage` размещаем дополнительные условия:

if (data.id === 1 ) {
// Тут будет функция выполняемая если этот ответ на запрос которому был присвоен id = 1
}

 Если вы помните, наш самый первый запрос голосу был 
`id: 1, method: 'get_dynamic_global_properties'`, стало быть ответ на этот запрос будет подписан как id 1. Мы ведь должны понимать, на какой вопрос получили ответ, потому все запросы мы сопровождаем id. Получив ответ на вопрос, мы создаем условие
`if (data.id === 1  ) `
Его можно понимать как если данные ответа с id 1, то... 
... Посылаем еще пару запросов голосу. 

// Если это ответ на запрос с id 1...
if (data.id === 1 ) {
// Отправляем запрос на получение аккаунта

socket.send(JSON.stringify({
id: 2,
method: 'get_accounts',
params: [
['vik'] // Имя аккаунта
]
}));
// Отправляем запрос на получение данных текущего блока
socket.send(JSON.stringify({
id: 3,
method: 'get_block',
'params': [lastblock]
}));
}


Мы попросили голос дать нам данные аккаунта `get_accounts` , 
а конкретно моего - `params: [ ['vik']  ]` и присвоили этому запросу id 2
Второй запрос с id 3 - мы просим данные блока
` method: 'get_block'`, а конкретно блока под номером из переменной `lastblock`. В этой переменной у нас как вы знаете перечисляются блоки каждые 3 секунды начиная со стартового. 
>стартовый блок у нас начинается с последнего актуального на момент открытия вами страницы со скриптом, но можно сделать и ручное указание блока, как в примере vikx.tumblr.com или, что еще сделаем - это выбор даты и времени с которого начинать стрим данных

 Выведем получаемые данные о блоках в мало-мальски читаемом формате.

// Если ответ содержит данные из запроса с id 3, а именно данные блока
if (data.id === 3 ){
/// Конвертируем данные в читаемый формат
blockdata = JSON.stringify(data.result);
/// Выводим в консоли
console.log(blockdata);
/// Выводим и на странице в ранее созданный блок c id#result
document.getElementById('result').insertAdjacentHTML('afterbegin',blockdata);
}

Теперь мы получаем каждые 3 секунды информацию блоков и отображаем ее в консоли и на странице

Снимок данных с пустыми блоками.png 
На скрине мы видим два содержательных блока и 3 с неинтересными для нас данными. Общее у бесполезных блоков это свойство
` transaction_merkle_root":"0000000000000000000000000000000000000000" `
Нули красноречиво намекают на пустышку, которая только засоряет эфир. Убираем из стрима такие блоки добавив в существующее условие: `data.id === 3` дополнительную строку.
Создаем переменную за пределами if условий, внутри функции onmessage после 
`data = JSON.parse(event.data)`
Пишем
`merkle = data.result.transaction_merkle_root;`
Возвращаемся в наше условие с if id 3 и добавляем туда строку:

if (data.id === 3 && merkle !== "0000000000000000000000000000000000000000"){

"Если ответ на вопрос с id 3 и в merkle не нули, то выполнять..."
Теперь в стриме наших данных уже не буде мусорных блоков, а только те, которые содержат полезные данные. Правда все еще в не фильтрованном виде...
Снимок данных с норм блоками
Но прочесть уже что-то можно.
Но все еще много лишнего. Например чей-то комментарий :

{
"previous": "002f64c52e27a",
"timestamp": "2017-02-03T11:25:45",
"witness": "kuna",
"transaction_merkle_root": "0c8536f6a2",
"extensions": [],
"witness_signature": "f7cbb5",
"transactions": [{
"ref_block_num": 25751,
"ref_block_prefix": 3393368288,
"expiration": "2017-02-03T11:25:57",
"operations": [
["comment", {
"parent_author": "primus",
"parent_permlink": "re-tolerant-partnerskaya-programma-20170203t111946105z",
"author": "tolerant",
"permlink": "re-primus-re-tolerant-partnerskaya-programma-20170203t112543813z",
"title": "",
"body": "В создании реферальной программы
должно быть заинтересовано руководство проекта.
Точно так же, как за рекламу продукта платит хозяин этого продукта.
То есть, руководство должно быть заинтересовано в рефералке,
соответственно, оно должно и
выделить какие-то средства для этого.",
"json_metadata": "{\"tags\":[\"ru--golos\"]}"
}]
],
"extensions": [],
"signatures": ["ff0ae"]
}]
}



Мы видим, что ключевые данные комментария расположены в объекте `"operations":`, тот в свою очередь находится в `"transactions"`, стало быть, нам нужно отфильтровать данные до транзакции > операции > результат 

Для этого в нашей переменной `blockdata`, которая выводит данные в консоль и на страницу мы вводим уточнения:
`data.result.transactions[0].operations[0][1]`
Цифры в скобках указывают на конкретные части массива данных, которые нужно выбрать. При этом `0` - это уже первое значение, а `1` - второе, `2` - третье и т.д. Объекты можно указывать и именами, но не всегда они однозначны, потому и дается возможность выбирать позициями.
Например: 
`"transactions": [{ "operations": [комментарий,{текст:"привет!"} ] }]`
Если мы укажем в переменной data.result.transactions[0]
Получим: 
`[{ "operations": [комментарий,{текст:"привет!"} ]}]`
А если 
`data.result.transactions[0].operations[0][1]`
То ответ будет:
`[ [комментарий,{текст:"привет!"}]`
 Это уже почти то, что нам нужно и выглядит почти как надо
voters.png
Но все еще не по человечески, хотя подсматривать уже можно :)
Неудобство в том, что в объекте операций мы встретим много разных данных, где апвоты самые простые. Остальные же выглядят хаотично и смешано, читать их в чистом виде с такой частотой генерации сложно:
more.png
Тут у нас и майнеры в фоне, и всякие транзакции mention бота, биды на бирже, бардак аккаунтов с БМ. Логично разобраться в сути каждой операции, отфильтровать и разложить все по типу действия пользователя. В силу того, что это самая интересная часть с точки зрения чтения событий из API - то писать об этом в конце такого объемного поста не буду, а размещу в следующей части урока.

А пока вы можете сами попробовать поковыряться в страничке.
Содержимое страницы тут http://pastebin.com/raw/4qNZxufB
Можете сохранить как html и пользоваться, редактировать в блокноте и т.д.
Весь javascript на ней сейчас такой:
JS

## В следующем посте
* Наконец-то разбираем и фильтруем все операции пользователей
* Вычисляем силу голоса
* Добавляем настройки 

Ссылки
Остальные уроки /@vik
Страница которую мы разбираем https://vikx.tumblr.com/
Предыдущий пост Подключаемся к ноде wss://ws.golos.io через блокнот. API голоса. 

Comments 0


это было очень интересно, чтобы поделиться, спасибо @vik

03.02.2017 13:24
0

Привет!

Этот пост был выбран Академией Голоса и попал в список программы поддержки качественных образовательных постов.
Ссылка на твой пост будет опубликована в отчете Академии.

Спасибо за полезный контент (ノ◕ヮ◕)ノ*:・゚✧

03.02.2017 13:46
0