Pull to refresh

Comments 178

Есть опыт среднего проекта на node.js в связке с безумной базой Marklogic (NoSQL + RDF/triples). В том числе этот сервер на ноде умеет конвертировать модели в SPARQL-запросы и обратно, т.е. логики там достаточно.
В общем, впечатление довольно положительное, единственно что смутило (и в чем в статье ни слова), это то что можно отстрелить себе ногу и не заметить этого. Например первый месяц у нас не работали транзакции, из-за пары строчек. При этом мы не знали об этом. Т.е. всё работало, но вот совсем не так как мы ожидали. Полез проверять: "ой, а вы в курсе что у нас все запрсоы к бд без транзакций?"
Это пугает.
Шутка про node.JS: "Вы начинаете асинхронно стрелять из асинхронных рук в асинхронные ноги, асинхронно не попадаете и запутываетесь в этой каше." довольно точная — если вы начинаете без четкой системы или представления как все сделать, то "вы начинаете", js позволяет начать, а дальше вы запутываетесь. Это плата за гибкость.

Вопрос про транзакции. Причина, по которой у вас случилась такая ошибка была в JS? В NodeJS? Или всё-таки дело в устройстве библиотеки, её API или чего-нибудь в таком духе?

Дело было в нашей невнимательности и всепрощении js, библиотека тут только при том, что пришлось писать хелпер для транзакций, который и не работал (точнее — открывал/закрывал транзакцию, а запросы к базе делал без указания её ID).
В js всё может работать даже при серьезном системном баге, вместо того чтобы свалиться, и это типа плохо. Соответственно, требования к пониманию, что именно ты делаешь, возрастают.

Чувак, походу, во всем фиксируется на негативных сторонах. Удивительно, что перешел обратно на Python вместо Go/Rust/Ruby/Erlang.
Удивительно, что перешел обратно на Python вместо Go/Rust/Ruby/Erlang.

У человека есть опыт с Python, зачем ему выбирать из Go/Rust/Ruby/Erlang? Мода типо?
Руби хорош, правда под него задачи 1 в 1 с питоном (только реализация отличается). И если знаешь питон, переходить на руби смысла нет.
Типо да, а что, незаметно? Вы видели его претензии к Python? Чувак просто ищет серебряную пулю. Удачи.
UFO just landed and posted this here
Python вместо Go/Rust/Ruby/Erlang.

Смешались в кучу люди, кони…

Гы-гы, в другой вкладке аналогичные доводы против JS, но в пользу ClojureScript — тынц

Человек после прочтения тутора по экспресс считает себя уже находящимся "в теме". И инструмент судя по всему он выбирал не для решения какой-то проблемы, а потому что стильно, модно, молодежно. Для таких целей было бы верно написать какой-нибудь микросервис, как мне кажется, и поиграться хватило бы, и оценить возможность применения в дальнейшем.


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

Асинхронная работа доступна сейчас практически на любых платформах, будь то php\phython\java\langname, достаточно затянуть в проект очередной reactphp\akka. То есть с этой точки зрения js\node ничего исключительного на данный момент времени предложить не смогут.

С другой стороны js\node могут привлекать существенно лучшим дизайном языка в мелочах, чем у того же php: те же map\reduce, методы строк и массивов существенно упрощают жизнь по сравнению с развесистыми циклами и преобразованиями на php.
UFO just landed and posted this here
Им весьма непросто пользоваться по нескольким причинам, первая из которых — отсутствие стрелочных функций (js или java, подойдет любой вариант), вторая — невозможность аккуратно выстраивать цепочки для обработки потока данных.

Наверняка имеются соответствующие библиотеки\инструменты, но все же это не так удобно, по сравнению со стандартными средствами языка.
UFO just landed and posted this here

Я пробовал пользоваться этим. И не только этим. Отчаявшись даже решил написать что-то свое. Ибо после lodash.js мне сильно не хватало возможностей. В итоге забил и снова стал писать foreach-и, for-ы и пр. конструкции для большинства задач, т.к. в PHP не только стрелочных функций нет, не только массивы не имеют объектного синтаксиса, но ещё и использование анонимных функций очень переусложнено (необходимость прокидывать всё используемое через use убивает всякое желание их использовать). Всё таки для работы в около функциональном стиле в языке должны быть для этого удобства.


Честно говоря после ES6-7 я не могу писать на PHP без боли. А глядя на PHP7 и его новшества я понимаю, что эти два, чем то похожих языка, пошли разными дорогами. В PHP, как мне показалось, основной курс идёт на статичную типизацию и всевозможные плюшки из Java-подобных языков. А JavaScript усиленно скачет в функциональную область. Не удивлюсь, если в каком-нибудь ES2017 нас будут ждать нативные производительные immutable-конструкции.

Если не нужно прокидывать переменные, то можно использовать костыльные стрелочные функции (см. например YaLinqo), получается компромисс между краткостью и вменяемостью кода. Если нужны use — только боль и полный синтаксис.


массивы не имеют объектного синтаксиса

Что вы имеете в виду?

Посмотрел YaLinqo… Ну это уже совсем лихо. Интерпретатор в интерпретаторе. По поводу массива: вместо $array->map array_map($array,.

Да нет там никакого интерпретатора, там вызов create_function после лёгкого преобразования строки. Впрочем, есть порт LINQ и с подключением интерпретатора для поддержки генерации запросов к БД (правда только в виде демки для MySQL, автор сдулся).


С функциями array_map и иже традиционная проблема PHP — у каждой функции свой порядок аргументов. Если вкладывать больше двух уровней, то получается невменяемая каша, да и сами функции — каша.

Да нет там никакого интерпретатора

Ну дык просто используется родной. Поверх уже запущенного кода. Подход, конечно, весьма производительный :)


традиционная проблема PHP — у каждой функции свой порядок аргументов

Да даже если привести их к одному виду, без какого-нибудь |> из функциональных языков дело с мёртвой точки тут не сдвинется.

Ну дык просто используется родной.

Есть аналогичная библиотека, где используется неродной, строится синтаксическое дерево, всё такое. Так что почётное звание "интерпретатор в интерпретаторе" скорее заслуживает она. :)


Подход, конечно, весьма производительный :)

Если бы PHP не был рождён умирать, было бы лучше. :) Кэш, всё такое. Если процесс каждый раз помирает — да, не очень приятно. Но я убеждён, что использовать PHP для проектов, где надо считать миллисекунды и экономить на удобствах — не самый рациональный подход, поэтому для меня некоторое просаживание скорости не проблема по определению.

Боюсь, что при малом N (где N размер коллекции), разница в производительности может достигать нескольких порядков. Впрочем, в одном вы правы, в том же JavaScript-е обычно забивают на динамическое создание функций в не высоко-нагруженных местах.

Мне кажется любой язык не может предложить чего-то исключительного, везде есть плюсы/минусы и подходящие/неподходящие области применения.


Например, есть задача с кучей операций ввода-вывода, допустим сложная бизнес-логика завязанная на десятках таблиц, и каждую таблицу нужно чуть ли не по хаотичным правилам читать/писать в зависимости от запросов. Стандартная история вроде. И я бы не стал в данном случае выбирать ноду, если нет каких-то еще более специфичных требований, например, по производительности. Но подобную логику чаще всего больно писать в асинхронном стиле и очень больно поддерживать.


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


Я скорее это имел ввиду: автор выбирал инструмент, не потому что он подходил для решения задачи, а потому что так захотелось. Иначе он бы был готов ко всем проблемам.

Питон щас на 4 месте по популярности в TIOBE Index.

И да лапша из каллбеков размывает код, и в итоге код с колббеками сложно поддерживать, как и в случае заглушек, так и в случае установки брейкпойнтов, Код становится ассинхронным но к сожалению не организованным.

Gavin Vickery понял что лучше будет оверхед с абстракцией, нежели супер мощное сложно поддерживаемое приложение.
Другой вопрос подходит ли принцип MVC Для Ноды и Express.js ?? может потом появится другая методология которая будет включать в себя такое понятие как асинхронность!
UFO just landed and posted this here
Я пришел из мира мобильной разработки. Особенно пользовался MVC паттерном. Благодря знаниям js, попытался написать сервер на nodejs и mongodb.

Ну что, первые дни были просто превосходны. Почему? Потому, что все было просто. Берешь request, получаешь json object, сохраняешь в базу и возращаешь ок. Потом, ежедневно API допиливался и простой API перестал быть простым. Очень большое количество кода. Из-за отсутсвия нормального IDE, приходилось писать JSdoc на каждую строку
/**
* @type {ProductModel}
*/
let product = getProduct(req)


Начал искать Javascript со строгой типизацией. Typescript, FlowType и многие другие. Но проблема была в том, что библиотеки, которым Я пользовался, писались на vanilla-js. Приходилось открывать браузер и читать документацию. Это быстро надоедает.

Потом наткнулся на Spark Java (http://sparkjava.com). Особенно порадовали две вещи:
1. Каждый запрос это новый Thread.
2. Запросы на MongoDB синхронные.

Ну естественно, мой любимый Java.

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

Думаю любой сложный проект надо начинать делать на том инструменте, которым хорошо владеешь ты или команда. Делать что-то большое и сложное на новом, для себя языке, провально. Будет много ошибок и боли.
В данном случае, JSDoc писался только для того, чтобы редактор понимал тип объекта, ну и естественно получить autocomplete и подсказки.
  1. Запросы на MongoDB синхронные.


Это "заслуга" не спарка, а драйверов монги. Они для java есть в 2-х вариантах: блокирующий и неблокирующий. Но для js тоже есть блокирующий драйвер.


Ну и странные слова про структуру проекта. Ведь тот же спарк ничем не поможет в этом (он сам по себе маленький фреймворк, который не заставляет использовать какую-то жёсткую структуру проекта, и без встроенных инструментов для IoC).


Так что если были проблемы в JS, то смена языка/фреймворка далеко не всегда будут панацеей.

Не так страшен черт как его малюют.

В начале достаточно определить Code Style в команде. Это не проблема перед началом договориться как писать код.

Что касается Promise, возможно автор не до конца понял суть их использования. Да, они ломают синхронную логику, к которой привыкли backend девелоперы. Но если грамотно подойти, то промисы отлично работают и код красиво структурируется цепочками вызовов.

Что касается исключений, то опять таки, в промисах вы смело можете их кидать и ниже по цепочке перехватывать. Это действительно удобно.

Что касается NPM, тут согласен — без фанатизма. Стоит брать только то, что действительно необходимо. Сам против того, что бы на каждый чих тянуть что-то из NPM.

Кстати, до этого писал на python (django, flask, tornado). Сейчас уже 2 года пишу исключительно на NodeJS. Пока обратно не тянет.

К хорошему быстро привыкаешь и от него больно отвыкать. Вы правда считаете, что такой код:


Promise.resolve()
.then( () => {
    console.time( 'time' )
    return greeter.say( 'Hello' , user )
} )
.then( () => {
    return greeter.say( 'Bye' , user )
} )
.then( () => {
    console.timeEnd( 'time' )
} )
.catch( error => {
    console.error( error )
    process.exit( 1 )
} )

И такие исчерпывающие стекстрейсы:


TypeError: Cannot read property 'name' of null
    at getConfig.then.config (./user.js:19:22)

Удобнее, чем такой код:


Future.task( () => {
        console.time( 'time' )
        greeter.say( 'Hello' , user )
        greeter.say( 'Bye' , user )
        console.timeEnd( 'time' )
} ).detach()

И такие стектрейсы:


TypeError: Cannot read property 'name' of null
    at Object.module.exports.getName (./user.js:14:23)
    at Object.module.exports.say (./greeter.js:2:41)
    at Future.task.error (./index.js:11:17)
    at ./node_modules/fibers/future.js:467:21

?

А что если в версии с промисами вывести лог таким образом:
    console.error( error.stack )

?
p.s. С нодой не знаком
С использованием генераторов(либо будущего async/await) можно писать код в синхронном стиле. Обычно использую что-то в таком духе:

_asyncCatch(function*(){
     try{
        var a1=yield promise1();
        var a2=yield promise2();
        var a3=yield promise3();
     } 
     catch(ex){
       console.log(ex);
     }
}


У Promise должен быть описан reject.
co( function*() {
    console.time( 'time' )
    yield greeter.say( 'Hello' , user )
    yield greeter.say( 'Bye' , user )
    console.timeEnd( 'time')
} ).catch( error => {
    console.error( error )
    process.exit( 1 )
} )

ypeError: Cannot read property 'name' of null
    at Object.<anonymous> (./user.js:18:33)
    at next (native)
    at onFulfilled (./node_modules/co/index.js:65:19)

Остальные варианты тут: https://github.com/nin-jin/async-js

Вот выдернул кусочек кода

        return validator
            .validate()
            .then(value => Model.findById(data.get('id')))
            .then(model => {
                model.password = hash(data.get('password'), settings.salt);

                return model.save();
            });


Я бы не сказал, что код нечитабельный.

Для сравнения:


validator.validate()
var model = Model.findById( data.get( 'id' ) )
model.password = hash( data.get( 'password' ) , settings.salt )
return model.save()

А теперь добавим банальный if:


validator.validate()
if( data.get( 'id' ) ) {
    var model = Model.findById( data.get( 'id' ) )
    model.password = hash( data.get( 'password' ) , settings.salt )
} else {
    var model = Model.create({ password : hash( generatePassword() , settings.salt ) })
}
return model.save()

Как вы это реализуете на промисах?

UFO just landed and posted this here
UFO just landed and posted this here
> до логического уровня «что мы делаем» с уровня «как мы это делаем».

А у вас есть чему поучиться, спасибо за отличную метрику качества кода.

Гхм. vintage намекнул, что подход основанный на Promise, Q и пр. будет всё более и более уродливым, с каждым вветвлением. И привёл максимально простой пример на основе выше указанного. А вы зачем-то привели пример того, как можно всё архитектурно переделать, дабы избежать вветвления. А смысл? Пример не самый удачный? Ну и ладно, суть то была как раз в том, чтобы продемонстрировать что Promise-ы не решают проблему читаемости асинхронного кода, а всего лишь её смягчают. А не в том, что всякую задачу можно реорганизовать, в особенности если она простая как 2 пальца.


Это как в споре о том, что Ока не самый лучший автомобиль для перевозки кирпичей, рассуждать о том, что всё не так плохо и можно прицепить к Оке тележку, а кирпичи грузить туда, а лучше купить две Оки и т.п. :)

Вообще в объектной парадигме большое количество ветвлений — частый признак не слишком хорошей декомпозиции задачи и предполагается, что таких мест с обилием ветвлений и тем более асинхронщиной — должно быть исчезающе мало. Вариант с рефакторингом кажется в этом смысле вполне логичным.

Угу, тут всё упирается вот в это: "большое количество ветвлений". Это сколько? Скажем в Promise хватает уже одного уровня, чтобы испоганить всю читаемость вхлам. Довольно грустно. Потому и ждём нативных await, извращаясь с co(* gen).


и тем более асинхронщиной

В nodeJS коде, в моём по крайней мере, НЕ асинхронного кода очень мало. Сама платформа располагает к асинхронности в практически любых задачах

Одно простое ветвление можно аккуратно поместить в тернарник, а все что сложнее — наверное заслуживает располагаться в именованом методе\функции, что позволит так же отдельно тестировать этот участок кода и иметь в нем синхронный вход.

Попробовал представить случай — где потребовались бы размещать относительно сложный код в анонимных фунциях внутри promise — и не смог, при нормальной декомпозиции системы это кажется редким случаем.
UFO just landed and posted this here
код с if вобщем-то с кучей багов, апример var model

Вам показалось.


Без тестов вообще ничего не получится.

При чём тут вообще тесты?


Дальше будет просто:

storePasswordForExistingUserOrCreateNewUserWithDefaultPassword( userId, userPassword ){
   if( userId ){
        return db.updateUserPassword( userId, passwordEncoder.hashPassword( userPassword ))
   } else {
       return db.createNewUser( passwordEncoder.generateDefaultHashedPassword() )
    }
}

Идём дальше. Что вы будете делать, когда passwordEncoder тоже потребуется сделать асинхронным? (Многие криптографические функции, внезапно, асинхронные.)

Можно сделать .then внутри метода и вернуть promise, с точки зрения читабельности кода это ничего не испортит. Очевидно, что никакая декомпозиция полностью не спасет от callback\promise в асинхронном коде, но по крайней мере при должном уровне декомпозиции — они не доставят проблем.

Если это не испортит читабельность, то почему вам лень привести пример такого кода? ;-)

return passwordEncoder
    .hashPassword( userPassword )
    .then(hash => db.updateUserPassword( userId, hash ))

К тому же, hashPassword вероятнее всего будет внутри updateUserPassword. Так же как и в createNewUser.

UFO just landed and posted this here

Я не спрашивал "Зачем нужны тесты?". Я спросил "Какое отношение имеют тесты к обсуждаемому вопросу?"

UFO just landed and posted this here

Это не предмет обсуждения, но раз тема тестов для вас интересней, то давайте затронем и её :-) очень часто разработчики пишут модульные тесты и на этом тестирование и заканчивается. Но куда более важные тесты — приёмочные. А для них не надо как-то по особому проектировать приложение. Если вы пишете переиспользуемый код (например, библиотеку), то приёмочный тест для этой библиотеки будет одновременно и модульным. Если же вы пишете не переиспользуемый код (например, обработчик конкретного запроса пользователя), то от модульного теста тут мало пользы, ведь тут важнее не то, что он правильно работает в изолированном окружении, а то, что он правильно работает в именно вашем окружении.


В целом я склоняюсь к тому, что хороший код — это наиболее простой и понятный код. И вместо того, чтобы его усложнять, чтобы его легче было тестировать, лучше подумать как улучшить тестовый фреймворк, чтобы для тестирования не приходилось усложнять код.


Это не отменяет необходимость грамотного проектирования. Но не модульное тестирование должно быть во главе угла при проектирования всё же.

UFO just landed and posted this here
UFO just landed and posted this here
Давай попробую переписать для if

return validator
    .validate()
    .then(() => {
        return Model.findById(data.get('id'))
            .then(model => {
                if (model) {
                    return model;
                } else {
                    throw new AppError({
                        status: 404,
                        message: gettext('User not found')
                    });
                }
            });
    })
    .then(model => {
        model.password = hash(data.get('password'), settings.salt);

        return model.save();
    });


Как по мне, не так уж и страшно. Но обычно поиск я выношу с саму модель, и проверка на найдено / не найдено уже скрыта. Поэтому в первоначальном варианте я привел без проверки на найденную модель.
  1. Кода получилось в 2 раза больше (и не менее чем в 2 раза больше вероятность допустить ошибку).
  2. Лесенка с 1 уровня разрослась до 6 (а мёржить такие вереницы — одно удовольствие).
  3. Вам потребовалось 3 дополнительных замыкания (они не бесплатны).
  4. У вас реализована совершенно друга логика (исходно проверялось передан ли id и, если нет, то модель создавалась, а не искалась).
По поводу кода, я всего лишь хотел показать, что не так страшны промисы, и с ними можно красиво работать.

Что касаемо кода — то это просто пример, как можно оформить код промисами. И код не превращен в жуткую лапшу.

Боюсь даже представить, что для вас "жуткая лапша", если этот код демонстрирует "красивую работу". :-) Вот объясните мне, зачем усложнять себе и другим жизнь, если можно этого не делать?

Судя по нашей дискуссии, читаемость / не читаемость цепочек — индивидуально.

Если честно, даже аргументировать мне вам нечем. :-) Я просто вижу цепочку и понимаю что происходит. Так же и программеры в моей команде свободно читают и понимают цепочки.

Это мне напоминает слова адептов Пунто Свичера: "Зачем мне ваш десятипальцевый слепой метод? Я и двумя пальцами огого как быстро набираю текст!".

Если человек не может мыслить и представить код в асинхронном стиле, а только в линейном. То это проблема не языка, а человека.

Ну не может он. Тут только одно — выбрать другой язык.

Мы проблем с читаемостью и пониманием кода не имеем. Ваши доводы, что код нечитаем — поверьте, всего лишь только ваши личные доводы.

В принципе не ради ходивара все затевалось. Я только хотел показать в начале, что цепочки не так уж и страшны. А юзать их или что другое — выбор либо человека, либо команды.

Не надо юлить ;) Код на fiber-ах и async-ах объективно проще и понятнее. Так же как и синхронный код куда проще и понятнее, нежели всё остальное. Тут личные доводы вовсе не причём :) Речь же идёт не о табах и пробелах.

Голословное заявление.

Мне код на промисах понятен. Команде код понятен. Скажите, что нам сделать, что бы понять вашу точку зрения?

Ну дык есть некоторая разница между словами "понятен" и "понятнее". Сравните, скажем, код на Promise-ах с синхронной реализацией. Даже не знаю о чём тут спорить.

Боюсь это не излечимо :-) Знаете, вот есть такие индивидуумы (и даже команды в прошлом), которые пишут по несколько операторов в строку. И аргументация у них точно такая же: "Мне же всё понятно! Это вы, убогие, не способны быстро читать неотформатированный код!".


Объективные факты:


  1. В 2 раза больший объём кода в принципе не может считываться также быстро. Как бы вы ни были натренированы.
  2. Использование замысловатых конструкций требует специальной дополнительной подготовки от программиста.
  3. Чтобы не напортачить, требуется особое внимание достаточно высококвалифицированных разработчиков. Типичная ошибка — не залогировать ошибку в конце цепочки.
  4. Асинхронный код сложнее в отладке. Вы получаете кривые стектрейсы. Вы не можете пройтись дебаггером по шагам. Вы не можете посмотреть значения переменных, выше по стеку, так как давно уже из него вышли.

Уверен, вы на столько круты, что можете писать хоть лесенкой, хоть столбиком, хоть цепочечкой. Но 99% людей — не такие. Чем сложнее конструкции, тем больше они допускают ошибок. Так что код, который требует высокой квалификации — это плохой код. А настоящий профессионализм — не в том, чтобы читать и писать заклинания, а в том, чтобы писать так, чтобы в нём мог разобраться даже дилетант. А где разберётся дилетант — там профи и ошибки не допустит.

Еще раз повторю — если избирается подход для написания Promise, то кроме как цепочкой писать не получится. Если не нравятся промисы, есть другие инструменты и подходы.

Повторюсь, я написал цепочку не ради холивара. Не надо доказывать мне, что квадратное круче теплого, или наоборот.

Мир дружба жвачка :-)
В 2 раза больший объём кода, Асинхронный код сложнее в отладке...
В данной ветке речь про сложность кода, и у асинхронного кода она выше, на написание кода тратиться больше времени, на отладку больше времени, выше вероятность ошибки, вместо обдумывания логики приложения вы тратите время на обдумывание как построить асинхронный код, в итоге на завершение потребуется больше времени (2 года вместо одного например) и более дорогие разработчики, а производительность может быть ещё и ниже чем у синхронного кода (сюрприз?).

Поэтому асинхронный код не не нужно использовать везде подряд, а там где он нужен.

Боюсь это не излечимо :-)
Нет, осознание иногда приходит (после года, два, пять), данная статья (и другие подобные) тому подтверждение. Разработчик вам не поверит, ему нужно это пережить.

А зачем увеличивать уровень вложенности и писать then внутри другого then? Одним уровнем не обойтись?

Я еще использую небольшой хак-обертку для генерации promise(внимание, довольно своеобразное решение, также нет обработчика ошибки для случаев отличных от сигнатуры (err, result)=>{}):

Object.defineProperty(Object.prototype, 'getPromise2', {
    value: function (func, args, argCount, argResolve) {
        argCount=argCount||1;
        argResolve=argResolve||0;
        var _this = this;
        var f=func===-1?_this:_this[func];
        if(!Array.isArray(args))
            args = [args];
         
        return new Promise((resolve, reject) => {
            var self=this;
            if(argCount==2){                
                f(...args, function (err, res) {
                    if (err)
                        reject(err);
                        //throw(err);
                    else
                        resolve(res);
                });
            }
            else{                
                f(...args, function () {
                    var result=arguments[argResolve];
                    resolve(result);
                });
            }
        });
    }
});


 
_asyncCatch(function*(){
var html=yield request.getPromise2('get', {
            url: url,
            encoding: 'UTF-8',
            gzip: true
        }, 3, 2, true);
});
console.log(html);


Правда, не во всех случаях такое решение подойдет, но вообще довольно удобно.

Попробуйте fibers — это куда удобней. На худой конец — co, а не стрёмный _asyncCatch.

Свои костыли писать полезно иногда. А за совет — спасибо.
И, если не секрет — в чем стремность _asyncCatch'а заключается, кроме названия функции? Работает он приблизительно так же, как async/await из ES7:

async function testAsync() {
  try {
    let html = await request.getPromise2('get', {
            url: url,
            encoding: 'UTF-8',
            gzip: true
        }, 3, 2, true);
  } catch (err) {
    console.log(err);
  }
}


Вот getPromise2 стремен, тут не поспоришь.

И название стрёмное. И документацию не найти. И чёрт его знает, что там в реализации. Судя по всему это то же, что и "co", но с меньшими возможностями.

Это вообще самопальная функция-переделка в 14 строк из какой-то функции, спертой вообще неведомо где и приспособленная для своих нужд. Назначение — эмуляция async/await, дабы на каждый проект за собой babel или зависимости не таскать.
То, что есть готовые модули типа того же co или async — это круто, но иногда достаточно и самопальных функций.

var _asyncCatch=function(generatorFactory){
    var generator = generatorFactory.apply(this, arguments);
    var handleResult = function(result) {
        if(result.done) return result.value;
        if(result.value.then)
            return result.value.then(function(nextResult) {
                    return handleResult(generator.next(nextResult));
                }, function(error) {
                    generator.throw(error);
                }
            );
    };
    return handleResult(generator.next());
}

Самопальная функция, возвращающая иногда промис, иногда что-то ещё, а иногда кидающая исключение. Что плохого в одной небольшой зависимости? Она всего чуть более 200 строк.

Самопальная функция, возвращающая иногда промис, иногда что-то ещё, а иногда кидающая исключение

Зато сколько азарта и веселья (при дебаге)!

Так и есть)
Что до возврата всего подряд — если внимательно просмотреть листинг функции, то станет понятно, что она проходится по всем елдам внутри generatorFactory, вываливая exception в случае, если в Promise сработает reject.
Как эта хреновина работает — можно посмотреть здесь — https://davidwalsh.name/async-generators либо тут(похоже именно здесь эту функцию я и брал) — http://bhashitparikh.com/2014/06/04/better-jquery-ajax-with-es6-generators.html.
Почему fibers предпочтительней co?
  1. Быстрее.
  2. Не требует оборачивания всех функций в обёртку.
  3. Адекватные стектрейсы.
Выходит, может возникнуть такая ситуация, когда будет не очевидно, что код, который вызывается глубоко внутри нескольких вложенных функций, на самом деле, выполняет что то асинхронно.
В концепции async await или co, поведение всегда очевидно.

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


А "некоторые" — не очень хорошие архитекторы. Проблема, описанная там, наблюдается даже в полностью синхронном коде — ничто не мешает где-то в глубине функции query вызвать колбек, который меняет глобальную переменную requestCount. Так что async-await никак не спасают от необходимости осторожно обращаться с глобальным состоянием.

Глобальная переменная там все таки только для примера и наглядности того, как с fiberами легко создать себе race condition. В полностью синхронном коде race condition не бывает.
Просто fiberы это слишком очень гибкая вещь. Как я понимаю, можно изменить поведение функции, сделав ее асинхронной, при этом никак не изменяя ее вызовы, они будут также работать. Это конечно круто, но, мне кажется, все таки безопаснее, когда в самом вызове функции видно, что вызов этот асинхронный.
В случае с генераторами, как раз придется еще вносить изменения в вызывающие функции, что конечно раздражает, но такой код становится более явным и предсказуемым, а это плюс.
Короче, await в вызове async функции — это совсем не избыточная информация в синтаксисе, он нужен для того, чтобы прочитав код, мое представление о нем соответствовало тому, что он на самом деле делает:) А fiberы создают иллюзии ;)

Любой race condition — следствие неправильной работы с разделяемым ресурсом. Глобальная переменная — частный случай. Да, в синхронном коде она не будет случайной, как в параллельном. Но суть ошибки та же самая — мы прочитали значение, никак его не залочив, потом другой код, которому мы передали управление (вызов функции/обращение к свойству/запуск сигнала/yield), его изменил, а потом мы записали новое значение, затерев чужие. async/await не спасёт вас от такого рода ошибок.


Мы проходили уже то же самое с обработкой ошибок:


  1. Сначала функции возвращали результат или ошибку.
  2. Выяснилось, что любая функция может попасть в исключительную ситуацию и должна вернуть ошибку.
  3. Весь код получался усеял копипастой вида "если функция вернула ошибку, нужно тоже вернуть ошибку. И так после вызова каждой функции.
  4. Потом додумались, что если в 99% случаев обработка ошибки заключается в том, чтобы пробросить её выше по стеку, то логично сделать это поведением по умолчанию.
  5. Пришли к соглашению, что любой код в любом месте может быть прерван исключением и нужно писать свой код имея это ввиду. А для изменения поведения по умолчанию ввели конструкцию try-catch.

Также и с асинхронностью. 90% функций так или иначе могут привести к необходимости произвести асинхронную операцию. Это не какая-то исключительная, а вполне себе типичная операция. А специальный синтаксис имеет смысл вводить лишь для особых ситуаций. Например, когда нам надо, чтобы никто не изменил переменную без нашего ведома:


exclusive( requestCount ) { //никто, кроме кода этого блока не может изменить переменную
    var nextValue = requestCount + 1 // посчитали новое значение, но почему-то сразу его не записали
    // можем вызывать что угодно и даже засыпать
    exclusiveRequestCount = nextCount // установили новое значение
} // освободили глобальную переменную.
Еще одно субъективное замечание: node-fibers — это все таки что-то из области черной магии), если сравнивать его с co (200+ срок на чистом js).
Написан он на С, код с использованием fibers тоже выглядит шаманством) В общем, это не просто js библиотека и это настораживает.
К тому же, fibers уже не станут мэйнстримом, так как node.js отчетливо движется в сторону async await. А наличие камьюнити в наше время решает.

Поэтому и надо популяризовывать правильные решения. К сожалению у JS нет толкового диктатора. А власть толпы ни до чего хорошего никогда не доводит. Чего только стоит цирк с пропертями:


  1. Была простая концепция: всё есть объекты и у объектов есть поля.
  2. Добавили Object.defineProperty и кучу свойств у каждого поля: перечеслимое, изменяемое, функция получения значения, функция установки значения.
  3. Добавили кучу мета методов в Object.prototype.
  4. Перенесли часть из них в Object.
  5. Добавили Symbols. Теперь у каждого объекта есть поля по строковым ключам и по символьным.
  6. Добавили Reflection со всем тем же самым.

Куда движется JS? В сторону PHP? ;-)

Правильное решение это Dart:


1) Все есть объекты, наследование на базе классов с миксинами, генерики
2) Свойства с сеттерами и гетерами )) встроены в язык
3) async/await вместо callback-hell, встроено в язык
4) exceptions/try/catch встроено в язык
5) Вменяемый "диктатор" отвечающий за развитие давно уже зрелого SDK разработанного в едином концептуальном стиле
6) Менеджер пакетов (типа npm)
7) Очень легко научиться.
6) Можно начинать с динамической типизацией и при необходимости вводить постепенно строгую.
7) Тесты
8) @Метатеги
9) Трансформеры
..


И да, Google конечно обязательно забросит/закроет Dart как и множество других проектов… ага


Допилят Flutter (спасибо Oracle с судами про Java) будет совсем весело..


Но мыши плакали, кололись и продолжали увлеченно спорить какими костылями и как сподручнее размахивать чтобы взлетать из болота… лишь бы не выходить из зоны комфорта ))


A JS движется в сторону WASM ;-)

Haxe — нельзя отлаживать без компиляции.
Dart — можно делать полноценную отладку в IDE без компиляции(с возможностью посмотреть значение любой переменной на брейкпоинте просто наведя на нее мышь). В Dartium встроена Dart VM. Изменили код, нажали F5 в Dartium. Разработка быстрее и удобнее.
Серверный код вообще не надо компилировать, он работает в DartVM быстрее и устойчивее чем JS на Node.


И на Haxe не пишут Angular ))
Приложение на AngularDart(которое транслируется в JS) работает быстрее чем AngularJS вариант, что как бы говорит о качестве компиляции(в команде Dart люди которые Chrome разрабатывали). Которой Haxe в силу основного упора на многоплатформенность похвастаться не сможет даже в теории.

  1. Компиляция происходит в любом случае. На сервере или в браузере.
  2. Haxe тоже можно отлаживать из IDE с просмотром значений переменных, брейкпоинтами и прочим.
  3. Возможность отладки в браузере, которым никто не пользуется — так себе фича.
  4. Только что вы кичились трансформерами — они тоже в браузере налету применяются?
  5. Ангуляр, что первый, что второй — редкостная порнография.
  6. Свежо предание. http://qiita.com/laco0416/items/df2e32787b36a01c3d6d
  7. Зато на Haxe можно нативные мобильные приложухи пилить.
  1. "Компиляция происходит в любом случае. На сервере или в браузере." — Обоснуйте пожалуйста. Я утверждаю, что на сервере Dart код выполняется в Dart VM без компиляции.
  2. В случае с компиляцией Haxe в JS, отладку можно будет делать только после компиляции(иначе как то что отлаживается будет взаимодействовать с DOM), так?
  3. Dartium = Chrome-V8+DartVM и корректные полифилы для всех остальных браузеров в SDK присутствуют. Так что фича рабочая.
  4. Трансформеры это про кодогенерацию. Выполняет их pub. Бывают проекты и без нее. Необходимый трансформер это например dart2js — нужен только перед выкладкой, в процессе отладки — не запускается. Или трансформер нужен, например, при работе с Polymer.
  5. Интернет из фо порн ;-)
  6. Предание 22 октября 2014 и что? Сейчас речь идет про Angular2
  7. Flutter is a new project to help developers build high-performance, high-fidelity, mobile apps for iOS and Android from a single codebase.

Про Haxe? расскажите пожалуйста — моб. приложение будет одно и тоже для iOS and Android? Или их можно писать дергая платформозависимые API?

  1. Без компиляции исполняются лишь машинные коды. Остальное либо транслируется, либо интерпретируется.
  2. Разумеется.
  3. Да-да, тут для JS-то не всегда полифил адекватный есть. Взять тот же WebRTC2.
  4. То есть препроцессинг всё равно будет.
  5. В данном случае, это — зоопедонекропорно.
  6. Пруфлинки приветствуются.
  7. Оно компилируется в основной язык целевой платформы с возможностью прозрачно и эффективно использовать все её возможности?
  8. Одна кодовая база транслируется в разные языки. Можно использоваться платформозависимые API на полную катушку.
UFO just landed and posted this here
Щас понабегут проповедники и религиозные фанатики и заминусуют пост, и кучу комметов в довесок.
Да как ты посмел обидеть ноду :D
Простите забыл перелогинится. :D
Да не, так нормально тоже :)

Далее Обращаюсь НЕ к автору предыдущего комментария…

Вообще, конечно, хотел просто ничего не писать, но это, что на пустом месте за кому-то показавшимся неудачным или неуместным юмор можно отхватить — надоело. Оно понятно, что «петросянство», но, что с Вами не так?

God Mode On — вообще не понимаю, что Вам с того, что двое в каментах решили потроллить друг друга?

Вообще, я люблю JS, но я одинаково не понимаю И Хейтеров И Обожателей. И если Вы подумаете хоть немного, то поймёте, что «Привет КЭП» — относится и к тем и к другим. Впрочем как и исходный комментарий про «сейчас набегут». Оба враждующих лагеря могут поразжигать на этой теме :)

Т.е., я не хожу и не рассказываю всем как я Люблю или Не Люблю ноду, JS и всё, что с этим связано. Коллеги, ни в любви ни в ненависти к JS нет ничего прикольного!

ИМХО — пытаться понять JS через парадигмы других ЯП — гиблое дело. Из комментария ниже про 300 мегабайт node_modules — в этом нет ничего плохого. Ничего хорошего тоже нет, но и плохого нет. Это просто так, как есть.

Считайте, что это такой вот DZEN.
Из комментария ниже про 300 мегабайт node_modules — в этом нет ничего плохого. Ничего хорошего тоже нет, но и плохого нет. Это просто так, как есть.

Когда CI на каждый коммит вытаскивает репозиторий и потом накатывает вендоринг, это очень долго и очень много трафика утекает.
На каждый коммит по 300Мб с инета тянуть это плохо.
А как насчёт кеширования node_modules например? Не путь самурая? Если флоу дорос до использования CI то и подходы кеширования использовать не грех. Достала CI тот же package.json, сравнила версии либ, если ничего нового, используем то что в кеше. Да и на каждый commit нет повода её дёргать. Скорее на каждый pull request. Это не так часто как могло бы показаться.

Ну не так страшна nod'a, проблема в том что js надо знать не плохо и как минимум npm пользоваться "каждый день". Вот к примеру автор совсем не знает про knex. И еще десятка 2 полезных пакетов для сервер сайда. Тут трабла в их поиске и адекватной оценке.

Мне интересно, за что минусуют человека? У кого-то плохой опыт был с этой штукой?

Ну это же топик про гнев и какая нода плохая, опять же knex не orm но всем видно насрать :)

Добавлю своей боли. У нас npm используется для разработки и сборки фронта. Так вот фронт написан на ReactJS. Собирается все с помощью gulp и вот в чем боль. Зависимости для работы всего этого (каталог node_modules) занимают 120Мб в проекте. Это с третим npm. Со вторым вообще 800Мб. Извините но это Адъ. 800Мб Карл, просто чтобы собрать фронтэнд. Так для справки кодовая база (js, php) без вендоринга весит 22Мб. Вендор php (42 Мб) и папка с пакетами ноды 120Мб. Wft? Причем там ничего не используется в продакшене из этого каталога. Вообще ни чего. (babel, gulp, react, webpack — вот эти 4 штуки вытягивают 100Мб зависимостей)
Попробуйте устанавливать с флагом production. npm install --production. Просто по умолчанию установки пакетов тянутся зависимости нужные только при разработке этих пакетов, вроде eslint и прочих, но без которых можно обойтись, если использовать просто как пакеты.

Так а в чем боль? Вам место жалко на диске или вы храните node_modules в репозитории.

Я с помощью CI запускаю сборку и тесты на каждый коммит. И каждый раз заново пулю репозиторий и заново все накатываю, чтобы никакие кеши не дали сайд эффектов. Да мне жалко на каждый коммит тащить с инета половину интернета. Конечно в случае разработки без CI и без автоматизированного тестирования проекта все супер. Но х х и в продакшен не наш путь. Каждый раз приходится ждать по 2-5 минут пока нода соизволит выкачать пол мира, а хочется быстрой обратной связи.
Вы что-то делаете не так. У меня тоже тесты на каждый коммит, но node_modules закеширован и обновляется только если изменится packages.json и/или npm-shrinkwrap.json.
Поясняю:
1) Каждый раз при коммите CI делает git clone (в отдельный каталог)
2) Далее переходит в каталог с кодом и устанавливает зависимости
3) После этого производит сборку пректа
4) После этого запускает тесты
5) После этого анализирует результаты тестирования

Теперь вопрос, что я делаю не так?

Угу. Учитывая что node_modules создаёт такие проблемы при таком подходе, а от npm вы отказываться не собираетесь, ну так откажитесь от этой схемы в пользу другой. Как-минимум п.2 можно усложнить и если bower.json, package.json и пр. не менялись с прошлого раза, то и не грузить ничего заново. У нас пока подгрузка зависимостей занимает 8-10 минут. И это не выносимо долго. Но прибегаем к такой подгрузке только если зависимости были изменены.

Не совсем понятно зачем п.2 если node_modules можно положить просто на уровень выше, чем каталог куда делается git clone.

1.5) ln -s ../node_modules node_modules (не говоря про то, что npm может пару вариантов для решения еще предложить, типа линковки пакетов, выбора источника и так далее)
2) npm install


Но если у вас требование все делать на чистую и при этом быстро, то тут не проблема того что пакеты много весят, а что-то еще. Потому что для чистоты можно и ноду каждый раз ставить, со всеми зависимостями, а лучше всю систему в целом.

Docker позволяет легко и просто "ставить систему с нуля". Правда с node_modules — та же беда, но её легко обойти:


  1. Обновляем репозиторий на хостовой системе.
  2. Добавляем в контейнер package.json.
  3. Выполняем в контейнере npm install.
  4. Добавляем в контейнер остальные исходники.

Таким образом, если package.json не менялся, то будет взят снепшот из кеша с установленными node_modules.

Можно просто закинуть путь к node_modules в process.env.NODE_PATH
Посмотрел: так себе, не очень понял накой это читать. Речь же про тестовую среду, так почему нельзя общие node_modules держать для всех билдов и только при изменении package.json их «перестягивать»?
Что, разве build среда не может узнать что package.json изменился, и сходить на уровень выше, скопировать туда package.json и установить там всё через тупо npm install?
Все так, но что если зависимости устанавливать глобально?
Репа скачается, а зависимости будут использоваться уже ранее установленные

Так и не ставьте зависимости глобально. npm прекрасно работает с локальными зависимостями; более того, это рекомендованный подход.

Вебпак позволяет за милисекунды билдить проект и на горячую подменять стили, а при опрелеленных условиях и код, и шаблоны, и все что угодно. То есть можно добиться того, что после нажатия на ctrl+s все применится до того как вы успеете взгляд на монитор с браузером перевести. А Ваши претензии сводятся к медленной обратной связи?


А поповоду выкачивания, чтобы не юзать версии из кеша, во избежание сайд-эффектов, это по-моему на уровне паранойи. Нет, стандарты по сборке и тестированию могут быть разными, но тогда чем обусловленно недоверие именно к системе кеширования в npm, при условии использования 120мб стороннего кода?


Зафиксировать версии, один раз проверить сборку после очередного коммита, при смене версий еще раз проверить, все ли подтянулось, обновилось, либо даже автоматический тест написать. Тоже не ваш путь?

Разверните у себя Sinopia и используйте ее для кэширование NPM пакетов.
Да в том-то и проблема, что этих стандартов как собак нерезаных в квадрате. Airbnb, wikimedia, google, на любой вкус. Точки с запятыми, фигурные скобки и их размещение, отсутствие, наличие и опциональность этих вещей, количество пробелов (или табов), переносы строки — в любом сочетании, оптом и в розницу. ИЧСХ, все неконсистентные и все нелогичные.
Надо все это смержить, пофиксить конфилкты, стандарт готов ) *шутка*

Просто выберите, который ближе по душе и используйте. Для других языков разве везде единый стиль?

Но в этом же и прелесть. Если для человека не достаточно авторитетны стандарты от гугла, или проект входящий в топ 15 по количеству звезд на гитхабе, и он не может сделать свой выбор, или сделать свои гайдлайны с блекджеком и прочими вещами (благо инструментов валом), а предпочитает только от производителя, то это уже идеологические проблемы.

Самый популярный npm пакет для http запросов с названием «request» у меня при очень большом количестве запросов вызывал утечку памяти, Карл! После длительных мучений и поисков причины утечки проблема была обнаружена именно в этом пакете. После замены «request» на аналог (вроде requestify он назывался) проблема исчезла. Причем в описании самого «requestify» тогда было написано что-то вроде: «Мы понимаем на сколько ужасен „request“ именно поэтому создали этот пакет». После этого я очень аккуратно отношусь к node вцелом и выбору npm пакетов.
Вы так говорите, будто в питоне все пакеты кошерные и пишут их академически подкованные, сертифицированные и ВСЕГДА соблюдающие пеп8(а это же стандарт!) люди. Если бы так было, никогда бы не вышел к примеру ConfigParser2. А попробуйте загуглить «python event emitter» и удивитесь сколько пакетов делают одно и то-же с разной степенью кривизны.
А вот взять общеизвестный пакет logger
https://docs.python.org/3/library/logging.html — методы там в camelCase, не совсем по стандарту, но он вроде как не запрещает, но потом ведь и ваш код становится солянкой, где одни методы вызываются так, другие почему-то иначе. А вот если взять тот-же JS так я не могу вспомнить ни одну популярную библиотеку где в последний раз видел, чтобы имена методов были не camelCase. В общем, не надо отбеливать то что белым не является, и очернять то что вовсе не черное, пакеты как и языки написаны людьми и априори не могут быть идеальными.
Если вы захотите написать асинхронный неблокирующий сервер на python, к примеру на twisted или tornado, каким образом вы сможете избежать callback-ов, Deferred-oв или yield-ов?
В twisted нету errback-ов? Все само ловится через try/catch?
На node нельзя писать синхронно? Нельзя кластеризировать? А потоки вам при работе с tornado сильно упростят логику?
Работа с асинхронным кодом имеет один и тот же стандарт в twisted и tornado? А стандарт такой вообще есть?
каким образом вы сможете избежать callback-ов, Deferred-oв или yield-ов? Все само ловится через try/catch?
В питоне есть gevent.
На node нельзя писать синхронно?
Вы имеете ввиду блокирующие вызовы или файберы?
один и тот же стандарт в twisted и tornado?
Для этого есть async.io

А вообще «асинхронщина» нужна в 5% случаев если не меньше (по моей статистике), а питон умеет и так и эдак в отличие от ноды.
https://www.npmjs.com/package/fibers
http://venkateshcm.com/2014/04/Reactor-Pattern-Part-4-Write-Sequential-Non-Blocking-IO-Code-With-Fibers-In-NodeJS/ — о таких файберах речь?

А вообще согласен, с большинством задач и django и flask замечательно справляются и делают это на хорошем уровне. Но не со всеми и не всегда.
Я кстати тоже очень люблю питон, и js люблю и спорить кто из них лучше не хочу, но вечные нападки неосиляторов со статическим ООП(как будто JS отменял ООП или невозможно прибить все типы гвоздями при необходимости, но это уже другая история...) головного мозга(это я не про вас, это я про крайние случаи, которые очень часты в интернетах) в сторону JS слегка расстраивают.
асинхронный комментарий получился )
> но вечные нападки неосиляторов со статическим ООП(как будто JS отменял ООП или невозможно прибить все типы гвоздями при необходимости

Так JS не предоставляет почти никаких инструментов, так характерных для ооп языков. Просто пройдусь по списку:
1) Автоматический рефакторинг — нет или очень слабый (для этого IDE должна хорошо понимать код)
2) Инкапсуляция — private\protected в es6 классах можно реализовать только окольными путями
3) Полиморфизм — интерфейсов нет и единственное что мы можем — это намекнуть на структуру ожидаемого объекта другому программисту в jsdoc
4) DI\IoC — никаких constructor injection, только хардкор
5) Возможность не держать в голове типы всех переменных в области видимости — тоже ожидаемо нет
6) Grasp паттерны:
* отделение асинхронного кода от синхронного (контроллер) — сложно и часто не имеет смысла, потому что асинхронно все
* слабое зацепление — сложно, потому что в большинстве случаев слишком многие подробности объекта доступны извне
* устойчивый к изменениям — нет, потому что у нас отсутствует интерфейс, где мы можем задекларировать api

Вот и получается, что ооп как-бы есть, но одновременно его как-бы нет.
Я не сомневаюсь, что со всем этим можно жить (хотя бы потому что сам долго пишу на js), но с современным пониманием ооп — javascript имплементация стыкуется слабо, эдакая «неизвестная земля» на фоне имплементаций в остальных языках.
Отсутствие интерфейсов не отменяет наличие полиморфизма
каким образом вы сможете избежать callback-ов, Deferred-oв или yield-ов?

Эм… там все делается через Handler…


В twisted нету errback-ов? Все само ловится через try/catch?

За twisted не скажу, tornado кидает ошибки и, если разработчик нигде ее не обработал, выкинет в лог.

Неблокирующий запрос к базе данных внутри Handler-а как будете делать? Взять momoko или motor например, кто будет обрабатывать результат выполнения? Без yield или callback получится?
А вообще тема конечно холиварная и вброс удачный. Для меня лично нет никакой неопределенности какой язык лучше. Писать мне приятно на JS на нем мысль сама вытекает — но это просто дело привычки, код обычно элегантнее, опрятнее и быстрее на питоне(но разумеется не всегда). Оба языка имеют и давно доказали свое право на существование, и у обоих языков рыльце в пушку, в питоне как и в js не все так идеально и радужно как хотелось бы.
Расскажите о своем опыте?… «перескакивать» назад, на более комфортный язык?
Мой опыт такой:
Года 4 назад я подумал о переходе на ноду, в итоге начал тестовый проект на ноде, что-бы пощупать как оно, асинхронность из «коробки», да и скорость v8 выше. Через несколько дней я понял что оно не сильно лучше чем в питоне (благо я наелся калбеков в питоне к этому времени), а самое главное что асинхронщина нужна далеко не всегда (хотя все* сейчас пытаются её использовать для всего подряд).
В итоге я вернулся в питон. И периодический наблюдаю подобные статьи об уходе с ноды.
Я по прежнему активно использую ноду, но только в качестве тулинга.
Что ЭТО вообще делает на главной Хабра?

Ага, всего лишь горстка приличных SQL пакетов. Позднее вы поймете, что все существующие ORM инструменты никуда не годятся и то, что базовый драйвер — это лучший выбор.


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

Вы никогда не станете мастером того, что движется с такой, ломающей голову скоростью.


У меня плохая новость для всех вас. Сегодня становится мастером тот, кто умеет изменяться «с ломающей голову скоростью».

Это способ писать код, который выглядит более-менее синхронно, без сумасшедшей 'callback' логики.


Этот пассаж я даже не знаю как комментировать. А что, callback в промисе чем-то отличается от остальных?

Последней каплей было то, что я обнаружил отсутствие стандартов.


Что, серьёзно, кто-то жалуется на недостаточное количество стандартов в JS?

Это всё накаляет… Никто не может сказать, как написать стандартизированный JavaScript-код. Просто забейте в гугле «JavaScript Coding Standards» и вы поймете, что я имею ввиду.


Вероятно, автор имеет в виду то, что он не понимает разницы между кодстайлом и стандартом.

Это проверенный временем вариант с великолепными стандартами, библиотеками, легкой отладкой и стабильной работой.


А напомните инструмент легкой отладки серверного Питона кто-нибудь.
У меня плохая новость для всех вас. Сегодня становится мастером тот, кто умеет изменяться «с ломающей голову скоростью».

Назовите топ-5 полезных библиотек для node.js, которые недавно вышли. Потом расскажите, как они работают и какие у них сложности.


Суть в том, что серьезных сложных библиотек в других языках программирования не появляется так много, как в случае node.js.


Что, серьёзно, кто-то жалуется на недостаточное количество стандартов в JS?

Когда стандартов много, их нет. Возможно, вы не знали об этом?


Вероятно, автор имеет в виду то, что он не понимает разницы между кодстайлом и стандартом.

Вероятно, автор называет кодстайл стандартом. Он же работает на Python, тут есть pep8.


А напомните инструмент легкой отладки серверного Питона кто-нибудь.

Как насчет pydb? PyCharm Ultimate версии отлично умеет в удаленную отладку, только path mapping настроить.

> Назовите топ-5 полезных библиотек для node.js, которые недавно вышли.

Ммм. Вообще-то весь nodejs недавно вышел. Ну ок, скажем, React, eslint, jsdoc3, gulp, swagger-tools
Алаверды, назовите топ-5 полезных библиотек для питона, которые недавно вышли.

> Суть в том, что серьезных сложных библиотек в других языках программирования не появляется так много, как в случае node.js.

Это проблема nodejs — в нём появляется слишком много серьёзных сложным библиотек? Серьёзно?

> Когда стандартов много, их нет. Возможно, вы не знали об этом?

Имею некоторое представление. Стандарт на ECMAScript как бы один — тот, который TC39 разрабатывает.

> Как насчет pydb? PyCharm Ultimate версии отлично умеет в удаленную отладку, только path mapping настроить.

Сюрприз — WebStorm точно такую же удалённую отладку Nodejs имеет.
Пройдите на оригинал статьи и на понятном английском объясните это все автору статьи, сопровождая это ORLY. Посмотрим че из этого выйдет. Это перевод, не понято кому вы это все доказываете тут. Переводчику?

Хватило бы одной совы, честное слово :)

Интересный пример асинхронщины:


UFO just landed and posted this here
А кстати скажите мне какой плюс nodejs в сравнении скажем с использованием asyncio или tornado на python? Не в производительности и синтаксисе, а именно архитектурно есть разница?
Исключая всякие полезные мелочи, типа возможности застримить тело POST-запроса — основное отличие ноды от async/await фреймворков в питоне/C#/вотэва в том, что цель последних — замаскировать асинхронность; ты пишешь типа синхронный код, вся магия скрыта в самом фреймворке. В ноде это не так. Вся асинхронность явно вынесена наружу, и ты должен писать код с пониманием того, как оно работает.
Вы видимо непонимаете что такое async/await (который, кстати, есть и в яваскрипте).
Про питон не скажу, но и в C# и в js async await просто удобная синтаксическая конструкция. Того же результата можно добиться и «старыми» методами — .then() в js, .ContinueWith() в C#. И в js и в C# необходимо понимать что пишешь асинхронный код (который с async/await просто выглядит чуть удобнее). Ни C# ни js не маскируют асинхронность ни в каком виде (это попросту невозможно), а просто дают инструмент для более удобной работы с ней.

> Вы видимо непонимаете что такое async/await (который, кстати, есть и в яваскрипте).

Ну вы держите меня в курсе.

> Ни C# ни js не маскируют асинхронность ни в каком виде (это попросту невозможно), а просто дают инструмент для более удобной работы с ней.

… причём под «более удобной» работой подразумевается написание «как будто» синхронного кода. Да, именно это я и имею в виду. Это «удобство» вот для тех самых разработчиков, которые боятся всего нового как огня.

В nodejs можно написать, например, так (в нотации vow):

vow.all([
    fetch(url1).timeout(200).then((res) => res.json()),
    doAsyncOperation1().timeout(100).then((res1) => {
        return doAsyncOperation2(res1.url).timeout(100);
    })
]).spread((res, res2) => {
    // do something valuable
});


Т.е. реально контролировать время и порядок исполнения асинхронных операций, в т.ч. свободно отстреливать их по таймаутам. При этом в ноде асинхронно реально ВСЁ.

И это для фронтендовских задач, которые обычно заключаются в построении ответа по куче источников, как бы must have. Писать в async/await стиле — просто не понимать, что за механизм тебе дали в руки.
— Писать в async/await стиле — просто не понимать, что за механизм тебе дали в руки.

Вы издеваетесь? То же самое что и в вашем коде с async/await:

main () {

let promise1 = getUsersAsync({timeout: 200});
let promise2 = getOrganizationsAsync({timeout: 1500});

let [users, organizations] = await Promise.all([promise1, promise2]);

// работаем с users, organizations

}

async getUsersAsync(configuration) {

let r = await fetch(usersUrl, configuration);

return r;

}

async getOrganizationsAsync(configuration) {

let result1 = await fetch(url1, configuration);
let result2 = await processAsync(result1);

return result2;

}
Ни C# ни js не маскируют асинхронность ни в каком виде (это попросту невозможно)

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


module.exports.say = ( greeting , user ) => {
   console.log( greeting + ', ' + user.name + '!' )
}

Казалось бы, зачем ему знать что либо про асинхронность? Но нет, если мы хотим получать имя пользователя в том числе и асинхронно, то нам придётся изменить и этот простой модуль:


module.exports.say = async ( greeting , user ) => {
    console.log( greeting + ', ' + ( await user.name ) + '!' )
}

И теперь, если у нас уже есть имя пользователя, то нужно писать такой вот странный код вызова:


var greeter = requiire( './greeter' )
async () => {
    await greeter.say( 'Hello' , { name : Promise.resolve( 'Anonymous' ) } )
    await greeter.say( 'Bye' , { name : Promise.resolve( 'Anonymous' ) } )
}().catch( error => {
    console.error( error )
    process.exit( 1 )
} 

Вместо более естественного:


var greeter = requiire( './greeter' )
greeter.say( 'Hello' , { name : 'Anonymous' } )
greeter.say( 'Bye' , { name : 'Anonymous' } )

Хорошо хоть require пока синхронный. :-D


async|await, generators, promises — как вирусы, они стремительно распространяются по вашему приложению, заставляя в каждую функцию вносить странные изменения. Стоит одну функцию сделать асинхронной и все вызывающие её функции тут же заразятся этой заразой. И никакая функция, которая вызывает хоть какую-то другую функцию или обращается к полям каких либо объектов, не застрахована от такого "переписывания генома".

— В JS это не просто возможно, но я бы и настоятельно рекомендовал использовать. Приведу пример.
А где пример? Покажите как асинхронный код можно использовать ничего не зная о том что он асинхронный (вроде вызова к серверу, который неизвестно когда выполнится и выполнится ли вообще).

Приведенный вами пример слишком синтетический, поэтому дать какие-то комментарии по нему проблематично. greeter.say — это не асинхронная функция. Вот код который вызывает greeter.say может быть асинхронным (если надо получить пользователя с сервера). А может быть и синхронным (если пользователь уже был получен).

А вообще nodejs пропагандирует асинхронность не просто потому что делать нечего. А для того чтобы не блокировать поток. Если все вызовы будут синхронными тогда на каждый такой вызов будет блокироваться поток выполнения, что значит что на 1 инстансе ноды нельзя будет выполнить более 1 запроса одновременно (все запросы будут выполняться последовательно), а это очевидно никому не нужно.

Вот пример.:


var Future = require( 'fibers/future' );
var Fetch = require( 'fetch-promise' );

module.exports = class Transport {

    constructor({ uri }) {
        this.uri = uri
    }

    fetch() {
        return Future.fromPromise( Fetch( this.uri ) ).wait();
    }

    fetchJSON() {
        return JSON.parse( this.fetch().buf );
    }

    fetchData() {
        return this.fetchJSON().data;
    }
};

И нет, системный поток не будет заблокирован. Заблокирована будет лишь текущая "задача" (например, обработка одного клиентского запроса). И пока она заблокирована, процессор будет заниматься другими задачами (например, другие запросы обрабатывать).

Во многом согласен с тезисами, но продолжаю использовать nodejs дальше как основной инструмент. Это весело. Среда очень быстро изменяется, постоянно узнаешь о новых подходах, пересматриваешь привычки, становишься гибче. Но надо понимать, что nodejs достаточно специфичен и не подходит для многих проектов.
По поводу codestyle есть удобная тулза standardjs.com вначале немного больно, но потом привыкаешь
Статья из формата «я не смог использовать _____ для своего проекта» (в данном случае NodeJS).

Нет JS отладчика?
Почему бы не использовать node-inspector.

Теряетесь в JS callback'ах?
Аналогию можно провести к любой платформе, пишите в соответствии с стандартами (всеже Coding Standards и Coding Style это разные вещи). А еще неплохо использовать eslint для Вашего проекта.

Считаете что стать мастером невозможно?
Слишком пессимистичный взгляд, все же стоит повысить свои знания и навыки прежде чем использовать любую технологию «в боевой обстановке».

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

Что по поводу создание сборки grunt/gulp/webpack?
Использовать Grunt в новых проектах, еще и для backend-сервера — для чего это может пригодиться )?
Почему бы не использовать всю силу webpack для отсеивания «неиспользуемого кода» и предварительных сборок?

Что по поводу native-драйверов и ORM?
То что описано в статье актуально и для других платформ. А именно существует немалое количество библиотек «сомнительного качества». Но никто не заставляет вас использовать именно их. Если ваш проект стоящий, почему бы не уделить больше внимания при выборе библиотек (много это не значит плохо), а еще лучше копнуть глубже — прямо в исходные коды библиотек.

О транзакция, Postgres а MongoDb.
Тут стоит заметить, что прежде чем выбирать СУБД для проекта, стоит задать вопрос, а нужна ли она вообще (вполне можно написать api и работать на его базе не оперируя с базой)? Возможно вы пытаетесь применить те подходы которые не актуальны для данной платформы (и соответственно удивляетесь почему они тут не работоспособны).

Вы потратили на NodeJS слишком много времени?
В этом случае вам стоило задуматься об обучении. Вы ведь не садитесь в самолет пытаясь им управлять, без обучения и инструктора? Как и в любой платформе — стоит выделить время на обучение и реализации отдельно от времени внедрения. Посмотрите на код который вы писали во время обучения, скорей всего вам будет неуютно (он далек от идеала).

Поделюсь своим опытом. Примерно год на node.js. Достаточно активно писал на Scala. С JS знаком был на уровне $.ajax(...).

О чем забыл упомянуть автор — входной порог. На JS может писать любой программист. Спустя месяц практики все тот же любой программист может писать на JS как на JS, а не как на том, чем он писал раньше. Это две большие разницы.

Второе — модули. Да, их на каждый чих и случай жизни, плохие, средние, глючные, но они уже тут и работают. Иногда текут, да. Но, если мы говорим о сервер-сайде, это не то, чтобы прям плюс, но в этом… ну что-ли есть положительная черта. Это тебя готовит к let it crash, что ли. Только crash у тебя не изолированный как в Erlang, а на уровне приложения. Как справедливо заметили cluster и pm2 тут как раз для этого.

Хорошо бы конечно писать без ошибок, но пишем как получается, и готовы к тому, что оно может рухнуть.

Третье — JS, достаточно простой, и, черт возьми, выразительный язык. Сейчас фанаты Lisp, конечно поперхнулись, но JS уровня ES2015 в силу first class citizen functions вполне себе может разные штуки, которые на Java 6 выглядели весьма монструозно. Взять те же Promise. Вполне вписывается в ES5, даже если не завезли изначально.

Асинхронщина? Нас промисы спасают еще как. Код выходит с функциональным душком, что вообще говоря радует, и достаточно прямолинейным.

Чего на самом деле не хватает? Нормального pattern matching-а, ну и иногда тоска накатывает из-за отсутствия typesafety. Но в такие моменты я обычно вспоминаю def myFunc[A, B >: C, E:A, D \/ E](a: A, b:B, c:C, d;D, e: E): [A \/ E] и как-то легче становится :)
Почему-то все кто пишут о минусах NodeJS всегда пишут о том что получается лапша из колбеков. Но… почему бы не писать эту лапшу? И почему считается что лапша решается использованием промисов? Ведь можно просто… делить шаги на отдельные функции/методы. А не пихать всё в один супер-метод с десятком колбеков, будь то просто функции или использование промисов. Думаю большинству тех кто пишет о таких проблемах необходимо срочно прочитать книгу Чистый код. И хоть речь там идет о Java — эта книга быстро вылечит от проблем с какими-то там вложенными колбеками, промисами и всем всем всем.
Пишу код на NodeJS и не имею проблем с колбек хэлом. При этом не использую ни промисы, ни генераторы, на обычном ES5 всё выглядит как аккуратные классы с методами, в которых нет пирамидок из колбеков и лапши из промисов. И всё работает. И это при логике, в которой по 5 различных обращений в БД за раз, множество колбеков и всего такого асинхронного. И код выглядит хорошо!
Ну и если эта книга, полностью прочитанная и осознанная, не спасет от колбек хэлла — возможно стоит задуматься о правильности выбранной архитектуры?
Лапша решается промисами, потому что позволяет избежать nesting. В целом суть одна, но промисы при этом — мало того что удобная обертка, но еще и эдакий кирпичик асинхронного строения.
Поддержу. Аналогично — мы у себя не используем промисы. И при этом никакой лапши нет. Правда спастись от нее помогла не книга, а одна простая статья «Лапша» из callback-ов — будьте проще (очередное спасибо автору).

Пример кода не приведёте? ;-)


А лапша — это не лесенка. Лапша — это когда вы разбиваете одну функцию на 10 не потому, что это логически обосновано, а потому, что иначе поток заблокируется на неопределённый срок. А лесенка — это лапша с горкой.

Например вот так.
Не то чтобы божественный пример, но рабочий код, без вырезок, переживший несколько доработок и крутящийся на продакшене.
Можно было бы конечно что-то более стремящееся к треугольности в плане колбеков, но либо не безопасно, либо не ищется с ходу.
Отмечу что использую классовый движок ExtJS, но он никак не выпрямляет код, лишь помогает в ООП.
Если вот этот класс написать в треугольном стиле — прокрутка в бок была бы весьма велика, как и разброс кода и мест определения локальных переменных. Не говоря уже о путанице в тех что видны через скоп.
По поводу B.util.Function.queue — утилита просто вызывает по очереди функции, передавая скоп текущего класса и первым параметром отправляя ссылку на следующую функцию, в которой заранее проставлен скоп.
Про обработку ошибок — суть в том что каждый метод сам лучше знает а что у него не так, что поломалось, что необходимо выбросить в исключение, залогировать и т.п. и он сам вызывает метод с ошибкой или сразу отправляет что-то клиенту. Это избавляет от вложенных поимок, катчей уровнем ниже и прочих дебрей вглубь. В итоге — всё плоско и предсказуемо.
Впрочем, перфекционист найдет что здесь ещё можно было улучшить. Например вынести в отдельный субкласс «управляемый режим запуска», можно было бы ещё больше разложить и подоптимизировать код. Но это — полноценный продакшн, не вылизанный код и даже в таком виде он лучше пирамидок, треугольников и цепочек отлова ошибок.
В добавок к этому разделение на шаги позволяет проименовать шаги, что ведет к большему пониманию что происходит в каждый момент времени. Особенно полезно если над проектом работает не 1 человек, и просто необходимо если планируется что код будет поддерживаться годами.
Ну и ещё одна особенность, уже касательно организации асинхронных путей исполнения кода — в рамках одного класса/модуля/etc. должен быть 1 путь. Если в процессе исполнения шагов порождаются вложенные наборы шагов или ветвления — необходимо выносить это в отдельный класс/модуль/etc. Всё это позволит избавиться от непредсказуемости, треугольности и проблем с дебагом.
И всё это на ES3/ES5, то есть промисы добро и только дополняют решение, но в голом виде не решают проблем. А вот архитектура и организация кода — наше всё!
Пример
/**
 * Логика размещения компании клиента.
 */
Ext.define('B.biz.client.Release', {
    extend: 'B.AbstractRequestHandler',

    requires: [
        'B.biz.auth.util.Account'
    ],

    config: {

        /**
         * @cfg {Boolean} isDirectMode
         * В управляемом режиме релиз производится логину, указанному в {@link #directLogin}.
         */
        isDirectMode: false,

        /**
         * @cfg {Boolean} directCallback
         * В управляемом режиме вызывает эту функцию в момент завершения, вместо отправки клиенту данных.
         */
        directCallback: Ext.emptyFn,

        /**
         * @cfg {Boolean} directCallbackScope
         * Скоуп выполнения {@link #directCallback}.
         */
        directCallbackScope: null,
        
        /**
         * @cfg {Boolean} directErrorCallback
         * В управляемом режиме вызывает эту функцию в случае ошибки, вместо отправки клиенту данных.
         */
        directErrorCallback: Ext.emptyFn,

        /**
         * @cfg {Boolean} directErrorCallbackScope
         * Скоуп выполнения {@link #directErrorCallback}.
         */
        directErrorCallbackScope: null,

        /**
         * @cfg {String/Null} directLogin
         * Логин, по которому необходимо произвести релиз в управляемом режиме.
         */
        directLogin: null,
        
        /**
         * @cfg {Boolean} isSendSuccessIfPayDateIsExpired
         * Флаг, указывающий на то что необходимо отправить что всё прошло успешно
         * в случае если истек переиод оплаты,
         * не смотря на то что из-за этого релиз произведен не был.
         * Может быть использовано в кейсе когда аккаунт был создан только что,
         * данные вносятся клиентом, но оплата ещё не произошла.
         */
        isSendSuccessIfPayDateIsExpired: false,

        /**
         * @private
         * @cfg {Object} accountData Данные аккаунта.
         */
        accountData: null,

        /**
         * @private
         * @cfg {String[]} tagsData Массив данных для тегов.
         */
        tagsData: null,

        /**
         * @private
         * @cfg {String[]} tags Массив тегов.
         */
        tags: null,

        /**
         * @private
         * @cfg {Object} searchObject Объект поиска.
         */
        searchObject: null
    },

    constructor: function () {
        this.callParent(arguments);

        B.util.Function.queue([
            this.extractAccountStep,
            this.validateAccountStep,
            this.extractTagsDataStep,
            this.makeTagsStep,
            this.makeSearchObjectStep,
            this.writeSearchObjectStep,
            this.sendSuccess
        ], this);

    },

    /**
     * @protected
     * Модифицированная версия, не пытается отправить клиенту ошибку при управляемом запуске.
     * Вместо этого вызывает {@link #directCallback}.
     */
    sendSuccess: function () {
        if (this.getIsDirectMode()) {
            this.getDirectCallback().apply(this.getDirectCallbackScope(), arguments);
        } else {
            this.callParent(arguments);
        }
    },
    
    /**
     * @protected
     * Модифицированная версия, не пытается отправить клиенту ошибку при управляемом запуске.
     * Вместо этого вызывает {@link #directErrorCallback}.
     */
    sendError: function () {
        if (this.getIsDirectMode()) {
            this.getDirectErrorCallback().apply(this.getDirectErrorCallbackScope(), arguments);
        } else {
            this.callParent(arguments);
        }
    },

    privates: {

        /**
         * @private
         * @param {Function} next Следующий шаг.
         */
        extractAccountStep: function (next) {
            var key = null;
            var login = null;

            if (this.getIsDirectMode()) {
                login = this.getDirectLogin();
            } else {
                key = this.getRequestModel().get('key');
            }

            Ext.create('B.biz.auth.util.Account', {
                key: key,
                login: login,
                type: 'company',
                scope: this,
                callback: function (acc) {
                    var data = acc.getPrivateAccountData();
                    
                    if (data) {
                        this.setAccountData(data);
                        next();
                    } else {
                        this.sendError('Данные указанного аккаунта не найдены!');
                    }
                }
            });
        },

        /**
         * @private
         * @param {Function} next Следующий шаг.
         */
        validateAccountStep: function (next) {
            var data = this.getAccountData();
            var basic =   Ext.create('B.biz.client.model.BasicData');
            var summary = Ext.create('B.biz.client.model.Summary');
            var photo =   Ext.create('B.biz.client.model.Photo');
            var words =   Ext.create('B.biz.client.model.Words');

            if (this.isPayDateExpired()) {
                this.handlePayDateExpired();
                return;
            }
            
            data.key = true; // Модели требуют наличия ключа сессии.
            
            basic.set(data);
            summary.set(data);
            photo.set(data);
            words.set(data);

            if (!basic.isValid()) {
                this.sendError('Основные данные о компании ещё не заполнены.');
                return;
            }

            if (!summary.isValid()) {
                this.sendError('Описание компании ещё не заполнено.');
                return;
            }

            if (!photo.isValid()) {
                this.sendError('Фотографии ещё не заполнены.');
                return;
            }

            if (!words.isValid()) {
                this.sendError('Ключевые слова ещё не заполнены.');
                return;
            }

            next();
        },

        /**
         * @private
         */
        handlePayDateExpired: function () {
            var successIfExpired = this.getIsSendSuccessIfPayDateIsExpired();

            if (successIfExpired) {
                this.sendSuccess();
            } else {
                this.sendError('Невозможно выполнить действие - услуга ещё не оплачена.');
            }
        },

        /**
         * @private
         * @return {Boolean} Закончился ли оплаченый период.
         */
        isPayDateExpired: function () {
            return this.getAccountData().payDate < new Date();
        },

        /**
         * @private
         * @param {Function} next Следующий шаг.
         */
        extractTagsDataStep: function (next) {
            var data = this.getAccountData();

            this.setTagsData([
                data.name || '',
                data.word1 || '',
                data.word2 || '',
                data.word3 || '',
                data.word4 || '',
                data.word5 || '',
                data.word6 || '',
                data.word7 || '',
                data.word8 || '',
                data.word9 || '',
                data.word10 || '',
                data.address || '',
                data.summary || ''
            ]);
            
            next();
        },

        /**
         * @private
         * @param {Function} next Следующий шаг.
         */
        makeTagsStep: function (next) {
            Ext.create('B.biz.search.util.Tokens', {
                value: this.getTagsData(),
                scope: this,
                callback: function (self, value) {
                    this.setTags(value);
                    next();
                }
            });
        },

        /**
         * @private
         * @param {Function} next Следующий шаг.
         */
        makeSearchObjectStep: function (next) {
            var data = this.getAccountData();

            this.setSearchObject({
                company: B.Mongo.makeId(data._id),
                rating: 0,
                tags: this.getTags(),
                map: data.map,
                payDate: data.payDate
            });
            
            next();
        },

        /**
         * @private
         * @param {Function} next Следующий шаг.
         */
        writeSearchObjectStep: function (next) {
            var searchObject = this.getSearchObject();
            
            B.Mongo.getCollection('search').update(
                {
                    company: searchObject.company
                },
                searchObject,
                {
                    upsert: true
                },
                function (error) {
                    if (error) {
                        this.sendError(B.Mongo.requestErrorText);
                    } else {
                        next();
                    }  
                }.bind(this)
            );
        }
    }
});

Я тут сократил ваш код в 3 раза...


Заголовок спойлера
/// Логика размещения компании клиента.
class Release extends AbstractRequestHandler {

    /// Логин, по которому необходимо произвести релиз в управляемом режиме.
    directLogin = null

    /// Флаг, указывающий на то что необходимо отправить что всё прошло успешно
    /// в случае если истек переиод оплаты,
    /// не смотря на то что из-за этого релиз произведен не был.
    /// Может быть использовано в кейсе когда аккаунт был создан только что,
    /// данные вносятся клиентом, но оплата ещё не произошла.
    ignoreExpiration = false

    @mem get response( ) {
        if( this.isPayDateExpired ) {
            if( this.ignoreExpiration ) return new ResponseOK({ message : 'Ждём оплаты...' })
            else throw new Error( 'Невозможно выполнить действие - услуга ещё не оплачена.' )
        }

        B.Mongo.getCollection('search').update(
            {
                company: this.searchObject.company
            },
            searchObject,
            {
                upsert: true
            }
        )

        return new ResponseCreated({ message : 'Релиз создан.' })
    }

    @mem get isPayDateExpired( ) {
        return this.account.payDate < new Date
    }

    @mem get searchObject( ) {
        return {
            company: B.Mongo.makeId( this.account._id ),
            rating: 0 ,
            tags: this.tags ,
            map: this.account.map ,
            payDate: this.account.payDate ,
        }
    }

    @mem get account() {
        // Модели требуют наличия ключа сессии.
        var data = { ...this.accountRaw , key : true }

        new BasicData({ data }).enforce( 'Основные данные о компании ещё не заполнены.' )
        new Summary({ data }).enforce( 'Описание компании ещё не заполнено.' )
        new Photo({ data }).enforce( 'Фотографии ещё не заполнены.' )
        new Words({ data }).enforce( 'Ключевые слова ещё не заполнены.' )

        return this.accountRaw
    }

    @mem get accountRaw( ) {
        var account = new Account({
            key : this.directLogin ? null : this.request.get( 'key' ) ,
            login : this.directLogin ,
            type : 'company' ,
        })

        if( !account.privateData ) throw new Error( 'Данные указанного аккаунта не найдены!' )

        return account.privateData
    }

    @mem get tags( ) {
        return new Tokens({
            value: this.tagsData
        })
    }

    @mem get tagsData( ) {
        return [
            this.account.name || '' ,
            this.account.word1 || '' ,
            this.account.word2 || '' ,
            this.account.word3 || '' ,
            this.account.word4 || '' ,
            this.account.word5 || '' ,
            this.account.word6 || '' ,
            this.account.word7 || '' ,
            this.account.word8 || '' ,
            this.account.word9 || '' ,
            this.account.word10 || '' ,
            this.account.address || '' ,
            this.account.summary || '' ,
        ]
    }

}

Что я сделал:


  1. Выпилил костыли с переключением точки выхода на основе флага isDirectMode. Теперь достаточно создать Release и взять у него свойство response. Что делать с ответом уже вызывающему коду решать.
  2. Выпилил несколько точек выхода любезно спрятанные в handlePayDateExpired. В любой момент она могла остановить выполнение цепочки шагов, нарисовав ответ пользователю. Теперь эта логика видна сразу при входе — вначале геттера response.
  3. Выпилил всю асинхронщину. Теперь все исключения могут быть перехвачены фронтальным контроллером посредством try-catch с выводом соответствующего http-ответа. Раньше исключения могли вообще уронить процесс и никто бы им в этом не помешал.
  4. Императивную парадигму сконвертировал в ленивую функциональную. Вместо последовательного выполнения "шагов" теперь одни свойства вычисляются на основе других свойств и кешируется результат. Это гарантирует правильность порядка выполнения "шагов" и готовность всех необходимых "шагу" данных.
  5. Упростил API валидаторов. Теперь их можно применять в одну строку, а не по 7 на каждый.
  6. Ну и по мелочи: воспользовался нативными классами, геттерами и декораторами. Сейчас нет смысла их не использовать. Они достаточно тривиально транслируются в ES5.

@mem — Декоратор для кеширования результата выполнения геттера, чтобы он не дёргался каждый раз.

Да, похоже мне всё-таки стоит глубже изучить новые стандарты.
Спасибо за развернутый ответ.

Во всех ваших примерах у меня глаз цепляется за var? Вы ведь даже декораторы используете (которые ещё фиг его знает будут ли в языке), но var… Он же ужасен. Зачем?

Как раз тем, в чём заключается его разница с let. У него не блочная область видимости и он всплывает. Хуже, на моей памяти, только объявление переменных в php (кажется именно поэтому там приходится использовать use в анонимках). Для чего же его использовать? Экономия строк в подобном коде:


if(some)
  var a = 1;
else
  a = 2;

?



Тут нас спрашивают «к какому из двух объявлений перейти»?



Обычно всё же интересует место инициализации переменной, а не первое её упоминание.

Ясно. На мой взгляд это преимущество притянуто за уши. Замечу, что js-линтеры будут пытаться выжечь такой код огнём из-за двойного определения одной и той же переменной. К тому же первое упоминание даст хорошее представление об области применения этой переменной.


Но мысль понял, спасибо :)
P.S. такие огромные скриншоты лучше под спойлер.

Ну, это помимо экономии строки (а то и двух) и необходимости разделять работу с переменной на 3 части (объявление, инициализация, получение значения).

У JS-линтеров, к сожалению, много глупых правил. Из-за динамической природы JS сколь-нибудь сложный статический анализ невозможен. Вот и анализируют, что могут — расположение пробелов, фигурных скобочек и прочие декоративные штуки.

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

1. В огромных сложных функциях. Но огромные функции сами по себе — антипаттерн.
2. В циклах, при использовании их в созданных в циклах замыканиях. Но создавать замыкания в цикле, опять же — антипаттерн.

Мысль ясна. Я же исхожу больше из очевидности и понятности кода. Использую const всегда, когда не требуется повторного присваивания (т.е. практически всегда). Повторного же присваивания избегаю в пользу разных переменных (чаще всего задача у переменной при этом меняется, а соответственно и имя стоит изменить). В оставшихся ситуациях применяю let. В тех редких случаях, когда я вынужден выносить декларацию за пределы инициализации (когда их может быть несколько), считаю это платой за очевидность области работы переменной. Что вполне уместно, т.к. код с несколькими точками инициализации переменной уже не так уж и очевиден.


Учитывая, что я как и вы разделяю подход, заключающийся в том, что код должен быть достаточно простым и ясным, и без каких-либо не очевидных моментов, считаю это наиболее оптимальным вариантом. Области применения для var для себя не нашёл. Считаю его архаичным потенциально баго-генерирующим мусором.

Я в целом-то согласен и пробовал постоянно использовать let, но, как сказал выше, особого профита он не даёт, а раздражающих ограничений вводит порядочно. Последней каплей стала невозможность в catch обратиться к переменным объявленным в try, что вынуждает объявления чуть ли не всех переменных выносить в начало функции, что провоцирует ошибки вида "использование неинициализированной переменной" и "объявление неиспользуемой переменной".


let az, buki, vedi
try {
    // some code
    az = foo()
    // some code
    buki = az.boo()
    // some code
    vedi = true
} catch( error ) {
    console.log( error )
    if( buki ) buki.destroy()
    vedi = false
}
UFO just landed and posted this here
Sign up to leave a comment.

Articles