KoaJS logo«Next generation web framework for node.js» — так написано в документации к версии 1.0. Звучит неплохо, я к этому добавлю что 2 года назад после внедрения koa на одном из проектов у наших программистов появился термин “псевдо-синхронный код” (Это когда код выглядит как синхронный но на самом деле исполняется асинхронно). Что за бред Как это работает я расскажу под катом.

Что такое 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)


  1. Strain
    19.05.2016 11:12

    iced coffee script ( и возможно не только он ) даёт похожий функционал при помощи await и defer


  1. rumkin
    19.05.2016 11:32

    Мне не понятно зачем нужно было переносить переменные request и response в this. ИМХО удобнее передавать их через аргументы функции, как в express. И выносить поддержку co за пределы koa до появления async/await в v8, как-то преждевременно.


    1. 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();
      }));


  1. spbcypher
    19.05.2016 12:31
    +3

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


    1. apelsyn
      19.05.2016 12:54

      Ну я не совсем это хотел сказать. Фреймворк опираеться на будущий стандарт языка ES2017 (ES8), который еще не принят (поэтому «новое поколение»). С помощью babel на нем разрабатывать можно уже сейчас.

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

      Кроме того я хочу отметить что авторами Koa являються (более 70-ти) человек, часть которых учавствуют в разаработке Express, который значительно консерватавнее к новым возможностям JS.


      1. spbcypher
        19.05.2016 13:06
        +1

        Эм… я про другое. callback, promise, generators, await, Koa — какое слово в этом ряду лишнее? Koa, потому что первые четыре — синтаксические конструкции языка, которые можно применять не привязываясь к конкретному фреймворку, а пятое — фреймворк, который что-то из этого использует. То, что фрейворк написан с использованием es2017 — это с точки зрения разработки на нём не минус и не плюс — разве что планируется в процессе юза лезть в его кишки. Я вот эксплуатирую связку React / redux и в ней я так же могу использовать es2017 в полный рост. Хотя я не сильно интересуюсь стайлгайдом кишков реакта.


  1. Jamdaze
    19.05.2016 12:33

    По колено в поколениях.


  1. Finom
    19.05.2016 12:45
    -4

    Бабель-фуябель генерирует неоптимизируемый V8 код и это, очевидно, может заметно негативно сказаться на производительности. Пока в движок не будет добавлен синтаксис async/await, во фреймворке нет смысла (не свитая небольших проектов "для души").


    1. faiwer
      19.05.2016 15:08
      +1

      Бабель-фуябель генерирует неоптимизируемый V8 код

      O_o. Откуда дровишки? Почему V8, по вашему, не будет оптимизировать js код после babel-а? Чем он провинился? :)


      1. Finom
        19.05.2016 15:26
        -1

        Взгляните, что генерирует Babel. Если разобрать _asyncToGenerator, там есть неоптимизируемый try..catch. Но это полбеды, посмотрите, во что превращается безобидный код. Попробуйте разобраться, сколько лишнего всего происходит при использовании async/await: конвертация в генератор, вызов вот этой функции (изменение proto налету, ага), вызов вот этой (и всего, что в ней находится). Вы по-прежнему хотите это на сервере? Мы вроде-как наоборот стараемся ускорить приложения на ноде, ну да ладно.


        1. faiwer
          19.05.2016 16:53
          +3

          там есть неоптимизируемый try..catch.

          Посмотрел. А в чём проблема то? Там же всего пару строк. try-catch вынесен уровнем выше. Самое главное — оптимизации подлежит.


          Попробуйте разобраться, сколько лишнего всего происходит при использовании async/await

          Да вроде ничего особенного, учитывая контекст применения. Для высоконагруженных частей кодовой базы, может и не сгодится, но в остальных случаях — более чем. Следует понимать, что async-await в принципе не будет работать особенно то быстро, т.к. там всегда будет большой switch-case автомат и промисы. Это ведь в первую очередь удобство.


          Я использую co и generator-ы (т.е. без babel). Выжимал по ab на простенькой веб-странице 1600 запросов в секунду (и это сквозь целую цепочку таких вот await-генераторов). Мне кажется такой производительности более чем достаточно для скриптового то языка. А позже и нативная реализация подойдёт. Правда я не думаю, что она будет заметно эффективнее.


  1. faiwer
    19.05.2016 15:07
    +2

    А зачем такой громкий пафосный заголовок? К чему это? Ожидал увидеть какие-нибудь новые подходы или фишки. Не стоит так писать.


    1. apelsyn
      19.05.2016 15:46
      -2

      «Next generation» это позиционирование фреймворка. В аннотации к статье это написано. Цель статьи популяризация фрейворка в основе которого лежит использование современного стандарта JS. Я сознательно старался сделать статью покороче.


      1. faiwer
        19.05.2016 16:58
        +2

        Вы знаете, мне кажется, сейчас каждый, кто начинает писать свой framework использует babel async-await или их имитацию через генераторы. Нет в этом никакого "фреймворка нового поколения". Если вы действительно считаете koa чем то выдающимся, то стоило именно об этом и написать. А так получилось что статья с ну прямо очень громким заголовком и описанием того, что мол есть babel и есть co, используйте. Я же по вашим примерам вижу в нём тот же самый express, который всегда не вызывал у меня ничего кроме недоумения.


        1. 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 и мне неприятно Ваше обобщение.


          1. faiwer
            24.05.2016 11:54

            Просто не хотелось погружаться вдетали.

            Ну тогда вы знаете о чём стоит написать в следующей статье про Koa :)