Изобилие выбора

Когда я пытаюсь освоиться с инструментами, библиотеками, фреймворками и механизмами шаблонов JavaScript для веб-приложений, я узнаю одну вещь: фреймворки, которые защищают вас от сложностей, связанных с знанием многого о функциях, которые они предоставляют, часто не действительно ограждает вас от необходимости изучать эти вещи в любом случае. Да, это так, когда вы смотрите на более простой пример приложения или проходите курс по предмету, но когда вы начинаете кодировать что-то свое, более значимое, использующее библиотеку или немного промежуточного программного обеспечения (для например, Passport.js), на самом деле это не защитит вас от изучения этого стороннего кода. Вам все еще нужно понимать Passport.js, или обещания (или асинхронность/ожидание), или сеансы, или локальное хранилище, или Redis, или Mongo, или socket.io, или EJS, или Vue, или Ember, или Handlebars, или что-то еще… Жестокая правда в том, что JavaScript веб-разработчикам необходимо хорошо знать многие из них и иметь общее представление об остальных, а также о многих, многих других фреймворках, промежуточном программном обеспечении и библиотеках. И я даже не включил сюда настоящие цепочки инструментов.

Теперь я думаю, что эти «простые» и самоуверенные инструменты и фреймворки на самом деле делают очень хорошо, так это дают хорошие рекомендации о том, что использовать, от людей, которые уже прошли этот путь. Они и их пользователи часто разрабатывают экосистему, которая является частью фреймворка или построена вокруг фреймворка, которая представляет все, что вам может понадобиться, и самые популярные надстройки. Здесь есть сила в числах, поэтому вы можете быть в безопасности, если выберете их.

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

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

Меняющийся ландшафт — Async/Await

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

Однако на этот раз я чувствую, что наконец-то пришло время обновиться до Node.js 7 и начать использовать async/await, чтобы сделать мой код более линейным, читабельным и удобным в сопровождении. Эта функция async/await появится в версии JavaScript ES7, уже есть в браузере Edge и является функцией Node.js, начиная с версии 7.6.0 (текущая версия на момент написания этой статьи — 7.10.0). ).

Обещания так 2016, amiright? Хм. (Подробнее см. в этой статье.)

Недавно я ответил на вопрос на Reddit, который теоретически был о Koa.js, но я думаю, что на самом деле он был больше о сравнении некоторого кода async/await ES7 с тем, что было бы необходимо без него (то есть с промисами). Я выделил линейный, не вложенный, callback/then free readable характер версии async/await:

Попробуйте кодировать этот пример с промисами:

var result1 = await func1();
var result2 = await func2(result1);

это настолько просто, насколько это возможно. Теперь добавим параллельную обработку нескольких асинхронных запросов:

const items = await fetchItems();
for (let item of items) {
const result = await processItem(item);
console.log(result);
}

Опять же, это линейный код. Нет обратного ада. Здесь легко увидеть логику и гораздо, намного более короткий код. Для обработки ошибок вы оборачиваете его в try/catch, и все, вы можете развернуть этот читаемый, поддерживаемый код.

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

И самое главное, здесь нет вызовов функций, связанных с промисами, концепция даже не видна (даже если так реализовано await).

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

const Koa = require('koa');
const app = new Koa();

// x-response-time

app.use(async function (ctx, next) {      // 0
  const start = new Date();               // 1
  await next();
  const ms = new Date() - start;          // 5
  ctx.set('X-Response-Time', `${ms}ms`);
});

// logger

app.use(async function (ctx, next) {     // 0
  const start = new Date();              // 2
  await next();
  const ms = new Date() - start;         // 4
  console.log(`${ctx.method} ${ctx.url} - ${ms}`);
});

// response

app.use(ctx => {                         // 0
  ctx.body = 'Hello World';              // 3
});

app.listen(3000);                        // 0

В этом случае код, помеченный (0), запускается линейно при запуске, затем при веб-запросе первая предоставленная асинхронная функция получает отметку времени (1) и ожидает вторую функцию, которая получает свою собственную отметку времени (2), а затем ожидает для третьего, который является фактическим обработчиком, возвращающим «Hello World» в качестве тела веб-страницы (3). Затем он возвращается, чтобы продолжить вторую функцию с того места, где остановился (4), которая регистрирует запрос… затем возвращается к первой функции, которая добавляет истекшее время для всего этого в заголовок X-Response-Time: (5).

Это линейный ряд функций, каждая со своим линейным кодом, которые работают предсказуемым, детерминированным образом, вызывая (через await) вложенные функции, о которых вызывающий даже не подозревает. Вау. С другой точки зрения, это самая простая реализация сопрограмм, которую я видел.

Готовый к будущему код

Многое из этого достижимо в коде ES6 с помощью функций генератора и ключевого слова yield , но мне не очень нравится синтаксис генератора (использование '*' как чего-то другого). чем оператор), и я рассматриваю его в основном как временный синтаксис для JavaScript, пока async/await полностью не завершит процесс утверждения и не станет официальной частью JavaScript в ES7.

Поэтому вместо этого, по крайней мере, для проектов Node.js, я буду максимально использовать async/await и избегать промисов и генераторов. Я просто пропущу этот этап, и мой код будет работать в Node 7 и с самого начала будет использовать самые современные и готовые к будущему функции.

Держите под рукой весь набор инструментов

Два моих новых проекта начинаются с предоставления только REST API. И да, я решил использовать для них Koa.js, потому что он компактный, беспристрастный, современный, от команды Express.js, просто новее. Это то, что они разработали бы, если бы начинали заново и не беспокоились об обратной совместимости с существующими приложениями. У меня уже есть собственное мнение. Да, я собираюсь использовать Passport.js для аутентификации, в основном потому, что мне нужна и локальная аутентификация, и OAUTH1 для аутентификации в Twitter, и OAUTH2 практически для всего остального. Это скудный, подлый только API, поэтому я думаю, что он идеально подходит для Koa.js. Я все еще люблю Feathers.js, но вот в чем дело:

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

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

Я потратил довольно много времени, пытаясь выбрать любимые фреймворки и инструменты. Я люблю Feathers.js и Vue.js, но это не обязательство. В моем последнем проекте я буду использовать Koa.js для серверной части, потому что я думаю, что это простой код, и я хочу, чтобы реализация была простой и минимальной, и потому что мне нравятся функции async/await, доступные сейчас. (Они еще недостаточно хорошо задокументированы в окружающей его экосистеме, поэтому в будущем мне может понадобиться разместить этот проект в блоге в качестве примера для других.)

И я буду использовать Ember.js (или, возможно, напрямую Glimmer.js) для внешнего интерфейса, хотя я люблю Vue.js. Сначала я делаю внутренний API, поэтому в духе моих комментариев выше я еще не полностью определился с внешним интерфейсом и не буду, пока у меня не будет возможности снова просмотреть все, когда придет время.

О «лучших» фреймворках

Я думаю, что из этого процесса поиска и выбора моих фаворитов я узнал, что на самом деле существует множество очень хороших фреймворков и инструментов. Любой из них, вероятно, является разумным выбором. Нам не нужны рамочные баталии и споры; «лучшей» инфраструктуры не больше, чем лучшей операционной системы. Лучшее зависит от обстоятельств и множества сопутствующих компромиссов, которые, вероятно, зависят от конкретной ситуации. Нет причин ограничивать себя при запуске нового проекта (если только это не является одним из ограничений организации, занимающейся разработкой, и в этом случае это бизнес-решение, а не техническое).

Тем не менее, у всех нас есть свои фавориты и предпочтения, симпатии и антипатии. Я постараюсь максимально использовать JavaScript в качестве основного языка. Мне не очень нравится синтаксис Angular, и (по крайней мере, на данный момент) я действительно не люблю смешивать HTML с JavaScript, как это часто бывает в проектах JSX и React. Однако мне гораздо больше нравится EJS, и я предпочитаю либо шаблоны Handlebars, либо EJS. Я предпочитаю, чтобы код интерфейса был разбит на повторно используемые компоненты, и по этой причине я предпочитаю Vue.js, а также Ember.js и Glimmer.js. Я думаю, что большинство бэкендов отлично справились бы с Koa.js (современный, компактный и независимый) или Feathers.js (на Express.js) для чего-то богатого и полнофункционального (полный стек). И я, вероятно, буду продолжать выбирать из них по мере продвижения проектов.

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