Год разработки с штатными инструментами командной строки, клиентские проекты и выпуск обновлений для продуктов нашей компании, в двух словах, это то, чему я научился за это время. Я бы хотел поделиться опытом, но речь пойдет не столько про Node, сколько за весь JavaScript в целом.
Легко научиться, но стать мастером невозможно
Node очень прост для освоения. Особенное если вы знакомы с JavaScript. Нагуглите несколько туториалов для начинающих, поиграйтесь с Express и вы уже в теме, не так ли? Затем вы начнете осознавать, что нужно как-то взаимодействовать с базами данных. Нет проблем, запускаете NPM… Ага, всего лишь горстка приличных SQL пакетов. Позднее вы поймете, что все существующие ORM инструменты никуда не годятся и то, что базовый драйвер — это лучший выбор. Теперь у вас проблемы c реализацией своей модели и проверкой логики. Скоро после этого, вы начнете писать более сложные запросы и просто потеряетесь в callback'ах. Естественно, вы прочитаете про 'callback hell', плюнете на это дело и начнете использовать одну из многих promise библиотек. С этих пор вы просто начнёте «промисифицировать» все штуки, которые делаете, взамен на спокойные выходные.
Всё это заставляет сказать, что как-будто экосистема Node постоянно движется. И вектор этого движения не очень хорош. Новые инструменты, которые вроде как «намного круче» старых, буду встречаться каждый день. Только представьте: всегда можно найти то, что у вас есть, но только еще и «светится». Вы будете удивлены как просто это может случиться с вами. И похоже, сообщество это только поощряет. Вы используете Grunt!? Все используют Gulp!? Подождите минутку, используйте NPM нативные скрипты!
Пакеты, которые состоят из тривиального кода не более чем на 10 строк, загружаются тысячи раз каждый день из NPM. Вы серьезно? Вам правда нужно установить всю эту вереницу зависимостей, чтобы просто проверить тип массива? И эти самые пакеты используются такими гигантами как React и Babel.
Вы никогда не станете мастером того, что движется с такой, ломающей голову, скоростью. И это ни слова не говоря о «стабильности» таких движений.
Обработка ошибок по принципу «повезло\не повезло»
Если вы пришли из другого языка, такого как Python, Ruby или PHP, то вы будете ожидать, что что-то бросит вам исключение, что-то его поймает, на худой конец функция вернет ошибку. И это нормально. Совершенно нормально… Но в Node это не так. Наоборот, вы передаете ошибки в ваши callback'и или promis'ы — всё верно, никаких бросков исключений. Но это работает ровно до тех пор, пока вы не захотите получить стек вызовов у нескольких вложенных callback'ов. Не говоря уже о том, что если вы забудете вернуть callback в случае ошибки, то скрипт продолжит выполняться и вызовет набор других ошибок, в добавок к первой. Вам придется удвоить количество отладочной информации, чтобы нормально дебажить.
Даже если вы начнете «серьезно» по стандарту обрабатывать свои собственные ошибки, вы не можете (без чтения исходников) убедиться, что большая часть пакетов, установленных из NPM, используют тот же подход.
Эти проблемы приведут вас к использованию «catchall» обработчиков исключений. Которые смогут залогировать проблемное место и позволят вашему приложению не просто грациозно «упасть». Запомните, Node однопоточный. Если что-то заблокирует процесс, всё вокруг порушится к чертям. Но это круто, вы же используете Forever, Upstart and Monit, верно?
Callback, Promise или Generator!?
Чтобы управлять 'callback hell', ошибками и трудночитаемой логикой всё больше и больше разработчиков начинают использовать Promis'ы. Это способ писать код, который выглядит более-менее синхронно, без сумасшедшей 'callback' логики. К сожалению, не существует ни одного «стандарта» (как и для ничего другого в JavaScript'е) для реализации или использования Promis'ов.
Самая часто упоминаемая библиотека сейчас — это Bluebird. Она довольно хороша, быстро работает и заставляет вещи «просто работать». Как бы там ни было, я нашел очень полезным оборачивать всё, что мне нужно в Promise.promisifyAll().
По большей части я использовал замечательную библиотеку async, чтобы держать свои callback'и под контролем. С ней всё выглядело более естественно.
Ближе к концу моего опыта с Node генераторы стали более популярны. Я так и не закончил свое «погружение» в них и поэтому не могу толком ничего сказать. Хотелось бы услышать кого-нибудь, кто с ними знаком поближе.
Плохая стандартизация
Последней каплей было то, что я обнаружил отсутствие стандартов. Такое ощущение, как будто каждый имеет свое собственное представление о том, как работать с вещами, описанными выше. Callback'и? Promis'ы? Обработка ошибок? Build скрипты? Да этому нет конца.
Это всё накаляет… Никто не может сказать, как написать стандартизированный JavaScript-код. Просто забейте в гугле «JavaScript Coding Standards» и вы поймете, что я имею ввиду.
Я понимаю, что много языков не имеют строгой структуры, но они обычно имеют стандартный гайдлайн, созданный мейнтейнерами языка.
Единственное, что хоть как-то приемлемо, написали в Mozilla.
Итоги по Node
Я потратил год, пытаясь заставить JavaScript и более специфичный Node работать на нашу команду. К сожалению, за это время мы потратили больше часов на поиск документации, знакомство со «стандартами», споры о библиотеках и отладку тривиального кода, чем на что-то полезное.
Посоветовал бы я Node для больших проектов? Абсолютно нет. Будут ли люди использовать его всё равно? Конечно будут. Я тоже пытался.
Как бы там ни было, я бы рекомендовал JavaScript для фронтенд разработчиков, таких как Angular или React (как будто у вас есть другой выбор).
Так же бы я посоветовал Node для простых back-end серверов, используемых в основном для вебсокетов или предоставления API. Это можно просто сделать с Express, говорю так, потому что мы так и сделали для своего Quoterobot PDF-процессинг сервера. Это один файл, содержащий 186 строк кода, включая пробелы и комментарии. И он так же хорошо работает, насколько он прост.
Возвращение на Python
Вполне возможно вы удивлены, что я делаю сейчас? Сейчас я продолжаю писать главные части наших продуктов и API, используя Python. В основном на Flask или Django, используя либо Postgres либо MongoDb.
Это проверенный временем вариант с великолепными стандартами, библиотеками, легкой отладкой и стабильной работой. Конечно, и у него есть недочеты. Но он хорош, стоит только начать писать на нем. По некоторой причине Node привлек внимание моих глаз и затянул меня. Я не сожалею о своей попытке подружиться с ним, но чувствую, что потратил на него больше времени, чем должен был.
Я надеюсь, JavaScript и Node улучшат в будущем. Я буду счастлив попробовать его вновь.
Расскажите о своем опыте? Были ли у вас проблемы, которые я испытывал? Закончили ли вы «перескакивать» назад, на более комфортный язык?
Комментарии (174)
k12th
18.07.2016 12:41-6Чувак, походу, во всем фиксируется на негативных сторонах. Удивительно, что перешел обратно на Python вместо Go/Rust/Ruby/Erlang.
auine
18.07.2016 13:02+16Удивительно, что перешел обратно на Python вместо Go/Rust/Ruby/Erlang.
У человека есть опыт с Python, зачем ему выбирать из Go/Rust/Ruby/Erlang? Мода типо?Terras
18.07.2016 14:46Руби хорош, правда под него задачи 1 в 1 с питоном (только реализация отличается). И если знаешь питон, переходить на руби смысла нет.
k12th
18.07.2016 14:52-2Типо да, а что, незаметно? Вы видели его претензии к Python? Чувак просто ищет серебряную пулю. Удачи.
alemiks
19.07.2016 10:57+3Мода типо?
Ну как бы в предыдущей статье он так и пишет, «мода типо»
I wanted something modern and designed for the new web. PHP, Python and Ruby are definitely not it
movl
18.07.2016 13:34+3Человек после прочтения тутора по экспресс считает себя уже находящимся "в теме". И инструмент судя по всему он выбирал не для решения какой-то проблемы, а потому что стильно, модно, молодежно. Для таких целей было бы верно написать какой-нибудь микросервис, как мне кажется, и поиграться хватило бы, и оценить возможность применения в дальнейшем.
По моему мнению, в первую очередь ноду нужно выбирать из-за асинхронности, и того, что это дает, а потом уже остальное. Но люди почему-то выбирают из-за моды, пилят достаточно большие проекты, типа другие же делают, а потом ноют.
atc
19.07.2016 03:33+4Асинхронная работа доступна сейчас практически на любых платформах, будь то php\phython\java\langname, достаточно затянуть в проект очередной reactphp\akka. То есть с этой точки зрения js\node ничего исключительного на данный момент времени предложить не смогут.
С другой стороны js\node могут привлекать существенно лучшим дизайном языка в мелочах, чем у того же php: те же map\reduce, методы строк и массивов существенно упрощают жизнь по сравнению с развесистыми циклами и преобразованиями на php.oxidmod
19.07.2016 08:56+1не поверите, но в пхп тоже есть меп и редьюс
atc
19.07.2016 09:02+3Им весьма непросто пользоваться по нескольким причинам, первая из которых — отсутствие стрелочных функций (js или java, подойдет любой вариант), вторая — невозможность аккуратно выстраивать цепочки для обработки потока данных.
Наверняка имеются соответствующие библиотеки\инструменты, но все же это не так удобно, по сравнению со стандартными средствами языка.oxidmod
19.07.2016 09:53до недавно в js тоже небыло стрелочных функций)
вот первое что нагуглилось: https://github.com/Anahkiasen/underscore-phpfaiwer
19.07.2016 11:44+4Я пробовал пользоваться этим. И не только этим. Отчаявшись даже решил написать что-то свое. Ибо после lodash.js мне сильно не хватало возможностей. В итоге забил и снова стал писать foreach-и, for-ы и пр. конструкции для большинства задач, т.к. в PHP не только стрелочных функций нет, не только массивы не имеют объектного синтаксиса, но ещё и использование анонимных функций очень переусложнено (необходимость прокидывать всё используемое через use убивает всякое желание их использовать). Всё таки для работы в около функциональном стиле в языке должны быть для этого удобства.
Честно говоря после ES6-7 я не могу писать на PHP без боли. А глядя на PHP7 и его новшества я понимаю, что эти два, чем то похожих языка, пошли разными дорогами. В PHP, как мне показалось, основной курс идёт на статичную типизацию и всевозможные плюшки из Java-подобных языков. А JavaScript усиленно скачет в функциональную область. Не удивлюсь, если в каком-нибудь ES2017 нас будут ждать нативные производительные immutable-конструкции.
Athari
21.07.2016 05:21Если не нужно прокидывать переменные, то можно использовать костыльные стрелочные функции (см. например YaLinqo), получается компромисс между краткостью и вменяемостью кода. Если нужны use — только боль и полный синтаксис.
массивы не имеют объектного синтаксиса
Что вы имеете в виду?
faiwer
21.07.2016 06:33Посмотрел
YaLinqo
… Ну это уже совсем лихо. Интерпретатор в интерпретаторе. По поводу массива: вместо$array->map
array_map($array,
.Athari
21.07.2016 09:03Да нет там никакого интерпретатора, там вызов
create_function
после лёгкого преобразования строки. Впрочем, есть порт LINQ и с подключением интерпретатора для поддержки генерации запросов к БД (правда только в виде демки для MySQL, автор сдулся).
С функциями
array_map
и иже традиционная проблема PHP — у каждой функции свой порядок аргументов. Если вкладывать больше двух уровней, то получается невменяемая каша, да и сами функции — каша.faiwer
21.07.2016 09:42Да нет там никакого интерпретатора
Ну дык просто используется родной. Поверх уже запущенного кода. Подход, конечно, весьма производительный :)
традиционная проблема PHP — у каждой функции свой порядок аргументов
Да даже если привести их к одному виду, без какого-нибудь
|>
из функциональных языков дело с мёртвой точки тут не сдвинется.
movl
19.07.2016 11:27Мне кажется любой язык не может предложить чего-то исключительного, везде есть плюсы/минусы и подходящие/неподходящие области применения.
Например, есть задача с кучей операций ввода-вывода, допустим сложная бизнес-логика завязанная на десятках таблиц, и каждую таблицу нужно чуть ли не по хаотичным правилам читать/писать в зависимости от запросов. Стандартная история вроде. И я бы не стал в данном случае выбирать ноду, если нет каких-то еще более специфичных требований, например, по производительности. Но подобную логику чаще всего больно писать в асинхронном стиле и очень больно поддерживать.
Или например есть ситуация, где нужна очень простая логика ввода-вывода, типа считал-обработал-записал, и все это для одной записи в монговской коллекции и каждый запрос так, но видов запросов большое множество, и одновременно ломятся тысячи, десятки, сотни тысяч запросов, и в данном случае я бы тоже хорошо подумал прежде чем не взять ноду.
Я скорее это имел ввиду: автор выбирал инструмент, не потому что он подходил для решения задачи, а потому что так захотелось. Иначе он бы был готов ко всем проблемам.
Diaskhan
18.07.2016 13:37+4Питон щас на 4 месте по популярности в TIOBE Index.
И да лапша из каллбеков размывает код, и в итоге код с колббеками сложно поддерживать, как и в случае заглушек, так и в случае установки брейкпойнтов, Код становится ассинхронным но к сожалению не организованным.
Gavin Vickery понял что лучше будет оверхед с абстракцией, нежели супер мощное сложно поддерживаемое приложение.
Другой вопрос подходит ли принцип MVC Для Ноды и Express.js ?? может потом появится другая методология которая будет включать в себя такое понятие как асинхронность!OnYourLips
19.07.2016 09:03+4На втором месте в этом индексе чистый C, на восьмом — бейсик, а на девятом — набирающий популярность Perl.
Стоит ли верить такому рейтингу, который никак не связан с рынком?
GRIM2D
18.07.2016 13:37+1Я пришел из мира мобильной разработки. Особенно пользовался 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 хорошо до тех пор, пока вы помните структуру проекта.Aries_ua
18.07.2016 13:43+3Писать солюшены, документацию и комментарии в коде — все это необходимо делать вне зависимости от языка программирования. Иначе любой сложный проект без этого, будет запутанный.
Думаю любой сложный проект надо начинать делать на том инструменте, которым хорошо владеешь ты или команда. Делать что-то большое и сложное на новом, для себя языке, провально. Будет много ошибок и боли.GRIM2D
18.07.2016 13:47+1В данном случае, JSDoc писался только для того, чтобы редактор понимал тип объекта, ну и естественно получить autocomplete и подсказки.
bromzh
18.07.2016 14:23+2- Запросы на MongoDB синхронные.
Это "заслуга" не спарка, а драйверов монги. Они для java есть в 2-х вариантах: блокирующий и неблокирующий. Но для js тоже есть блокирующий драйвер.
Ну и странные слова про структуру проекта. Ведь тот же спарк ничем не поможет в этом (он сам по себе маленький фреймворк, который не заставляет использовать какую-то жёсткую структуру проекта, и без встроенных инструментов для IoC).
Так что если были проблемы в JS, то смена языка/фреймворка далеко не всегда будут панацеей.
Aries_ua
18.07.2016 13:37+9Не так страшен черт как его малюют.
В начале достаточно определить Code Style в команде. Это не проблема перед началом договориться как писать код.
Что касается Promise, возможно автор не до конца понял суть их использования. Да, они ломают синхронную логику, к которой привыкли backend девелоперы. Но если грамотно подойти, то промисы отлично работают и код красиво структурируется цепочками вызовов.
Что касается исключений, то опять таки, в промисах вы смело можете их кидать и ниже по цепочке перехватывать. Это действительно удобно.
Что касается NPM, тут согласен — без фанатизма. Стоит брать только то, что действительно необходимо. Сам против того, что бы на каждый чих тянуть что-то из NPM.
Кстати, до этого писал на python (django, flask, tornado). Сейчас уже 2 года пишу исключительно на NodeJS. Пока обратно не тянет.vintage
18.07.2016 17:21+8К хорошему быстро привыкаешь и от него больно отвыкать. Вы правда считаете, что такой код:
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
?
webmasterx
18.07.2016 17:36А что если в версии с промисами вывести лог таким образом:
console.error( error.stack )
?
p.s. С нодой не знаком
Demogor
18.07.2016 19:12+2С использованием генераторов(либо будущего async/await) можно писать код в синхронном стиле. Обычно использую что-то в таком духе:
_asyncCatch(function*(){ try{ var a1=yield promise1(); var a2=yield promise2(); var a3=yield promise3(); } catch(ex){ console.log(ex); } }
У Promise должен быть описан reject.vintage
18.07.2016 20:30+1co( 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
Aries_ua
18.07.2016 21:01Вот выдернул кусочек кода
return validator .validate() .then(value => Model.findById(data.get('id'))) .then(model => { model.password = hash(data.get('password'), settings.salt); return model.save(); });
Я бы не сказал, что код нечитабельный.vintage
19.07.2016 00:35Для сравнения:
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()
Как вы это реализуете на промисах?
AxVPast
19.07.2016 09:09+1Напишу тест (код с if вобщем-то с кучей багов, апример var model). Без тестов вообще ничего не получится.
Вызовы к модели обретут свои имена (тут явно перемешаны сущности из разных уровней абстракций — в частности data.get('id') встречается с Model.* — что не должно быть, разве что data — объект для работы с БД, но больше похоже на то, что это обёртка над http request, то есть напрямую в функции участвовать не должна).
Дальше будет просто:
function storePasswordForExistingUserOrCreateNewUserWithDefaultPassword( userId, userPassword ){ return Q.fcall(function (){ if( userId ){ return db.updateUserPassword( userId, passwordEncoder.hashPassword( userPassword )); } return db.createNewUser( passwordEncoder.generateDefaultHashedPassword() ); }).then( function (userProfile){ return userProfile.save() }) }
AxVPast
19.07.2016 09:25+1Краткое пояснение — почему именно так
1. Model — сущность работы с БД. Черт его знает какая там реализация промисов, по этому db — обёртка для того, чтобы все привести к Q, а за одно перевести уровень абстракции с конкретной СУБД до логического уровня «что мы делаем» с уровня «как мы это делаем».
2. Q.fcall — перехват ситуации, что db == null или passwordEncoder == null. В принципе такое можно отловить в тестах, но если код инициализации системы асинхронный и кто-то накосячил — в момент исполнения там может быть null.
3. passwordEncoder и db должны быть переданы в модуль при инициализации — иначе не написать тесты на эту функцию без прямой работы с БД (иногда допускается но идея не сильно хорошая).
4. Сама функция называется storePasswordForExistingUserOrCreateNewUserWithDefaultPassword — что подразумевает что весь проект приведет в аснхре к Q. Если это не так, то следует назвать как qStorePasswordForExistingUserOrCreateNewUserWithDefaultPassword (дабы было очевидно, что возвращается именно обещание).atc
19.07.2016 09:35> до логического уровня «что мы делаем» с уровня «как мы это делаем».
А у вас есть чему поучиться, спасибо за отличную метрику качества кода.
faiwer
19.07.2016 12:06+2Гхм. vintage намекнул, что подход основанный на Promise, Q и пр. будет всё более и более уродливым, с каждым вветвлением. И привёл максимально простой пример на основе выше указанного. А вы зачем-то привели пример того, как можно всё архитектурно переделать, дабы избежать вветвления. А смысл? Пример не самый удачный? Ну и ладно, суть то была как раз в том, чтобы продемонстрировать что Promise-ы не решают проблему читаемости асинхронного кода, а всего лишь её смягчают. А не в том, что всякую задачу можно реорганизовать, в особенности если она простая как 2 пальца.
Это как в споре о том, что Ока не самый лучший автомобиль для перевозки кирпичей, рассуждать о том, что всё не так плохо и можно прицепить к Оке тележку, а кирпичи грузить туда, а лучше купить две Оки и т.п. :)
atc
19.07.2016 12:14Вообще в объектной парадигме большое количество ветвлений — частый признак не слишком хорошей декомпозиции задачи и предполагается, что таких мест с обилием ветвлений и тем более асинхронщиной — должно быть исчезающе мало. Вариант с рефакторингом кажется в этом смысле вполне логичным.
faiwer
19.07.2016 12:20Угу, тут всё упирается вот в это: "большое количество ветвлений". Это сколько? Скажем в Promise хватает уже одного уровня, чтобы испоганить всю читаемость вхлам. Довольно грустно. Потому и ждём нативных
await
, извращаясь сco(* gen)
.
и тем более асинхронщиной
В nodeJS коде, в моём по крайней мере, НЕ асинхронного кода очень мало. Сама платформа располагает к асинхронности в практически любых задачах
atc
19.07.2016 12:38Одно простое ветвление можно аккуратно поместить в тернарник, а все что сложнее — наверное заслуживает располагаться в именованом методе\функции, что позволит так же отдельно тестировать этот участок кода и иметь в нем синхронный вход.
Попробовал представить случай — где потребовались бы размещать относительно сложный код в анонимных фунциях внутри promise — и не смог, при нормальной декомпозиции системы это кажется редким случаем.
AxVPast
19.07.2016 15:20Цикломатическая сложность (см. Вики) > 15 и до свидания тестирование. Написать лапши типа:
Object.prototype.killThemAll = function (){ console.log('Wow'); }; express.use('/some/url', function (req, resp, next){ someDb.hget( 'someKey:' + req.body.id, function (err, d){ // хрен с ней с ошибкой smtp.sendMail({ from: 'admin@bad.site', to: req.body.email }, "Hi, I am black dead code :). You should debug me forever:)." + d, function (err2){ // задолбали вы со своими обработчиками ошибок, отвяжитесь res.killThemAll(); res.end('Need to set X-Frame-Options header? forget :) you do not control your code :)'); ..........
Язык позволяет. JavaScript предоставляет определенную «свободу» писать код как попало ввиду отсутствия жестких правил, например, как в Java. Некоторые по наивности на это покупаются. Понимание ошибки приходит эдак через месяц. После этого вдруг все резко вспоминают про паттерны, ООП, dependency injection и другие страшные слова :).
Проектирование кода тоже важная часть и без нее никак. В JavaScript програмимрование без понимания как тестировать этот код — тоже никак. Точнее до определенного уровня сложности можно — потом все разваливается. Особенно если проект длится долго и его ведет более чем 1 программист.
Да и программа на JavaScript написанная с аснихронным кодом внутри — это функция с множественными точками выхода 90% программистов мыслят одной точкой входа и одной точкой выхода — тут такого (традиционно) нет. 95% тех кто сталкивается с этой проблемой впервые напоминают ёжика из анекдота (который косит план когда ему на голову наступил медведь). Пытаясь втиснуть множественные точки выхода к привычной одной и судороржно пытаясь вернуть значение из аснихронной функции. Другими словами, если вы вычитали из базы данных 2000 записей и каждую из них пытаетесь обработать отдельно-асинхронно — то имеете эквивалент 2000 одновременно запущенных потоков выполнения каждый из которых чем-то когда-то заканчивается.
Вопрос ведь в том, как в 2х мерном редаторе (строка + столбец) писать многомерный код и вариант с промисами далеко не самый худший (особенно если сравнивать с подходом var done = _.after(2000,… ).
vintage
19.07.2016 13:16+1код с if вобщем-то с кучей багов, апример var model
Вам показалось.
Без тестов вообще ничего не получится.
При чём тут вообще тесты?
Дальше будет просто:
storePasswordForExistingUserOrCreateNewUserWithDefaultPassword( userId, userPassword ){ if( userId ){ return db.updateUserPassword( userId, passwordEncoder.hashPassword( userPassword )) } else { return db.createNewUser( passwordEncoder.generateDefaultHashedPassword() ) } }
Идём дальше. Что вы будете делать, когда passwordEncoder тоже потребуется сделать асинхронным? (Многие криптографические функции, внезапно, асинхронные.)
atc
19.07.2016 13:46Можно сделать .then внутри метода и вернуть promise, с точки зрения читабельности кода это ничего не испортит. Очевидно, что никакая декомпозиция полностью не спасет от callback\promise в асинхронном коде, но по крайней мере при должном уровне декомпозиции — они не доставят проблем.
vintage
19.07.2016 14:02+1Если это не испортит читабельность, то почему вам лень привести пример такого кода? ;-)
faiwer
19.07.2016 13:48return passwordEncoder .hashPassword( userPassword ) .then(hash => db.updateUserPassword( userId, hash ))
К тому же,
hashPassword
вероятнее всего будет внутриupdateUserPassword
. Так же как и вcreateNewUser
.
AxVPast
19.07.2016 15:37>При чём тут вообще тесты?
Программирование на NodeJS напоминает курение рядом с полностью заправленной топливом ракетой. Если все будет ОК — долетит до Плутона (в остальных языках программирования — разве, что перепрыгнет через соседнюю канаву и это уже будет большим достижением), если шарахнет — мало не покажется :). Если Ваш кода рботает с СУБД и вы получили 100% CPU Load, значит рядом стояших 2 клластера с СУБД уже угнулись насмерть.
К чему это я? К тому что ракеты преимущественно летают ввиду наличия рабочей системы контроля качества. И если перед стартом автотесты не проходят — старт отменяют.vintage
19.07.2016 15:41+1Я не спрашивал "Зачем нужны тесты?". Я спросил "Какое отношение имеют тесты к обсуждаемому вопросу?"
AxVPast
19.07.2016 15:50Код изначально должен проектироваться с учетом возможности тестирования на всех уровнях.
Вариант — напишем как есть сейчас, а потом злой дракон заставит нас написать тесты обычно не работает. Точнее написать тесты на код, который не проектировался с учетом тестирования это ад и яд. На это тесты никто не пишет. Как следствие такой код «иногда работает».
Приведенный пример достаточно хорош, чтобы показать, что его уже трудно тестировать — нужно писать мок на data, и вообще не понятно как писать мок на Model.
Впрочем, теперь подумайте — как Вы на этот код напишите тест? И не надо говорить, что это не предмет обсуждения :).vintage
19.07.2016 16:43+2Это не предмет обсуждения, но раз тема тестов для вас интересней, то давайте затронем и её :-) очень часто разработчики пишут модульные тесты и на этом тестирование и заканчивается. Но куда более важные тесты — приёмочные. А для них не надо как-то по особому проектировать приложение. Если вы пишете переиспользуемый код (например, библиотеку), то приёмочный тест для этой библиотеки будет одновременно и модульным. Если же вы пишете не переиспользуемый код (например, обработчик конкретного запроса пользователя), то от модульного теста тут мало пользы, ведь тут важнее не то, что он правильно работает в изолированном окружении, а то, что он правильно работает в именно вашем окружении.
В целом я склоняюсь к тому, что хороший код — это наиболее простой и понятный код. И вместо того, чтобы его усложнять, чтобы его легче было тестировать, лучше подумать как улучшить тестовый фреймворк, чтобы для тестирования не приходилось усложнять код.
Это не отменяет необходимость грамотного проектирования. Но не модульное тестирование должно быть во главе угла при проектирования всё же.
AxVPast
19.07.2016 23:03очень часто разработчики НЕ пишут НИКАКИЕ тесты и на этом тестирование и заканчивается
Потом приходит дракон-МЕНЕДЖЕР (в чьём там теле должен жить Дракон, который заставляет Вас писать тесты?) и принуждает людей писать тесты на адски написанный код.
Пример https://habrahabr.ru/post/305832/reply_to=9706834#comment_9706778 во многом реалистичен и красиво показывает как написть код так, чтобы его можно было тестировать только ручными приёмочными тестами и этот код все равно после этого будет падать в продакшине, а поддерживать его будет адским мучением даже для самого автора.
Правильное приёмочное тестирование = 1 (один) BRT тест после выхода совершенно нового приложения в продакшин и последующей его работой без ошибок на протяжении нескольких месяцев.
Про дизайн — приведу пример функции:
function killHumanOrNot( params ){ if( params.a > params.b ){ return params.c == params.d; } else { if( params.a == 7 ){ return params.k % 10 == 3; } else { return params.z < params.k; } } }
Теперь вопрос — как с 1 раза написать тест на это. Из очевидного — функция считает убить человека или нет :). То есть финальное приёмочное тестирование, в случае ошибки, предусматривает физическую смерть реального физического человека.vintage
19.07.2016 23:51AxVPast
20.07.2016 00:21:) Это я тоже люблю :) Приходит человек на собеседование. Живенько так рассказывает про TDD, уровни тестирования, паттерны — то есть книжки читал вроде-бы.
Через неделю работы проверяем результат — нет ни одного теста или такое:
describe("test", function (){ it("a=b+c", function (){ assert.equal( x(1,2), 3); }); });
Дело в том, что если все сделано правильно, то 70% codebase это именно тесты. И написание тестов, с моей точки зрения, как раз шикарный способ научиться писать работающий асинхронный код. Так как если не писать тесты, то прошляпить какой-нить аснихронный вызов вполне даже можно и оно будет некоторое время работать (правда периодически процесс необъяснимо вылетает). А вот тестовый фреймворк типа mocha тебе такого не простит.
Aries_ua
19.07.2016 09:42Давай попробую переписать для 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(); });
Как по мне, не так уж и страшно. Но обычно поиск я выношу с саму модель, и проверка на найдено / не найдено уже скрыта. Поэтому в первоначальном варианте я привел без проверки на найденную модель.vintage
19.07.2016 13:32+1- Кода получилось в 2 раза больше (и не менее чем в 2 раза больше вероятность допустить ошибку).
- Лесенка с 1 уровня разрослась до 6 (а мёржить такие вереницы — одно удовольствие).
- Вам потребовалось 3 дополнительных замыкания (они не бесплатны).
- У вас реализована совершенно друга логика (исходно проверялось передан ли id и, если нет, то модель создавалась, а не искалась).
Aries_ua
19.07.2016 14:10По поводу кода, я всего лишь хотел показать, что не так страшны промисы, и с ними можно красиво работать.
Что касаемо кода — то это просто пример, как можно оформить код промисами. И код не превращен в жуткую лапшу.vintage
19.07.2016 14:36+1Боюсь даже представить, что для вас "жуткая лапша", если этот код демонстрирует "красивую работу". :-) Вот объясните мне, зачем усложнять себе и другим жизнь, если можно этого не делать?
Aries_ua
19.07.2016 15:48Судя по нашей дискуссии, читаемость / не читаемость цепочек — индивидуально.
Если честно, даже аргументировать мне вам нечем. :-) Я просто вижу цепочку и понимаю что происходит. Так же и программеры в моей команде свободно читают и понимают цепочки.vintage
19.07.2016 16:47+1Это мне напоминает слова адептов Пунто Свичера: "Зачем мне ваш десятипальцевый слепой метод? Я и двумя пальцами огого как быстро набираю текст!".
Aries_ua
19.07.2016 16:56Если человек не может мыслить и представить код в асинхронном стиле, а только в линейном. То это проблема не языка, а человека.
Ну не может он. Тут только одно — выбрать другой язык.
Мы проблем с читаемостью и пониманием кода не имеем. Ваши доводы, что код нечитаем — поверьте, всего лишь только ваши личные доводы.
В принципе не ради ходивара все затевалось. Я только хотел показать в начале, что цепочки не так уж и страшны. А юзать их или что другое — выбор либо человека, либо команды.faiwer
19.07.2016 18:47+1Не надо юлить ;) Код на fiber-ах и async-ах объективно проще и понятнее. Так же как и синхронный код куда проще и понятнее, нежели всё остальное. Тут личные доводы вовсе не причём :) Речь же идёт не о табах и пробелах.
Aries_ua
19.07.2016 19:44Голословное заявление.
Мне код на промисах понятен. Команде код понятен. Скажите, что нам сделать, что бы понять вашу точку зрения?faiwer
19.07.2016 20:11+1Ну дык есть некоторая разница между словами "понятен" и "понятнее". Сравните, скажем, код на Promise-ах с синхронной реализацией. Даже не знаю о чём тут спорить.
vintage
19.07.2016 20:26+2Боюсь это не излечимо :-) Знаете, вот есть такие индивидуумы (и даже команды в прошлом), которые пишут по несколько операторов в строку. И аргументация у них точно такая же: "Мне же всё понятно! Это вы, убогие, не способны быстро читать неотформатированный код!".
Объективные факты:
- В 2 раза больший объём кода в принципе не может считываться также быстро. Как бы вы ни были натренированы.
- Использование замысловатых конструкций требует специальной дополнительной подготовки от программиста.
- Чтобы не напортачить, требуется особое внимание достаточно высококвалифицированных разработчиков. Типичная ошибка — не залогировать ошибку в конце цепочки.
- Асинхронный код сложнее в отладке. Вы получаете кривые стектрейсы. Вы не можете пройтись дебаггером по шагам. Вы не можете посмотреть значения переменных, выше по стеку, так как давно уже из него вышли.
Уверен, вы на столько круты, что можете писать хоть лесенкой, хоть столбиком, хоть цепочечкой. Но 99% людей — не такие. Чем сложнее конструкции, тем больше они допускают ошибок. Так что код, который требует высокой квалификации — это плохой код. А настоящий профессионализм — не в том, чтобы читать и писать заклинания, а в том, чтобы писать так, чтобы в нём мог разобраться даже дилетант. А где разберётся дилетант — там профи и ошибки не допустит.
Aries_ua
19.07.2016 22:09Еще раз повторю — если избирается подход для написания Promise, то кроме как цепочкой писать не получится. Если не нравятся промисы, есть другие инструменты и подходы.
Повторюсь, я написал цепочку не ради холивара. Не надо доказывать мне, что квадратное круче теплого, или наоборот.
Мир дружба жвачка :-)lega
20.07.2016 20:00В 2 раза больший объём кода, Асинхронный код сложнее в отладке...
В данной ветке речь про сложность кода, и у асинхронного кода она выше, на написание кода тратиться больше времени, на отладку больше времени, выше вероятность ошибки, вместо обдумывания логики приложения вы тратите время на обдумывание как построить асинхронный код, в итоге на завершение потребуется больше времени (2 года вместо одного например) и более дорогие разработчики, а производительность может быть ещё и ниже чем у синхронного кода (сюрприз?).
Поэтому асинхронный код не не нужно использовать везде подряд, а там где он нужен.
Боюсь это не излечимо :-)
Нет, осознание иногда приходит (после года, два, пять), данная статья (и другие подобные) тому подтверждение. Разработчик вам не поверит, ему нужно это пережить.
bromzh
19.07.2016 20:09А зачем увеличивать уровень вложенности и писать then внутри другого then? Одним уровнем не обойтись?
Demogor
19.07.2016 09:57Я еще использую небольшой хак-обертку для генерации 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);
Правда, не во всех случаях такое решение подойдет, но вообще довольно удобно.vintage
19.07.2016 13:36Demogor
19.07.2016 13:52Свои костыли писать полезно иногда. А за совет — спасибо.
И, если не секрет — в чем стремность _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 стремен, тут не поспоришь.vintage
19.07.2016 14:06И название стрёмное. И документацию не найти. И чёрт его знает, что там в реализации. Судя по всему это то же, что и "co", но с меньшими возможностями.
Demogor
19.07.2016 14:17Это вообще самопальная функция-переделка в 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()); }
vintage
19.07.2016 14:49Самопальная функция, возвращающая иногда промис, иногда что-то ещё, а иногда кидающая исключение. Что плохого в одной небольшой зависимости? Она всего чуть более 200 строк.
bromzh
19.07.2016 14:50Самопальная функция, возвращающая иногда промис, иногда что-то ещё, а иногда кидающая исключение
Зато сколько азарта и веселья (при дебаге)!
Demogor
19.07.2016 15:01Так и есть)
Что до возврата всего подряд — если внимательно просмотреть листинг функции, то станет понятно, что она проходится по всем елдам внутри generatorFactory, вываливая exception в случае, если в Promise сработает reject.
Как эта хреновина работает — можно посмотреть здесь — https://davidwalsh.name/async-generators либо тут(похоже именно здесь эту функцию я и брал) — http://bhashitparikh.com/2014/06/04/better-jquery-ajax-with-es6-generators.html.
alvyn
20.07.2016 15:00Почему fibers предпочтительней co?
vintage
20.07.2016 20:15- Быстрее.
- Не требует оборачивания всех функций в обёртку.
- Адекватные стектрейсы.
alvyn
22.07.2016 14:11Некоторые считают , что нетребовательность волокон к оборачиванию всех функций — это не безусловный позитив.
alvyn
22.07.2016 14:28Выходит, может возникнуть такая ситуация, когда будет не очевидно, что код, который вызывается глубоко внутри нескольких вложенных функций, на самом деле, выполняет что то асинхронно.
В концепции async await или co, поведение всегда очевидно.vintage
22.07.2016 15:12Ну а с промисами может возникнуть такая ситуация, когда будет не очевидно, что код, который вызывается глубоко внутри нескольких вложенных промисов, на самом деле, выполняет что то синхронно.
А "некоторые" — не очень хорошие архитекторы. Проблема, описанная там, наблюдается даже в полностью синхронном коде — ничто не мешает где-то в глубине функции query вызвать колбек, который меняет глобальную переменную requestCount. Так что async-await никак не спасают от необходимости осторожно обращаться с глобальным состоянием.
alvyn
22.07.2016 17:12Глобальная переменная там все таки только для примера и наглядности того, как с fiberами легко создать себе race condition. В полностью синхронном коде race condition не бывает.
Просто fiberы этослишкомочень гибкая вещь. Как я понимаю, можно изменить поведение функции, сделав ее асинхронной, при этом никак не изменяя ее вызовы, они будут также работать. Это конечно круто, но, мне кажется, все таки безопаснее, когда в самом вызове функции видно, что вызов этот асинхронный.
В случае с генераторами, как раз придется еще вносить изменения в вызывающие функции, что конечно раздражает, но такой код становится более явным и предсказуемым, а это плюс.
Короче, await в вызове async функции — это совсем не избыточная информация в синтаксисе, он нужен для того, чтобы прочитав код, мое представление о нем соответствовало тому, что он на самом деле делает:) А fiberы создают иллюзии ;)vintage
22.07.2016 21:34Любой race condition — следствие неправильной работы с разделяемым ресурсом. Глобальная переменная — частный случай. Да, в синхронном коде она не будет случайной, как в параллельном. Но суть ошибки та же самая — мы прочитали значение, никак его не залочив, потом другой код, которому мы передали управление (вызов функции/обращение к свойству/запуск сигнала/yield), его изменил, а потом мы записали новое значение, затерев чужие. async/await не спасёт вас от такого рода ошибок.
Мы проходили уже то же самое с обработкой ошибок:
- Сначала функции возвращали результат или ошибку.
- Выяснилось, что любая функция может попасть в исключительную ситуацию и должна вернуть ошибку.
- Весь код получался усеял копипастой вида "если функция вернула ошибку, нужно тоже вернуть ошибку. И так после вызова каждой функции.
- Потом додумались, что если в 99% случаев обработка ошибки заключается в том, чтобы пробросить её выше по стеку, то логично сделать это поведением по умолчанию.
- Пришли к соглашению, что любой код в любом месте может быть прерван исключением и нужно писать свой код имея это ввиду. А для изменения поведения по умолчанию ввели конструкцию try-catch.
Также и с асинхронностью. 90% функций так или иначе могут привести к необходимости произвести асинхронную операцию. Это не какая-то исключительная, а вполне себе типичная операция. А специальный синтаксис имеет смысл вводить лишь для особых ситуаций. Например, когда нам надо, чтобы никто не изменил переменную без нашего ведома:
exclusive( requestCount ) { //никто, кроме кода этого блока не может изменить переменную var nextValue = requestCount + 1 // посчитали новое значение, но почему-то сразу его не записали // можем вызывать что угодно и даже засыпать exclusiveRequestCount = nextCount // установили новое значение } // освободили глобальную переменную.
alvyn
22.07.2016 14:51+1Еще одно субъективное замечание: node-fibers — это все таки что-то из области черной магии), если сравнивать его с co (200+ срок на чистом js).
Написан он на С, код с использованием fibers тоже выглядит шаманством) В общем, это не просто js библиотека и это настораживает.
К тому же, fibers уже не станут мэйнстримом, так как node.js отчетливо движется в сторону async await. А наличие камьюнити в наше время решает.vintage
22.07.2016 15:21-1Поэтому и надо популяризовывать правильные решения. К сожалению у JS нет толкового диктатора. А власть толпы ни до чего хорошего никогда не доводит. Чего только стоит цирк с пропертями:
- Была простая концепция: всё есть объекты и у объектов есть поля.
- Добавили Object.defineProperty и кучу свойств у каждого поля: перечеслимое, изменяемое, функция получения значения, функция установки значения.
- Добавили кучу мета методов в Object.prototype.
- Перенесли часть из них в Object.
- Добавили Symbols. Теперь у каждого объекта есть поля по строковым ключам и по символьным.
- Добавили Reflection со всем тем же самым.
Куда движется JS? В сторону PHP? ;-)
ko11ega
23.07.2016 17:20Правильное решение это 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 ;-)
vintage
24.07.2016 02:10Почему не Haxe?
ko11ega
24.07.2016 12:24Haxe — нельзя отлаживать без компиляции.
Dart — можно делать полноценную отладку в IDE без компиляции(с возможностью посмотреть значение любой переменной на брейкпоинте просто наведя на нее мышь). В Dartium встроена Dart VM. Изменили код, нажали F5 в Dartium. Разработка быстрее и удобнее.
Серверный код вообще не надо компилировать, он работает в DartVM быстрее и устойчивее чем JS на Node.
И на Haxe не пишут Angular ))
Приложение на AngularDart(которое транслируется в JS) работает быстрее чем AngularJS вариант, что как бы говорит о качестве компиляции(в команде Dart люди которые Chrome разрабатывали). Которой Haxe в силу основного упора на многоплатформенность похвастаться не сможет даже в теории.vintage
24.07.2016 14:07- Компиляция происходит в любом случае. На сервере или в браузере.
- Haxe тоже можно отлаживать из IDE с просмотром значений переменных, брейкпоинтами и прочим.
- Возможность отладки в браузере, которым никто не пользуется — так себе фича.
- Только что вы кичились трансформерами — они тоже в браузере налету применяются?
- Ангуляр, что первый, что второй — редкостная порнография.
- Свежо предание. http://qiita.com/laco0416/items/df2e32787b36a01c3d6d
- Зато на Haxe можно нативные мобильные приложухи пилить.
ko11ega
25.07.2016 00:35+1- "Компиляция происходит в любом случае. На сервере или в браузере." — Обоснуйте пожалуйста. Я утверждаю, что на сервере Dart код выполняется в Dart VM без компиляции.
- В случае с компиляцией Haxe в JS, отладку можно будет делать только после компиляции(иначе как то что отлаживается будет взаимодействовать с DOM), так?
- Dartium = Chrome-V8+DartVM и корректные полифилы для всех остальных браузеров в SDK присутствуют. Так что фича рабочая.
- Трансформеры это про кодогенерацию. Выполняет их pub. Бывают проекты и без нее. Необходимый трансформер это например dart2js — нужен только перед выкладкой, в процессе отладки — не запускается. Или трансформер нужен, например, при работе с Polymer.
- Интернет из фо порн ;-)
- Предание 22 октября 2014 и что? Сейчас речь идет про Angular2
- 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?
vintage
25.07.2016 15:19- Без компиляции исполняются лишь машинные коды. Остальное либо транслируется, либо интерпретируется.
- Разумеется.
- Да-да, тут для JS-то не всегда полифил адекватный есть. Взять тот же WebRTC2.
- То есть препроцессинг всё равно будет.
- В данном случае, это — зоопедонекропорно.
- Пруфлинки приветствуются.
- Оно компилируется в основной язык целевой платформы с возможностью прозрачно и эффективно использовать все её возможности?
- Одна кодовая база транслируется в разные языки. Можно использоваться платформозависимые API на полную катушку.
DexterHD
18.07.2016 14:48+1Щас понабегут проповедники и религиозные фанатики и заминусуют пост, и кучу комметов в довесок.
Да как ты посмел обидеть ноду :D
wentout
18.07.2016 14:54-1Привет КЭП :)
DexterHD
18.07.2016 14:58-5Простите забыл перелогинится. :D
wentout
18.07.2016 15:49Да не, так нормально тоже :)
Далее Обращаюсь НЕ к автору предыдущего комментария…
Вообще, конечно, хотел просто ничего не писать, но это, что на пустом месте за кому-то показавшимся неудачным или неуместным юмор можно отхватить — надоело. Оно понятно, что «петросянство», но, что с Вами не так?
God Mode On — вообще не понимаю, что Вам с того, что двое в каментах решили потроллить друг друга?
Вообще, я люблю JS, но я одинаково не понимаю И Хейтеров И Обожателей. И если Вы подумаете хоть немного, то поймёте, что «Привет КЭП» — относится и к тем и к другим. Впрочем как и исходный комментарий про «сейчас набегут». Оба враждующих лагеря могут поразжигать на этой теме :)
Т.е., я не хожу и не рассказываю всем как я Люблю или Не Люблю ноду, JS и всё, что с этим связано. Коллеги, ни в любви ни в ненависти к JS нет ничего прикольного!
ИМХО — пытаться понять JS через парадигмы других ЯП — гиблое дело. Из комментария ниже про 300 мегабайт node_modules — в этом нет ничего плохого. Ничего хорошего тоже нет, но и плохого нет. Это просто так, как есть.
Считайте, что это такой вот DZEN.DexterHD
18.07.2016 17:10+3Из комментария ниже про 300 мегабайт node_modules — в этом нет ничего плохого. Ничего хорошего тоже нет, но и плохого нет. Это просто так, как есть.
Когда CI на каждый коммит вытаскивает репозиторий и потом накатывает вендоринг, это очень долго и очень много трафика утекает.
На каждый коммит по 300Мб с инета тянуть это плохо.sosnovskyas
19.07.2016 18:09+1А как насчёт кеширования node_modules например? Не путь самурая? Если флоу дорос до использования CI то и подходы кеширования использовать не грех. Достала CI тот же package.json, сравнила версии либ, если ничего нового, используем то что в кеше. Да и на каждый commit нет повода её дёргать. Скорее на каждый pull request. Это не так часто как могло бы показаться.
DexterHD
18.07.2016 15:04+5Добавлю своей боли. У нас npm используется для разработки и сборки фронта. Так вот фронт написан на ReactJS. Собирается все с помощью gulp и вот в чем боль. Зависимости для работы всего этого (каталог node_modules) занимают 120Мб в проекте. Это с третим npm. Со вторым вообще 800Мб. Извините но это Адъ. 800Мб Карл, просто чтобы собрать фронтэнд. Так для справки кодовая база (js, php) без вендоринга весит 22Мб. Вендор php (42 Мб) и папка с пакетами ноды 120Мб. Wft? Причем там ничего не используется в продакшене из этого каталога. Вообще ни чего. (babel, gulp, react, webpack — вот эти 4 штуки вытягивают 100Мб зависимостей)
yeti357
18.07.2016 15:45+1Попробуйте устанавливать с флагом production. npm install --production. Просто по умолчанию установки пакетов тянутся зависимости нужные только при разработке этих пакетов, вроде eslint и прочих, но без которых можно обойтись, если использовать просто как пакеты.
fetis26
18.07.2016 16:18-1Так а в чем боль? Вам место жалко на диске или вы храните node_modules в репозитории.
DexterHD
18.07.2016 17:11+2Я с помощью CI запускаю сборку и тесты на каждый коммит. И каждый раз заново пулю репозиторий и заново все накатываю, чтобы никакие кеши не дали сайд эффектов. Да мне жалко на каждый коммит тащить с инета половину интернета. Конечно в случае разработки без CI и без автоматизированного тестирования проекта все супер. Но х х и в продакшен не наш путь. Каждый раз приходится ждать по 2-5 минут пока нода соизволит выкачать пол мира, а хочется быстрой обратной связи.
spuf
18.07.2016 20:30-1Вы что-то делаете не так. У меня тоже тесты на каждый коммит, но node_modules закеширован и обновляется только если изменится packages.json и/или npm-shrinkwrap.json.
DexterHD
18.07.2016 21:51Поясняю:
1) Каждый раз при коммите CI делает git clone (в отдельный каталог)
2) Далее переходит в каталог с кодом и устанавливает зависимости
3) После этого производит сборку пректа
4) После этого запускает тесты
5) После этого анализирует результаты тестирования
Теперь вопрос, что я делаю не так?faiwer
19.07.2016 06:45Угу. Учитывая что
node_modules
создаёт такие проблемы при таком подходе, а отnpm
вы отказываться не собираетесь, ну так откажитесь от этой схемы в пользу другой. Как-минимум п.2 можно усложнить и еслиbower.json
,package.json
и пр. не менялись с прошлого раза, то и не грузить ничего заново. У нас пока подгрузка зависимостей занимает 8-10 минут. И это не выносимо долго. Но прибегаем к такой подгрузке только если зависимости были изменены.
wentout
19.07.2016 09:12Не совсем понятно зачем п.2 если node_modules можно положить просто на уровень выше, чем каталог куда делается git clone.
DexterHD
19.07.2016 11:29http://12factor.net/ru/dependencies — приятного чтения
movl
19.07.2016 12:09+21.5) ln -s ../node_modules node_modules (не говоря про то, что npm может пару вариантов для решения еще предложить, типа линковки пакетов, выбора источника и так далее)
2) npm install
Но если у вас требование все делать на чистую и при этом быстро, то тут не проблема того что пакеты много весят, а что-то еще. Потому что для чистоты можно и ноду каждый раз ставить, со всеми зависимостями, а лучше всю систему в целом.
vintage
19.07.2016 13:58+1Docker позволяет легко и просто "ставить систему с нуля". Правда с node_modules — та же беда, но её легко обойти:
- Обновляем репозиторий на хостовой системе.
- Добавляем в контейнер package.json.
- Выполняем в контейнере npm install.
- Добавляем в контейнер остальные исходники.
Таким образом, если package.json не менялся, то будет взят снепшот из кеша с установленными node_modules.
wentout
19.07.2016 15:34Посмотрел: так себе, не очень понял накой это читать. Речь же про тестовую среду, так почему нельзя общие node_modules держать для всех билдов и только при изменении package.json их «перестягивать»?
Что, разве build среда не может узнать что package.json изменился, и сходить на уровень выше, скопировать туда package.json и установить там всё через тупо npm install?
gladilindv
19.07.2016 09:14Все так, но что если зависимости устанавливать глобально?
Репа скачается, а зависимости будут использоваться уже ранее установленныеk12th
19.07.2016 10:49Так и не ставьте зависимости глобально. npm прекрасно работает с локальными зависимостями; более того, это рекомендованный подход.
movl
18.07.2016 21:24Вебпак позволяет за милисекунды билдить проект и на горячую подменять стили, а при опрелеленных условиях и код, и шаблоны, и все что угодно. То есть можно добиться того, что после нажатия на ctrl+s все применится до того как вы успеете взгляд на монитор с браузером перевести. А Ваши претензии сводятся к медленной обратной связи?
А поповоду выкачивания, чтобы не юзать версии из кеша, во избежание сайд-эффектов, это по-моему на уровне паранойи. Нет, стандарты по сборке и тестированию могут быть разными, но тогда чем обусловленно недоверие именно к системе кеширования в npm, при условии использования 120мб стороннего кода?
Зафиксировать версии, один раз проверить сборку после очередного коммита, при смене версий еще раз проверить, все ли подтянулось, обновилось, либо даже автоматический тест написать. Тоже не ваш путь?
Ashur_451
18.07.2016 15:33+1По поводу стандартов для написания JavaScript, могу посоветовать взглянуть на Airbnb JavaScript Style Guide github.com/airbnb/javascript
k12th
18.07.2016 15:50+12Да в том-то и проблема, что этих стандартов как собак нерезаных в квадрате. Airbnb, wikimedia, google, на любой вкус. Точки с запятыми, фигурные скобки и их размещение, отсутствие, наличие и опциональность этих вещей, количество пробелов (или табов), переносы строки — в любом сочетании, оптом и в розницу. ИЧСХ, все неконсистентные и все нелогичные.
Ashur_451
18.07.2016 16:15+2Надо все это смержить, пофиксить конфилкты, стандарт готов ) *шутка*
k12th
18.07.2016 16:21Надо какой-то «JSR». Только от сообщества, а не влажные фантазии неизвестно кого.
fetis26
18.07.2016 16:19Просто выберите, который ближе по душе и используйте. Для других языков разве везде единый стиль?
movl
18.07.2016 16:33Но в этом же и прелесть. Если для человека не достаточно авторитетны стандарты от гугла, или проект входящий в топ 15 по количеству звезд на гитхабе, и он не может сделать свой выбор, или сделать свои гайдлайны с блекджеком и прочими вещами (благо инструментов валом), а предпочитает только от производителя, то это уже идеологические проблемы.
OpieOP
18.07.2016 16:55Самый популярный npm пакет для http запросов с названием «request» у меня при очень большом количестве запросов вызывал утечку памяти, Карл! После длительных мучений и поисков причины утечки проблема была обнаружена именно в этом пакете. После замены «request» на аналог (вроде requestify он назывался) проблема исчезла. Причем в описании самого «requestify» тогда было написано что-то вроде: «Мы понимаем на сколько ужасен „request“ именно поэтому создали этот пакет». После этого я очень аккуратно отношусь к node вцелом и выбору npm пакетов.
bigbn
18.07.2016 19:18+1Вы так говорите, будто в питоне все пакеты кошерные и пишут их академически подкованные, сертифицированные и ВСЕГДА соблюдающие пеп8(а это же стандарт!) люди. Если бы так было, никогда бы не вышел к примеру ConfigParser2. А попробуйте загуглить «python event emitter» и удивитесь сколько пакетов делают одно и то-же с разной степенью кривизны.
А вот взять общеизвестный пакет logger
https://docs.python.org/3/library/logging.html — методы там в camelCase, не совсем по стандарту, но он вроде как не запрещает, но потом ведь и ваш код становится солянкой, где одни методы вызываются так, другие почему-то иначе. А вот если взять тот-же JS так я не могу вспомнить ни одну популярную библиотеку где в последний раз видел, чтобы имена методов были не camelCase. В общем, не надо отбеливать то что белым не является, и очернять то что вовсе не черное, пакеты как и языки написаны людьми и априори не могут быть идеальными.
bigbn
18.07.2016 17:07+4Если вы захотите написать асинхронный неблокирующий сервер на python, к примеру на twisted или tornado, каким образом вы сможете избежать callback-ов, Deferred-oв или yield-ов?
В twisted нету errback-ов? Все само ловится через try/catch?
На node нельзя писать синхронно? Нельзя кластеризировать? А потоки вам при работе с tornado сильно упростят логику?
Работа с асинхронным кодом имеет один и тот же стандарт в twisted и tornado? А стандарт такой вообще есть?lega
18.07.2016 18:20каким образом вы сможете избежать callback-ов, Deferred-oв или yield-ов? Все само ловится через try/catch?
В питоне есть gevent.
На node нельзя писать синхронно?
Вы имеете ввиду блокирующие вызовы или файберы?
один и тот же стандарт в twisted и tornado?
Для этого есть async.io
А вообще «асинхронщина» нужна в 5% случаев если не меньше (по моей статистике), а питон умеет и так и эдак в отличие от ноды.bigbn
18.07.2016 18:59+1https://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 слегка расстраивают.atc
19.07.2016 04:25+1> но вечные нападки неосиляторов со статическим ООП(как будто JS отменял ООП или невозможно прибить все типы гвоздями при необходимости
Так JS не предоставляет почти никаких инструментов, так характерных для ооп языков. Просто пройдусь по списку:
1) Автоматический рефакторинг — нет или очень слабый (для этого IDE должна хорошо понимать код)
2) Инкапсуляция — private\protected в es6 классах можно реализовать только окольными путями
3) Полиморфизм — интерфейсов нет и единственное что мы можем — это намекнуть на структуру ожидаемого объекта другому программисту в jsdoc
4) DI\IoC — никаких constructor injection, только хардкор
5) Возможность не держать в голове типы всех переменных в области видимости — тоже ожидаемо нет
6) Grasp паттерны:
* отделение асинхронного кода от синхронного (контроллер) — сложно и часто не имеет смысла, потому что асинхронно все
* слабое зацепление — сложно, потому что в большинстве случаев слишком многие подробности объекта доступны извне
* устойчивый к изменениям — нет, потому что у нас отсутствует интерфейс, где мы можем задекларировать api
Вот и получается, что ооп как-бы есть, но одновременно его как-бы нет.atc
19.07.2016 04:35Я не сомневаюсь, что со всем этим можно жить (хотя бы потому что сам долго пишу на js), но с современным пониманием ооп — javascript имплементация стыкуется слабо, эдакая «неизвестная земля» на фоне имплементаций в остальных языках.
SirEdvin
18.07.2016 18:24каким образом вы сможете избежать callback-ов, Deferred-oв или yield-ов?
Эм… там все делается через Handler…
В twisted нету errback-ов? Все само ловится через try/catch?
За twisted не скажу, tornado кидает ошибки и, если разработчик нигде ее не обработал, выкинет в лог.
bigbn
18.07.2016 18:46+1Неблокирующий запрос к базе данных внутри Handler-а как будете делать? Взять momoko или motor например, кто будет обрабатывать результат выполнения? Без yield или callback получится?
А вообще тема конечно холиварная и вброс удачный. Для меня лично нет никакой неопределенности какой язык лучше. Писать мне приятно на JS на нем мысль сама вытекает — но это просто дело привычки, код обычно элегантнее, опрятнее и быстрее на питоне(но разумеется не всегда). Оба языка имеют и давно доказали свое право на существование, и у обоих языков рыльце в пушку, в питоне как и в js не все так идеально и радужно как хотелось бы.
lega
18.07.2016 18:42Расскажите о своем опыте?… «перескакивать» назад, на более комфортный язык?
Мой опыт такой:
Года 4 назад я подумал о переходе на ноду, в итоге начал тестовый проект на ноде, что-бы пощупать как оно, асинхронность из «коробки», да и скорость v8 выше. Через несколько дней я понял что оно не сильно лучше чем в питоне (благо я наелся калбеков в питоне к этому времени), а самое главное что асинхронщина нужна далеко не всегда (хотя все* сейчас пытаются её использовать для всего подряд).
В итоге я вернулся в питон. И периодический наблюдаю подобные статьи об уходе с ноды.
Я по прежнему активно использую ноду, но только в качестве тулинга.
forgotten
18.07.2016 18:54-13Что ЭТО вообще делает на главной Хабра?
Ага, всего лишь горстка приличных SQL пакетов. Позднее вы поймете, что все существующие ORM инструменты никуда не годятся и то, что базовый драйвер — это лучший выбор.
Во-первых, вы так говорите «базовый драйвер», как будто это что-то плохое.
Во-вторых, ну уж точно всяких ORM-ов под ноду написано не меньше, чем под всё остальное. Возможно, вместе взятое.
Вы никогда не станете мастером того, что движется с такой, ломающей голову скоростью.
У меня плохая новость для всех вас. Сегодня становится мастером тот, кто умеет изменяться «с ломающей голову скоростью».
Это способ писать код, который выглядит более-менее синхронно, без сумасшедшей 'callback' логики.
Этот пассаж я даже не знаю как комментировать. А что, callback в промисе чем-то отличается от остальных?
Последней каплей было то, что я обнаружил отсутствие стандартов.
Что, серьёзно, кто-то жалуется на недостаточное количество стандартов в JS?
Это всё накаляет… Никто не может сказать, как написать стандартизированный JavaScript-код. Просто забейте в гугле «JavaScript Coding Standards» и вы поймете, что я имею ввиду.
Вероятно, автор имеет в виду то, что он не понимает разницы между кодстайлом и стандартом.
Это проверенный временем вариант с великолепными стандартами, библиотеками, легкой отладкой и стабильной работой.
А напомните инструмент легкой отладки серверного Питона кто-нибудь.SirEdvin
18.07.2016 19:00+3У меня плохая новость для всех вас. Сегодня становится мастером тот, кто умеет изменяться «с ломающей голову скоростью».
Назовите топ-5 полезных библиотек для node.js, которые недавно вышли. Потом расскажите, как они работают и какие у них сложности.
Суть в том, что серьезных сложных библиотек в других языках программирования не появляется так много, как в случае node.js.
Что, серьёзно, кто-то жалуется на недостаточное количество стандартов в JS?
Когда стандартов много, их нет. Возможно, вы не знали об этом?
Вероятно, автор имеет в виду то, что он не понимает разницы между кодстайлом и стандартом.
Вероятно, автор называет кодстайл стандартом. Он же работает на Python, тут есть pep8.
А напомните инструмент легкой отладки серверного Питона кто-нибудь.
Как насчет pydb? PyCharm Ultimate версии отлично умеет в удаленную отладку, только path mapping настроить.
forgotten
18.07.2016 22:20+4> Назовите топ-5 полезных библиотек для node.js, которые недавно вышли.
Ммм. Вообще-то весь nodejs недавно вышел. Ну ок, скажем, React, eslint, jsdoc3, gulp, swagger-tools
Алаверды, назовите топ-5 полезных библиотек для питона, которые недавно вышли.
> Суть в том, что серьезных сложных библиотек в других языках программирования не появляется так много, как в случае node.js.
Это проблема nodejs — в нём появляется слишком много серьёзных сложным библиотек? Серьёзно?
> Когда стандартов много, их нет. Возможно, вы не знали об этом?
Имею некоторое представление. Стандарт на ECMAScript как бы один — тот, который TC39 разрабатывает.
> Как насчет pydb? PyCharm Ultimate версии отлично умеет в удаленную отладку, только path mapping настроить.
Сюрприз — WebStorm точно такую же удалённую отладку Nodejs имеет.
DexterHD
18.07.2016 21:57Пройдите на оригинал статьи и на понятном английском объясните это все автору статьи, сопровождая это ORLY. Посмотрим че из этого выйдет. Это перевод, не понято кому вы это все доказываете тут. Переводчику?
ReklatsMasters
18.07.2016 23:09+2Автор судя по статье просто нытик какой-то. Он даже не касался утечек памяти и оптимизаций. А весь баттхёрт у него в итоге из-за непонимания nodejs как платформы. Control-flow теперь легко управлять с помощью co, ES6 в node@6 поддерживается уже практически полностью. Если он хороши пишет на питоне, ну и писал бы на питоне.
MadJeck
18.07.2016 23:38+1А кстати скажите мне какой плюс nodejs в сравнении скажем с использованием asyncio или tornado на python? Не в производительности и синтаксисе, а именно архитектурно есть разница?
forgotten
19.07.2016 08:45+2Исключая всякие полезные мелочи, типа возможности застримить тело POST-запроса — основное отличие ноды от async/await фреймворков в питоне/C#/вотэва в том, что цель последних — замаскировать асинхронность; ты пишешь типа синхронный код, вся магия скрыта в самом фреймворке. В ноде это не так. Вся асинхронность явно вынесена наружу, и ты должен писать код с пониманием того, как оно работает.
Diverclaim
19.07.2016 10:51Вы видимо непонимаете что такое async/await (который, кстати, есть и в яваскрипте).
Про питон не скажу, но и в C# и в js async await просто удобная синтаксическая конструкция. Того же результата можно добиться и «старыми» методами — .then() в js, .ContinueWith() в C#. И в js и в C# необходимо понимать что пишешь асинхронный код (который с async/await просто выглядит чуть удобнее). Ни C# ни js не маскируют асинхронность ни в каком виде (это попросту невозможно), а просто дают инструмент для более удобной работы с ней.
forgotten
19.07.2016 11:53> Вы видимо непонимаете что такое 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 стиле — просто не понимать, что за механизм тебе дали в руки.Diverclaim
19.07.2016 13:01— Писать в 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;
}
vintage
19.07.2016 14:31+1Ни 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 — как вирусы, они стремительно распространяются по вашему приложению, заставляя в каждую функцию вносить странные изменения. Стоит одну функцию сделать асинхронной и все вызывающие её функции тут же заразятся этой заразой. И никакая функция, которая вызывает хоть какую-то другую функцию или обращается к полям каких либо объектов, не застрахована от такого "переписывания генома".
Diverclaim
19.07.2016 16:00+1— В JS это не просто возможно, но я бы и настоятельно рекомендовал использовать. Приведу пример.
А где пример? Покажите как асинхронный код можно использовать ничего не зная о том что он асинхронный (вроде вызова к серверу, который неизвестно когда выполнится и выполнится ли вообще).
Приведенный вами пример слишком синтетический, поэтому дать какие-то комментарии по нему проблематично. greeter.say — это не асинхронная функция. Вот код который вызывает greeter.say может быть асинхронным (если надо получить пользователя с сервера). А может быть и синхронным (если пользователь уже был получен).
А вообще nodejs пропагандирует асинхронность не просто потому что делать нечего. А для того чтобы не блокировать поток. Если все вызовы будут синхронными тогда на каждый такой вызов будет блокироваться поток выполнения, что значит что на 1 инстансе ноды нельзя будет выполнить более 1 запроса одновременно (все запросы будут выполняться последовательно), а это очевидно никому не нужно.vintage
19.07.2016 16:54+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; } };
И нет, системный поток не будет заблокирован. Заблокирована будет лишь текущая "задача" (например, обработка одного клиентского запроса). И пока она заблокирована, процессор будет заниматься другими задачами (например, другие запросы обрабатывать).
megahertz
19.07.2016 07:27+1Во многом согласен с тезисами, но продолжаю использовать nodejs дальше как основной инструмент. Это весело. Среда очень быстро изменяется, постоянно узнаешь о новых подходах, пересматриваешь привычки, становишься гибче. Но надо понимать, что nodejs достаточно специфичен и не подходит для многих проектов.
reactoranime
19.07.2016 09:13По поводу codestyle есть удобная тулза standardjs.com вначале немного больно, но потом привыкаешь
hcbogdan
19.07.2016 09:15Статья из формата «я не смог использовать _____ для своего проекта» (в данном случае NodeJS).
Нет JS отладчика?
Почему бы не использовать node-inspector.
Теряетесь в JS callback'ах?
Аналогию можно провести к любой платформе, пишите в соответствии с стандартами (всеже Coding Standards и Coding Style это разные вещи). А еще неплохо использовать eslint для Вашего проекта.
Считаете что стать мастером невозможно?
Слишком пессимистичный взгляд, все же стоит повысить свои знания и навыки прежде чем использовать любую технологию «в боевой обстановке».
Считаете что зависимости ненужны вашему проекту?
Создавайте свои приватные пакеты, и тесты для них, в том стиле, который нужен вашему проекту. Никто не заставляет вас пользоваться уже готовым.
Что по поводу создание сборки grunt/gulp/webpack?
Использовать Grunt в новых проектах, еще и для backend-сервера — для чего это может пригодиться )?
Почему бы не использовать всю силу webpack для отсеивания «неиспользуемого кода» и предварительных сборок?
Что по поводу native-драйверов и ORM?
То что описано в статье актуально и для других платформ. А именно существует немалое количество библиотек «сомнительного качества». Но никто не заставляет вас использовать именно их. Если ваш проект стоящий, почему бы не уделить больше внимания при выборе библиотек (много это не значит плохо), а еще лучше копнуть глубже — прямо в исходные коды библиотек.
О транзакция, Postgres а MongoDb.
Тут стоит заметить, что прежде чем выбирать СУБД для проекта, стоит задать вопрос, а нужна ли она вообще (вполне можно написать api и работать на его базе не оперируя с базой)? Возможно вы пытаетесь применить те подходы которые не актуальны для данной платформы (и соответственно удивляетесь почему они тут не работоспособны).
Вы потратили на NodeJS слишком много времени?
В этом случае вам стоило задуматься об обучении. Вы ведь не садитесь в самолет пытаясь им управлять, без обучения и инструктора? Как и в любой платформе — стоит выделить время на обучение и реализации отдельно от времени внедрения. Посмотрите на код который вы писали во время обучения, скорей всего вам будет неуютно (он далек от идеала).
wert_lex
19.07.2016 09:15+2Поделюсь своим опытом. Примерно год на 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] и как-то легче становится :)
Format-X22
19.07.2016 10:51+2Почему-то все кто пишут о минусах NodeJS всегда пишут о том что получается лапша из колбеков. Но… почему бы не писать эту лапшу? И почему считается что лапша решается использованием промисов? Ведь можно просто… делить шаги на отдельные функции/методы. А не пихать всё в один супер-метод с десятком колбеков, будь то просто функции или использование промисов. Думаю большинству тех кто пишет о таких проблемах необходимо срочно прочитать книгу Чистый код. И хоть речь там идет о Java — эта книга быстро вылечит от проблем с какими-то там вложенными колбеками, промисами и всем всем всем.
Пишу код на NodeJS и не имею проблем с колбек хэлом. При этом не использую ни промисы, ни генераторы, на обычном ES5 всё выглядит как аккуратные классы с методами, в которых нет пирамидок из колбеков и лапши из промисов. И всё работает. И это при логике, в которой по 5 различных обращений в БД за раз, множество колбеков и всего такого асинхронного. И код выглядит хорошо!
Ну и если эта книга, полностью прочитанная и осознанная, не спасет от колбек хэлла — возможно стоит задуматься о правильности выбранной архитектуры?wert_lex
19.07.2016 12:04+1Лапша решается промисами, потому что позволяет избежать nesting. В целом суть одна, но промисы при этом — мало того что удобная обертка, но еще и эдакий кирпичик асинхронного строения.
nep
19.07.2016 14:06Поддержу. Аналогично — мы у себя не используем промисы. И при этом никакой лапши нет. Правда спастись от нее помогла не книга, а одна простая статья «Лапша» из callback-ов — будьте проще (очередное спасибо автору).
vintage
19.07.2016 15:01+1Пример кода не приведёте? ;-)
А лапша — это не лесенка. Лапша — это когда вы разбиваете одну функцию на 10 не потому, что это логически обосновано, а потому, что иначе поток заблокируется на неопределённый срок. А лесенка — это лапша с горкой.
Format-X22
19.07.2016 15:56Например вот так.
Не то чтобы божественный пример, но рабочий код, без вырезок, переживший несколько доработок и крутящийся на продакшене.
Можно было бы конечно что-то более стремящееся к треугольности в плане колбеков, но либо не безопасно, либо не ищется с ходу.
Отмечу что использую классовый движок 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) ); } } });
vintage
19.07.2016 18:51Я тут сократил ваш код в 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 || '' , ] } }
Format-X22
19.07.2016 18:54Да, похоже мне всё-таки стоит глубже изучить новые стандарты.
Спасибо за развернутый ответ.
faiwer
19.07.2016 19:22+1Во всех ваших примерах у меня глаз цепляется за
var
? Вы ведь даже декораторы используете (которые ещё фиг его знает будут ли в языке), ноvar
… Он же ужасен. Зачем?vintage
19.07.2016 20:42И чем же он ужасен?
faiwer
19.07.2016 20:50Как раз тем, в чём заключается его разница с
let
. У него не блочная область видимости и он всплывает. Хуже, на моей памяти, только объявление переменных вphp
(кажется именно поэтому там приходится использоватьuse
в анонимках). Для чего же его использовать? Экономия строк в подобном коде:
if(some) var a = 1; else a = 2;
?
vintage
19.07.2016 21:13
Тут нас спрашивают «к какому из двух объявлений перейти»?
Обычно всё же интересует место инициализации переменной, а не первое её упоминание.faiwer
19.07.2016 21:18+1Ясно. На мой взгляд это преимущество притянуто за уши. Замечу, что js-линтеры будут пытаться выжечь такой код огнём из-за двойного определения одной и той же переменной. К тому же первое упоминание даст хорошее представление об области применения этой переменной.
Но мысль понял, спасибо :)
P.S. такие огромные скриншоты лучше под спойлер.vintage
19.07.2016 21:31+1Ну, это помимо экономии строки (а то и двух) и необходимости разделять работу с переменной на 3 части (объявление, инициализация, получение значения).
У JS-линтеров, к сожалению, много глупых правил. Из-за динамической природы JS сколь-нибудь сложный статический анализ невозможен. Вот и анализируют, что могут — расположение пробелов, фигурных скобочек и прочие декоративные штуки.
Объявление переменных (и функций, кстати, тоже) на уровне функции — вполне себе разумный компромис между строгостью и удобством. Ограничение области видимости фигурными скобками может быть полезно лишь в немногих случаях:
1. В огромных сложных функциях. Но огромные функции сами по себе — антипаттерн.
2. В циклах, при использовании их в созданных в циклах замыканиях. Но создавать замыкания в цикле, опять же — антипаттерн.faiwer
19.07.2016 21:43+1Мысль ясна. Я же исхожу больше из очевидности и понятности кода. Использую const всегда, когда не требуется повторного присваивания (т.е. практически всегда). Повторного же присваивания избегаю в пользу разных переменных (чаще всего задача у переменной при этом меняется, а соответственно и имя стоит изменить). В оставшихся ситуациях применяю let. В тех редких случаях, когда я вынужден выносить декларацию за пределы инициализации (когда их может быть несколько), считаю это платой за очевидность области работы переменной. Что вполне уместно, т.к. код с несколькими точками инициализации переменной уже не так уж и очевиден.
Учитывая, что я как и вы разделяю подход, заключающийся в том, что код должен быть достаточно простым и ясным, и без каких-либо не очевидных моментов, считаю это наиболее оптимальным вариантом. Области применения для var для себя не нашёл. Считаю его архаичным потенциально баго-генерирующим мусором.
vintage
19.07.2016 22:05Я в целом-то согласен и пробовал постоянно использовать 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 }
AxVPast
19.07.2016 15:43>лапша решается использованием промисов
Поддеру идею. Если лапша в голове — никакие промисы не спасут.
Fen1kz
Есть опыт среднего проекта на node.js в связке с безумной базой Marklogic (NoSQL + RDF/triples). В том числе этот сервер на ноде умеет конвертировать модели в SPARQL-запросы и обратно, т.е. логики там достаточно.
В общем, впечатление довольно положительное, единственно что смутило (и в чем в статье ни слова), это то что можно отстрелить себе ногу и не заметить этого. Например первый месяц у нас не работали транзакции, из-за пары строчек. При этом мы не знали об этом. Т.е. всё работало, но вот совсем не так как мы ожидали. Полез проверять: "ой, а вы в курсе что у нас все запрсоы к бд без транзакций?"
Это пугает.
Шутка про node.JS: "Вы начинаете асинхронно стрелять из асинхронных рук в асинхронные ноги, асинхронно не попадаете и запутываетесь в этой каше." довольно точная — если вы начинаете без четкой системы или представления как все сделать, то "вы начинаете", js позволяет начать, а дальше вы запутываетесь. Это плата за гибкость.
faiwer
Вопрос про транзакции. Причина, по которой у вас случилась такая ошибка была в JS? В NodeJS? Или всё-таки дело в устройстве библиотеки, её API или чего-нибудь в таком духе?
Fen1kz
Дело было в нашей невнимательности и всепрощении js, библиотека тут только при том, что пришлось писать хелпер для транзакций, который и не работал (точнее — открывал/закрывал транзакцию, а запросы к базе делал без указания её ID).
В js всё может работать даже при серьезном системном баге, вместо того чтобы свалиться, и это типа плохо. Соответственно, требования к пониманию, что именно ты делаешь, возрастают.