misc/class
lib/jquery_pnotify, lib/moment, lib/lodash, misc/notification, site/engine, misc/social
if( $.browser.msie && $.browser.version <= 8 ) include('lib/respond'); $._social.__cfg = {"init":[{"service":"basic"},{"fb_app_id":"1997094873850041","service":"fb"},{"vk_app_id":"2978320","service":"vk"},{"service":"twi"}],"like":[{"service":"fb"},{"service":"vk"},{"via":"","channel":"","hash_tag":"","service":"twi"}]}; window._SiteEngine = new classes.SiteEngine( { user_id: 0, controller: 'content_tape', action: 'tag', content_css_version: '1459538664', social_enabled: 0} );

Faiwer

Блог web-программиста

Поиск по метке: JavaScript

Рецепт пуле-непробиваемого toJSON

5 апреля 2016

Многие разработчики не раз за свою практику сталкивались с тем, что стандартная возможность упаковать объект в JSON падает с ошибкой, или, что хуже, уходит в вечный цикл. Причина тому цикличные ссылки. Пример:

const a = {};
const b = { a };
a.b = b;
JSON.stringify(a);

Получаем закономерный Uncaught TypeError: Converting circular structure to JSON. Но, что если вам, к примеру, для нужд debug-а, потребовался полунепробиваемый метод toJSON?

Хорошая новость! Мы можем его написать самостоятельно. Причём, благодаря наличию таких классов, как Map или Set, без особых ухищрений и извращений. Принцип действия алгоритма достаточно прост: всякий раз, когда мы собираемся упаковать очередной объект (а только они передаются по ссылке, и приводят к цикличности), мы должны проверить, а не упаковывали ли мы его ранее. А т.к. Set и Map в качестве ключа в Map и значения в Set понимают всё, что угодно, включая объекты, не приводя их к строковому виду, то и переложить эту работёнку можно и нужно именно на них.

Circular

Т.е. где-то внутри нашего toJSON будет:

if(map.has(obj))
  return `$circular:${map.get(obj)}`;
map.set(obj, path);

Где path это путь к текущему итерируемому элементу. Таким образом, столкнувшись с объектом повторно, мы, вместо этого объекта, упакуем строку вида $circular:a.b.c. Что будет очень удобным подспорьем при разборе сложных багов.

Element-ы

Не будет лишним проверять — является ли объект DOMElement-ом, Document-ом или чем-нибудь подобным, т.к. эти сущности не просты внутри, но при этом, едва ли представляют для вас интерес:

if(
    obj instanceof RegExp ||
    obj instanceof Date ||
    obj instanceof Element ||
    obj instanceof Document
  )
  return obj.toString();

Получим строку вида [object HTMLDivElement]

jQuery

В случае, если вы используете jQuery, то стоит все порождённые ею jQuery-массивы превратить в обычные:

if(obj instanceof $) // jQuery-nodes
    return _.map(obj, (v, idx) => { /* toJS */ });

В моём случае типовым результатом будет — ["[object HTMLDivElement]", "[object HTMLDivElement]"]. Но можно и усложнить схему, конвертируя DOM-ноды в их CSS-path (например: #section.cls[attr="value"]).

NaN & Null

Скорее всего, внутри своего toJSON вы будете использовать typeof obj === 'object'. Напоминаю о засаде — typeof null === 'object'; Не забудьте это учесть.

Про NaN — JSON.stringify(NaN) === "null". Не самый лучший вариант, не правда ли?

if(_.isNaN(obj))
    return '$NaN$';

Дело в том, что JSON не умеет никаких NaN. Но для нужд дебага такие моменты могут быть критичными. Тоже самое и для undefined

Загрузка больших текстовых файлов из JS-окружения

5 апреля 2016

Текстовый (и не только) файл можно слепить на ходу, используя client-side JS, и отдать юзеру на загрузку. Обычно для этого используют что-то вроде:

var $a = $('<a/>',
	{
		'href': 'data:' + mime + ';charset=utf-8,' + encodeURIComponent(data),
		'download': _.isString(fname) ? fname : 'file.txt'
	})
	.appendTo(document.body);
$a[0].click();
$a.remove();

Т.е. формируется dataURI с указанием кодировки. А её содержимое экранируется за счёт encodeURI.  В случае, если файл превышает некий предел, то начинаются проблемы в Chrome, который не позволяет скачать содержимое ругаясь на ошибку сети. Полагаю, что сбоит или encodeURIComponent, или же длина аттрибута href превышает некую норму. Firefox такими проблемами не страдает — 100+ MiB грузит, в то время как Chrome спотыкается уже на 15 MiB.

Можно попробовать паковать содержимое как base64, оставив суть подхода (dataURI + a[href]) неизменной, а можно воспользоваться Blob-ом.

Blob

Упаковываем строку в Blob-объект:

new Blob([str], { encoding: 'UTF-8', type: mime });

Получаем псевдо-ссылку на этот Blob-ресурс:

const link = (window.URL || window.webkitURL).createObjectURL(blob);

Ссылка будет начинаться с blob://. Подставляем её в [href] и дело в шляпе.

В случае, если загруженный файл содержит какую-то чушь, сбита кодировка, или же вам не удалось получить Blob-ресурс из строки, попробуйте воспользоваться ручной конвертацией в Blob, с использованием обычных или типизированных массивов. Примеры кода легко найти в сети. Да прибудет с вами сила!

Broadcast в PM2

4 апреля 2016

PM2 — это обёртка вокруг node-cluster для запуска nodeJS приложения, в виде нескольких instance's, каждый из которых представлен отдельным процессом. При этом, в стандартном применении, в качестве master-а выступает внутренний скрипт пакета pm2, а в качестве worker-ов ваше приложение. Они всё так же могут слушать один и тот же порт. PM2 (а может и не он) сам будет распределять запросы между worker-ами.

Рано или поздно может возникнуть необходимость коммуникации между worker-ами. Как её осуществить? Изначально worker-ы друг о друге ничего не знают, но если подключить require('pm2'), то через полученное API можно добиться многого. Нормальной документации к этому API, похоже не существует. Но большая часть доступных методов общая с CLI API.

Для начала необходимо подключиться к pm2 из worker-а:

const pm2 = require('pm2');
pm.conect(err =>
  {
    // с этого момента можно использовать API
  });

Для получения всех запущенных pm2-процессов (кроме master-а) можно воспользоваться pm2.list(). Он вернёт вам ВСЕ запущенные им процессы, включая те, что никак не связаны с вашим приложением. Включая текущий instance тоже. Возвращённая пачка данных изобилует подробностями. 3-10 KiB всякой ерунды на каждый worker.

Отфильтровать оттуда текущий instance можно сравнив pmID текущего процесса (process.env.pm_id), с pmID каждого worker-а из списка. Для того чтобы выфильтровать лишние процессы, не связанные с вашим приложением, можно воспользоваться name-ом (process.env.pm_id). Это значение name из json-pm2-профиля вашего приложения.

Метод list несколько избыточен, мягко говоря. Можно получить только pmID-ки worker-ов по указанному name-у. Для этого есть pm2.getProcessIdByName(name). Останется только выфильтровать из него pmID текущего процесса.

А для того, чтобы разослать каждому из worker-ов сообщение с данными, есть метод pm2.sendDataToProcessId(id, packet). Где packet должен быть объектом с ключами: topic (строка с ключом сообщения) и data (сами данные).

Получать сообщения можно как штатными методами pm2, через launchBus (что у меня сделать не получилось), так и штатным process.on('message', cb) от nodejs.

В итоге простой broadcast можно организовать примерно так:

// при инициализации приложения
pm.connect(err => {});

// отправка сообщения
function broadcast(topic, data)
{
	pm.getProcessIdByName(process.env.name, (err, ids) =>
	{
		if(err) {}
		else ids
			.filter(i => i != process.env.pm_id)
			.forEach(id =>
			{
				pm2.sendDataToProcessId(id, { topic, data }, err => {});
			});
	});
}

// приём
process.on('message', msg => { /* msg */ });

Ложка дёгтя

На getProcessIdByName на моей машине уходит от 6ms до 15ms. Т.е. очень очень много. Код внутри по любому поводу формирует и гоняет между процессами груду данных. Используются теже механизмы, что и задействуются для pm2 monit. Даже если запрашивался только ID

Почему нет никаких стандартных и удобных механизмом для коммуникации между процессами мне не ясно. Причём ни в pm2, ни в nodeJS. С nodeJS впринципе всё ясно, ведь использование node-cluster предполагает, что master всему голова и сам всё порешает. Но в случае pm2 ситуация несколько иная.

V8 & unicode RegExp-ы

21 марта 2016

Не так давно в V8 движок добавили поддержку флага /u (т.е. поддержку unicode) для регулярных выражений. В nodeJS можно подключить флагом --harmony_unicode_regexps. Вот небольшой обзор по новым возможностям. При установленном флаге . понимает иероглифы, их же стало можно использовать в описании регулярного выражения без экранирования, появилась поддержка i флага.

Но! Они недобавили самого вкусного ― \p{L}. При помощи модификатора \p можно кратко описать многие вещи (см. раздел Unicode Categories). К примеру можно описать в пару символом регулярку, которая будет проверять принадлежность символа к алфавиту какого-либо языка. Например, /^\p{L}[\p{L}\d-_ ]+$/u отлично подойдёт для валидации имени пользователя, не поставив в незавидное положение ни грузин, ни китайцев, ни русских. Но отлично отфильтрует рандомный бред с клавиатуры.

Надеюсь, что это временно. А пока можно воспользоваться, к примеру, XRegExp-ом.

Об IE9 замолвите слово

20 марта 2016

Минутка гнева… Internet Explorer 9 не умеет:

  • Placeholder
  • FormDataFileReaderBlobUint8Array
  • Flexbox

Помимо этого обладает ужаасным набором инструментов, постоянно дохнет и очень медленно работает. В общем, этот браузер не так уж и далеко ушёл от своего 8-го собрата.