Pull to refresh
442
0
Mikhail Davydov @azproduction

Frontend/Node.js JavaScript

Send message
Хорошее обсуждение получилось. Мне в свою очередь важен формат CommonJS. Этот «хак» c DOMContentLoaded как правило нужен перед стартом(ну в самом деле не вешаеть же его в зависимости каждому модулю). Ну и 1 «хакнутый» модуль это не «хак» а особеннсоть ;-) И, кстати, многие модульные системы «чинят» DOMContentLoaded.
Через тэг ты получишь только заглушку, у которой нужно дождаться еще ready.
Действуем по аналогии с DOMContentLoaded — см мой пример в ready.js

О какой блокировке основного потока идет речь при «асинхронности» модулей?
Это если бы я синхронно грузил модули с сервера
То есть, опять вводя знания в свой код, что он неработоспособен, пока кто-то где-то как-то снаружи не обеспечит это.
Пример с внешним api это исключение, которое чинится 1 «асинхронным» модулем. Притом, что эту зависимость мы можем представить как синхронно (тег скрипт) так и асинхронно и вызов require('ymaps') в обоих случаях отработает одинакого. Зависимость модуля обеспечивается либо кодом, либо конфигом, либо явной зависимостью(тег скрипт).
Почему же для модульной системы эти различия должны играть такую роль?
В «асинхронности» для модулей нет смысла потому как 95% модулей могут быть получены без видимой блокировки основного потока (они загружаются при старте приложения). А для тех 5% модулей, которые мы должны догружать мы можем сделать обвязку, которая примит на себя всю асинхронноть(за счет теж же промисов) и обеспечит точечый контроль ошибок дозагрузки модуля. Я устал писать обертки над модулями(AMD-стиль), которые никогда не будут использоваться по назначению, поэтому я ввожу это различие. И хочу использовать весь потенциал динамических require без регулярок и прочей магии.
Асинхронно загрузить сторонний код перед стартом приложения или его части, а потом унаследовать синхронно. В этом случае сторонний код будет тем самым редким случаем о котором я писал выше.
Я перешел с асинронных модулей на синхронные, поняв тот факт, что в продакшене все модули так или иначе грузятся синхронно (упаковыны в 1 файл), а вся эта обвязка с define — это лишняя писанина.

Если говорить о собственном коде(не сторонних апи), то асинхронно догружаютя только крупные части приложения (бандлы): несколько модулей, упаковынных в 1 файл и такая дозагрузка в моем коде встречается крайне редко и только тогда я превращаю мой синхонный модуль в асинхронный, притом, что пришедший бандл может сам себя запустить и избавить меня от писанины:
// Активный по своей конфигурации
require.bundle('userSettings'); // сам себя запустит и будет использовать как модули из основой части так и свои

// Пассивный по своей конфигурации
require.bundle('bunchOfViews').then(function () {
    require('viewFromBunchOfViews').doStuff();
});

Я так же использую я.карты, но в максимально синхронном виде. Если говорить об собственном коде и стороннем апи карт, то в твоем случае все модули синхронны кроме ymaps и без ymaps ни один модуль не запустится. И тк загрузка апи асинхронна и без этого апи запуск приложения невозможен я выношу всю асинхронность в один модуль, позволяя остальным модулям быть синхронными:
// ready.js
var lang = require('lang'),
    config = require('config'),
    ymapsApi = config.ymapsApi.replace('%lang', lang);

var maps = $.getScript(ymapsApi).pipe(function () {
    var dfd = $.Deferred();
    window.ymaps.ready(dfd.resolve);
    return dfd.promise();
});

var ready = $.Deferred();
$(ready.resolve);

module.exports = $.when(maps, ready.promise());

// index.js
require('ready').pipe(function () {
    require('mapView')('#map');
});

// views/map.js
var ymaps = require('ymaps'); // из глобалов
// ...
Пока ходил, понял, что в LMD это можно сделать лучше без лишней обвязки с синхрнно-асинхронном конфигом:
// .lmd.js
{
    ...,
    "modules": {
        "config": "@http://site.com/user/config",
        ...
    },
    "async": true,
    "shortcuts": true,
    "promise": true
}

// index.js
// грузим асинхронный модуль, который резолвится по ссылке на http://site.com/user/config
// и после загрузки будет доступен синхронно под именем "config" и "http://site.com/user/config"
require.async('config').then(function (config) {
    require('main');
}, require('epicFailHandler'));


// main.js
var config = require('config');

exports.getPrice = function () {
    console.log(config.currency);
};
Понимаю твою идею. В случае Model приложение не должно стартовать пока не произошел sync. Но я бы старался сделать конфиг пользователя доступным синхронно и это более чем реально сделать.

Ну и на крайний случай вот так:
// config.js
var request = require('request'),
    _ = require('underscore');

var promise = request.get('/cfg.json').then(function (data) {
    _.extends(exports, data);
});

exports.then = promise.then.bind(promise);

// index.js
require('config').then(function () {
    require('main');
}, require('epicFailHandler'));

// main.js
var config = require('config');

exports.getPrice = function () {
    console.log(config.currency);
};
Стоит определить, что есть модуль, а что есть данные. Ошибки данных мы можем контролировать, а исключения модулей мы не можем контролировать (если модуль делает throw при инциализации то все — конец) даже если обработать исключение, то толку не будет и приложение от этого не заработает.
А вот если модуль ходит за данными и делает асинхронный provide(), то тогда ошибки данных становятся ошибками модуля и мы теряем над ними контроль.
В твоем случае настройки пользователя — это обязательные данные без которых ничего не получится. Но это данные, а не модуль, а значит мы можем повлиять на их доступность.

Лучше если эти данные будут доступны при старте приложения (будут отрендерены в html). За этими данными нам в любом случае придется сходить и лучше если мы обратимся к ним без лишнего запроса на сервер (приложение запустится быстрее и не будет проблем с сетевыми ошибками и лагами).

Если это невозможно, то стоит заблокировать работу приложения до того момента пока данные не придут:

var request = require('request');
request.get(config).then(run, epicFail);

// и с обработкой ошибки и перезапросом
var Attempt = require('attempt');

new Attempt(function promiseGenerator() {
    return request.get(config);
}, function timeoutGenerator(error, attemptNo) {
    // Если 404 или попыток больше 3 - не повторяем запрос
    if (error.status === 404 || attemptNo > 3) {
        return Infinity;
    }
    var repeatRequestIn = attemptNo * 2000;
    
    return repeatRequestIn;
})
.then(run, epicFail, notifyUser);

timeoutGenerator в свою очередь может быть асинхронным.
Если модуль выбросил исключение — это штатная ситуация. Если моуль пошел за данными и у него ничего не получилось и он выбросил исключение — это не штатная ситуация. Объясню на примере из жизни:

Есть завод(наше приложение) есть поставщик станков(модуль) и поставщик ресурсов(REST API). Если при поставке станков поезд сошел с рельс — это нормально — этот случай прописывался в договоре(документации к модулю) и завод застраховал этот риск (try catch) и сообщил клиентам, что предоставить какие-то ресурсы он не может.

Другое дело если станки поставляются вместе с их ресурсами: поставщик станков тем сымым подчиняет заказчика и задерживает поставку и станков и ресурсов(на время запроса). Если ресурсы не доходят, то поставщик станков говорит, что я не могу поставить станки потому, что ресурсы, которые обрабатывают эти станки не были поставлены. Это звучит по меньшей мере глупо. Завод не может сообщить своим клиентам о прогрессе(не знает о состоянии ресурсов) и более того не может повлиять на поставщика ресурсов (перезапросить).
В LMD все модули (CommonJS) синхронны с возможностью пополнять «абстрактную ФС» модулей.

Я считаю, что нуджно разделять модули и данные. Если наличие модуля мы можем гарантировать, то вот гарантировать наличие данных мы не можем. Достаточно представить ситуацию, когда модуль полез за данными и произошел сетевой сбой и предоставить свой интерфейс он не может.
У модуля есть несколько путей решения этой проблемы:
1) повторять запрос — тем самым он заблокирует модуль-родитель и многократный повтор запроса не гарантирует поступление данных
2) выбросить исключение — тогда неизвестно кто его будет отлавливать или если кто-то как-то отловит(например Promise-way), то как прозрачно повторить попытку инициализации модуля?!
3) сообщить пользователю об ошибке — тогда модуль получает знание о приложении и подчинит себе логику — самый плохой случай
Желание получить «Асинхронный require модулей» я понимаю.

«Асинхронный provide модулей» — не совсем понимаю (те загрузился какой-то модули и он как-то внутри себя понимает, что он должет загрузить один из ресурсов a, b или с и только потом задекларировать себя) это же совсем не явный подход.

А вот «Возможность передекларации/додекларации модуля» совсем не понимаю — это подход же извращает интерфейс модуля. Вот например, разработчик загружает модуль и оказывается так, что какая-то функция модуля не доступна. Какие цели у этого подхода? Горячая замена кода? Зачем нужна додекларация? Хорошо бы пример.

Да, конечно. По сути это менеджер пакетов и сборщик как npm+browserify или bower+bem-tools. Он так же использует CJS и может быть хорошим инструментом для работы с Web Components.
Не помню, что я называл Node.js модули AMD они, безусловно, CommonJS. Ну и, конечно, Node.js сам оборачивает CommonJS модуль контекстом c require, module, exports. И я, думаю, как и Вы, на стороне CommonJS модулей. Возможно, Вас смутил тот факт, что AMD модули можно использовать в Node.js с адаптором node-requirejs.
Кстати TypeScript модули это практически ES6 Modules. Можно использовать «компилятор» TS для трансляции. Модули, к тому же транслируются в ES3. А вот у Traceur Compiler в ES5.
WebStorm 6.0 с включенным ECMAScript Harmony ругается на синтаксис модуля. yield и прочие — ок.
Это можно, ждем поддержку со стороны IDE, да и стандарт пока в черновике. На свой страх и риск, но можно.
Рад, что вам понравилось. Месяц писания статьи не прошел даром :)
Пожалуйста! Я хотел побить статью на 2, но решил опубликовать все-в-одном, чтобы читатель не потерял «контекст» рассказа, изучая «кишки» эмуляторов модульности.
Ну вот давайте не будем придираться и сравнивать ToNumber и parseInt. Когда я пишу + перед строкой, которая представляет из себя упакованное в строку число с плавающей точкой, то я вполне адекватно полагаю, что на выходе получу float, аналогично с целыми. Когда я пишу + перед месевом из цифр и букв (42px), то я естественно ожидаю получить NaN. Это очевидно, естественно и просто — вот эти 3 составляющие являются ключевыми в любом API и ЯП. parseInt же, в свою очередь, имеет далеко не очевидное поведение.

Information

Rating
Does not participate
Location
Berlin, Berlin, Германия
Registered
Activity