Что такое KoaJS
Чтоб не повторяться:
С помощью особенности работы генераторов и директивы yield мы можем асинхронно выполнить Promise или специально нами сконструированную thinkify-функцию и после выполнения промиса/функции вернуться в ту точку где вызывался yield вернуть результат и продолжить выполнение кода.
Пример такого кода:
//...
let userId = Number(this.request.userId);
let projects = yield users.getActiveProjectsByUserId(userId); // Например, обращаемся к БД
for (let project of projects) {
project.owners = yield projects.getProjectOwnersById(project.id); // Тут тоже обращаемся к БД
}
this.body = yield this.render('userProjects', projects); // Тут асихронно рендерим ответ
//...
Зачем мне KoaJS?
Идея Koa идеально ложиться на парадигму микросервисов, которую я уже давно внедрил в нашей компании. Ядро фреймворка минималистично, код middleware для коа читается и понимается очень легко, что очень важно для командной разработки. Активное использоване фреймворком возможностей ES2015, минимальное травмирование психики программиста, который до этого писал на PHP (эти ребята колбеки не любят :) ).
Замечательно, чем удивит KoaJS 2.0?
То что было основой KoaJS, а именно библиотека co, построенная на генераторах теперь удалена из базовой реализации Koa 2.
Давайте посторим Hello world!
const Koa = require('koa');
const app = new Koa();
// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
Как видите тут генераторов нет, но тогда как же писать расхваленый мной “псевдо-синхронный код”. В самом простом случае можно обойтись нативным кодом:
app.use((ctx, next) => {
const start = new Date();
return next().then(() => {
const ms = new Date() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
});
next — является промисом, ctx — контекст запроса.
В таком виде без колбеков многие вещи реализовать нельзя, поэтому авторы предлагают использовать новый синкасис async/await, который еще не стал стандартом и нативно не поддерживается NodeJS но уже давно реализован в транспилере Babel. Выглядит это так
app.use(async (ctx, next) => {
const start = new Date();
await next();
const ms = new Date() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
Также предусмотрен вариант с дополнительным подключением библиотеки co и генераторами:
app.use(co.wrap(function *(ctx, next) {
const start = new Date();
yield next();
const ms = new Date() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
}));
Совместимость с Koa 1.x
Когда я говорил о “выпиленных” генераторах, то был не совсем точен. Для совместимости, если вы оформите middleware в стиле koa 1.0, koa 2.0 его подключит и исполнит он при этом в консоли будет “Koa deprecated Support for generators will been removed in v3…” Другими словами до версии 3.x все будет работать.
Вот пример:
// Koa will convert
app.use(function *(next) {
const start = new Date();
yield next;
const ms = new Date() - start;
console.log(`${this.method} ${this.url} - ${ms}ms`);
});
Также можно сконвертировать существующий middleware в формат 2.0 самостоятельно с помощью модуля koa-convert
const convert = require('koa-convert');
app.use(convert(function *(next) {
const start = new Date();
yield next;
const ms = new Date() - start;
console.log(`${this.method} ${this.url} - ${ms}ms`);
}));
В этом случае никаких предупреждений на консоль, поэтому я рекомендую использовать именно такой способ подключения legacy middleware.
Зачем переходить на 2.0
Конечно я не могу утверждать на 100%, но полгода стабильной работы одного из типовых сервисов дают мне уверенность в том что 2.0 достаточно стабилен.
Я хочу иметь право выбора каким способом реализовывать свой middleware. Koa 2.0 дает мне три пути: нативный, генераторы и async/await
Koa 2.0 уже поддерживают многие популярные middleware, а если не поддерживают, то работают через koa-convert
Если вы сейчас приглядываетесь к новым фреймворкам, попробуйте что-то небольшое закодить на Koa — уверен, Вы не пожалеете что потратите на это время.
Что посмотреть
Комментарии (16)
rumkin
19.05.2016 11:32Мне не понятно зачем нужно было переносить переменные request и response в this. ИМХО удобнее передавать их через аргументы функции, как в express. И выносить поддержку co за пределы koa до появления async/await в v8, как-то преждевременно.
apelsyn
19.05.2016 12:29В Koa 2.0 практически так и есть, там передается контекст объекта запроса в middleware первым аргументом. В этом объекте есть свойство request и response. Также в ctx вы можете добавить свой объект, который могут использовать другие middleware.
app.use(co.wrap(function *(ctx, next) { // ctx.request // ctx.response yield next(); }));
spbcypher
19.05.2016 12:31+3Смотрится странно, когда синтаксические конструкции языка считают плюсом фреймворка — их с равным успехом можно использовать и в plain-js и в любом другом фреймворке. Рассматривать это как конкурентное преимущество я бы не стал.
apelsyn
19.05.2016 12:54Ну я не совсем это хотел сказать. Фреймворк опираеться на будущий стандарт языка ES2017 (ES8), который еще не принят (поэтому «новое поколение»). С помощью babel на нем разрабатывать можно уже сейчас.
Ну а если использование babel не приемлемо то можно обходиться генераторами или промисами, которые нативно работают уже сейчас.
Кроме того я хочу отметить что авторами Koa являються (более 70-ти) человек, часть которых учавствуют в разаработке Express, который значительно консерватавнее к новым возможностям JS.spbcypher
19.05.2016 13:06+1Эм… я про другое. callback, promise, generators, await, Koa — какое слово в этом ряду лишнее? Koa, потому что первые четыре — синтаксические конструкции языка, которые можно применять не привязываясь к конкретному фреймворку, а пятое — фреймворк, который что-то из этого использует. То, что фрейворк написан с использованием es2017 — это с точки зрения разработки на нём не минус и не плюс — разве что планируется в процессе юза лезть в его кишки. Я вот эксплуатирую связку React / redux и в ней я так же могу использовать es2017 в полный рост. Хотя я не сильно интересуюсь стайлгайдом кишков реакта.
Finom
19.05.2016 12:45-4Бабель-фуябель генерирует неоптимизируемый V8 код и это, очевидно, может заметно негативно сказаться на производительности. Пока в движок не будет добавлен синтаксис async/await, во фреймворке нет смысла (не свитая небольших проектов "для души").
faiwer
19.05.2016 15:08+1Бабель-фуябель генерирует неоптимизируемый V8 код
O_o. Откуда дровишки? Почему V8, по вашему, не будет оптимизировать js код после babel-а? Чем он провинился? :)
Finom
19.05.2016 15:26-1Взгляните, что генерирует Babel. Если разобрать _asyncToGenerator, там есть неоптимизируемый try..catch. Но это полбеды, посмотрите, во что превращается безобидный код. Попробуйте разобраться, сколько лишнего всего происходит при использовании async/await: конвертация в генератор, вызов вот этой функции (изменение proto налету, ага), вызов вот этой (и всего, что в ней находится). Вы по-прежнему хотите это на сервере? Мы вроде-как наоборот стараемся ускорить приложения на ноде, ну да ладно.
faiwer
19.05.2016 16:53+3там есть неоптимизируемый try..catch.
Посмотрел. А в чём проблема то? Там же всего пару строк. try-catch вынесен уровнем выше. Самое главное — оптимизации подлежит.
Попробуйте разобраться, сколько лишнего всего происходит при использовании async/await
Да вроде ничего особенного, учитывая контекст применения. Для высоконагруженных частей кодовой базы, может и не сгодится, но в остальных случаях — более чем. Следует понимать, что async-await в принципе не будет работать особенно то быстро, т.к. там всегда будет большой switch-case автомат и промисы. Это ведь в первую очередь удобство.
Я использую co и generator-ы (т.е. без babel). Выжимал по ab на простенькой веб-странице 1600 запросов в секунду (и это сквозь целую цепочку таких вот await-генераторов). Мне кажется такой производительности более чем достаточно для скриптового то языка. А позже и нативная реализация подойдёт. Правда я не думаю, что она будет заметно эффективнее.
faiwer
19.05.2016 15:07+2А зачем такой громкий пафосный заголовок? К чему это? Ожидал увидеть какие-нибудь новые подходы или фишки. Не стоит так писать.
apelsyn
19.05.2016 15:46-2«Next generation» это позиционирование фреймворка. В аннотации к статье это написано. Цель статьи популяризация фрейворка в основе которого лежит использование современного стандарта JS. Я сознательно старался сделать статью покороче.
faiwer
19.05.2016 16:58+2Вы знаете, мне кажется, сейчас каждый, кто начинает писать свой
framework
используетbabel async-await
или их имитацию через генераторы. Нет в этом никакого "фреймворка нового поколения". Если вы действительно считаетеkoa
чем то выдающимся, то стоило именно об этом и написать. А так получилось что статья с ну прямо очень громким заголовком и описанием того, что мол естьbabel
и естьco
, используйте. Я же по вашим примерам вижу в нём тот же самыйexpress
, который всегда не вызывал у меня ничего кроме недоумения.apelsyn
24.05.2016 11:43Пожалуй, вы правы насчет заголовка. Но не согласен по поводу кода на koa — это не express. Просто не хотелось погружаться вдетали. В идеологии Koa есть upstreem-обработчики и downstreem обработчики:
app.use(convert(function *(next) { const start = new Date(); // Это выполниться до того как будет отдан ответ на запрос yield next; const ms = new Date() - start; // Это выполниться после того console.log(`${this.method} ${this.url} - ${ms}ms`); // как будет отдан ответ на запрос }));
Если не нужен обработчик на upstreem-и или downstreem-е эту часть кода опускаем. Никаких колбеков. Где же тут express? Использование middleware? Так это у половины фреймворков такой стиль!
Мне не правится express и мне неприятно Ваше обобщение.faiwer
24.05.2016 11:54Просто не хотелось погружаться вдетали.
Ну тогда вы знаете о чём стоит написать в следующей статье про Koa :)
Strain
iced coffee script ( и возможно не только он ) даёт похожий функционал при помощи await и defer