Pull to refresh

Comments 20

Lazy-load штука хорошая. Но я бы вам рекомендовал посмотреть на асинхронные задачи, все-таки в самой ноде обычно проводится на порядок меньше времени, чем в libuv.
К тому же клиент обычно не сразу инициализируется, а времени на загрузку тратится совсем немного.

У меня сейчас примерно так работает одна система, которая весьма бодро аггрегирует данные из соцсетей.

//noinspection JSUnresolvedVariable
var remaining_thread_count = Number(process.env.THREAD_COUNT) || 150;

export async function init() {
    ...миграции БД...
    while (remaining_thread_count--)
    /*important! not an async call, that's an fibering*/
        start_thread();
}

async function start_thread() {
    await (await getNextTask());
    start_thread();
}
await (await getNextTask());

оО
await ожидает промис, а промис не может зарезолвить промис, таким образом что делает этот код?
Интересно. Не является ли это специфичной для async/await «фичей», или багом regenerator?
function test() {
  return new Promise((resolve) => {
    resolve(Promise.resolve(5));
  });
}

test().then((val) => console.log(val)) // 5
таки нет, вы не правы.
Этот код вполне рабочий:
(async function start_thread() {
  const result = await getNextTask();
  console.log(`got ${result} from promise`);
})();

async function getNextTask(){
  return new Promise(resolve => resolve(5))
}

// этот код эквивалентен
log(await (await foo()));
// этому
foo().then(r => r).then(r => log(r));

И да, все async-функции возвращают промис, как результат работы, by design.
я имел ввиду, что await getPromise() способен асинхронно вернуть промис.
То, что с точки зрения архитектуры они все построены поверх промисов — я знаю.
Так зачем вы используете конструкцию await (await ...);?
По началу, я тоже использовал одновременное асинхронное выполнение кода в несколько потоков. Но, код выполняется в одном процессе, соответственно, на одном ядре CPU. Для использования максимума ресурсов обычно используется оба подхода: код работает в несколько процессов, каждый процесс из которых выполняет код в несколько потоков.

Повышать количество потоков в каждом процессе может быть выгодно для экономии оперативной памяти, т.к. модули для второго и последующих потоков уже загруженны. Главное — не переборщить, во избежание зависания процессов. Но использование нескольких процессов тоже неизбежно.
Кроме того, процессы node могут иметь такие состояния, когда их необходимо завершить или перезапустить:
  1. idle: процесс использует CPU на 0% продолжительное время. Тут может быть два варианта: если процесс выполнял какую-то работу, скорее всего процесс по какой-то причине потерял callback или что-то в этом роде. Такой процесс необходимо перезагрузить. Если данный процесс реализует, например, http-сервер с каким-нибудь малопосещаемым сайтом, то скорее всего такое состояние обусловленно тем, что на сайт никто давно не заходил. Такой процесс можно завершить, а при запросе на сайт, снова по быстрому поднять http-сервер.
  2. CPU-limit: процесс использует CPU на 100% (или свой ограниченный максимум) продолжительное время. Такой процесс скорее всего зациклился или попал на невыполнимый участок задачи. Такой процесс можно перезагрузить, чтобы сам не застревал на одной задаче и не мешал другим конкурирующим процессам.
  3. RAM-limit: процесс использует максимум выделенной для него памяти. Не секрет, что node-процессы по ходу своего выполнения текут. Совсем ожиревшие процессы тоже можно перезагружать. Это напоминает борьбу с утечками методом PHP. — То есть, зачем удалять ненужные переменные?! — все переменные удаляются вместе с завершением процесса.
Без Object.defineProperty эта задача прекрасно решалась, просто библиотека вместо хеша конструкторов должна возвращать функцию с одним параметром — именем провайдера. Это можно назвать фабрикой конструкторов. Фабрика делает нужный require и выглядит это гораздо лаконичнее.

Реализация модуля storage:
module.exports = function(providerName) {
  return require('./'+providerName+'-storage.jsx');
};

Вместо этого:
var storage = require('storage');
var fsStorage = new storage.FsStorage();

Загрузка модуля с провайдером выглядит так:
var storageConstructor = require('storage')('fs'),
    fsStorage = new storageConstructor();
Вы правы, можно обойтись и без Object.defineProperty().
Правда, я не использую в проектах фабрики из-за ненаглядности структуры кода, например, для IDE.

Так же мы используем общую документацию по всем проектам, пакетам, модулям. Для представления списка всех классов в древовидной форме, нам приходится лишь помогать jsdoc лишь указывая что есть @constructor, а что @namespace. Использование фабрик требовало бы модернизации сборщика/генератора документации.
А что, jsdoc хорошо понимает добавление геттеров через Object.defineProperty?
Нет, как раз за это отвечает метки @constructor, @namespace и остальные.

Например, для обозначения namespace я добавлю в index.js:

/** @namespace storage */


А для класса, например:
/**
  * @name storage.FsStorage
  * @extends storage.BaseStorage 
  * @constructor 
  */


Классы я обычно определяю олдскул-способом Function().prototype.

Так, jsdoc шарится по всем файлам исходных кодов, а IDE тоже прекрасно понимает такие комментарии, структура кода всегда под рукой. Код готов к любому рефакторингу.
Вообще, меня jsdoc и IDE (все) очень огорчают. JavaScript не мыслим без примесей, замыканий и фабрик, а я вообще не могу жить без динамических примесей в прототипы. Они позволяют мне писать в 10 раз меньше кода. Пока не удалось найти систему комментирования исходников, генерации доков, или даже систему автодополнения кода для IDE, которая бы все это могла адекватно отпарсить и уразуметь. Ее конечно можно написать, но что-то подсказывает мне, что она будет очень. Обратное решение, когда все нужно декларировать явно, потребует много писанины в комментариях. Так что, единственный приличный способ сделать это, по моему мнению, это чтобы IDE подключались через дебагер к процессам и показывали содержимое прямо из памяти.
Я не понимаю вашего негодования.

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

/**
 * @name storage.getStorage
 * @returns {storage.FsStorage|storage.MysqlStorage|storage.MongoStorage}
 */


Замыкания к процессу документирования вообще никакой гранью не могу отнести.

По поводу динамических примесей в прототипы. На Node.JS это реализованно util.inherits().
А в jsdoc множественное наследование можно описать:

/**
 * @name storage.MysqlStorage
 * @extends {storage.BaseStorage|storage.SqlStorage}
 * @constructor 
 */
Где негодование? Нет признаков негодования.

Зачем нужна возможность именования классов по жесткой ссылке?
Замыкания относятся к документированию потому, что это не только способ написания прикладного кода, а еще и способ непрямого наследования. При использовании замыканий наследуются и переопределяются не классы и прототипы, а функции. Например каррирование — это функциональное наследование через замыкания. Например:
function curry(x) {
    return function(y) {
        return x + y;
    }
}

Можем теперь делать: или сразу var res = curry(4)(5); или получать унаследованную функцию var fn = curry(4); и потом var var res = fn(5); С первого взгляда, может быть непонятно, зачем наследовать функции, но таким способом можно реализовывать метапрограммирование (порождающее программирование), см. примеры: habrahabr.ru/post/227753 Там замыкания повсеместно для порождения модификаций используются, и как это скормить в jsdoc? Это же все в рантайме создается.

Теперь про util.inherits(); Это способ классового наследования. Это не имеет ни какого отношения к примесям в прототипы. Примеси в прототипы нужны когда мы хотим, опять же, динамически создать прототип для массового порождения объектов. Например, имеем Client.prototype, и заранее мы не знаем, будет включен или отключен модуль безопасности. Когда он включен, то делается require('security')(Client); И уже сам модуль безопасности примешивает к Client.prototype свои методы, например: Client.prototype.signIn(login, password); и перекрывает своими методами часть базовых, Client же не знал до этого что есть методы модуля безопасности, и он должен так расширить Client, чтобы новые методы объявить и вызывать их из уже имеющихся.
По части curr вы правы. Для корректного понимания IDE можно добавить в комментарий @returns {function}. Ну, а как пользоваться сей конструкцией можно разъяснить в свободной форме в формате markdown в том же комментарии.

По поводу примисей и наследования я понял, что Вы хотели донести. Но все это можно реализовать и с util.inherits(). Разница лишь в том, что util.inherits(Client, Security) оставит методы и свойства Client, если таковые имеются в Security.

То есть, например, есть классы Security и Client. Если реализовать так, то методы и свойства Security будут поверх Client:

var SecurityClient = mixClass(Client, Security);

function mixClass(cl, mx) {
    var resClass = function(){};
    util.inherits(resClass, mx);
    util.inherits(resClass, cl);
    return resClass;
}


Как оформить сие добро в jsdoc не могу уверенно предложить. Но, могу с уверенностью сказать, может примиси и удобный для web инструмент (в браузере много стандартных классов, в которых можно расширять функционал: HTMLDocument, XMLHttpRequest), но в Node.JS я бы уверенно обходился бы без них, с базовым одноуровневым наследованием.

Так или иначе, все зависит от конкретной задачи.
Заметил еще косяк, что потерял конструктор. Правильнее было бы так:

var SecurityClient = mixClass(Client, Security);

function mixClass(cl, mx) {
    var resClass = cl.prototype.constructor;
    util.inherits(resClass, mx);
    util.inherits(resClass, cl);
    return resClass;
}
Даже возвращать новый класс необязательно, можно вкорячить прототип и в старый класс.

mixClass(Client, Security);

function mixClass(cl, mx) {
    var resClass = cl.prototype.constructor;
    util.inherits(resClass, mx);
    util.inherits(resClass, cl);
    cl.prototype = Object.create(resClass.prototype);
}
Не использовать фабрики в js очень большие жертвы. Кстати, можно даже сделать фабрику фабрик, таким образом экранировать класс провайдера от модификации и избавиться от new.
Sign up to leave a comment.

Articles