Koajs 2.0: новое поколение фреймворка нового поколения

    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
    • 0
      iced coffee script ( и возможно не только он ) даёт похожий функционал при помощи await и defer
      • 0

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

        • 0
          В Koa 2.0 практически так и есть, там передается контекст объекта запроса в middleware первым аргументом. В этом объекте есть свойство request и response. Также в ctx вы можете добавить свой объект, который могут использовать другие middleware.
          app.use(co.wrap(function *(ctx, next) {
            // ctx.request
            // ctx.response
            yield next();
          }));
        • +3
          Смотрится странно, когда синтаксические конструкции языка считают плюсом фреймворка — их с равным успехом можно использовать и в plain-js и в любом другом фреймворке. Рассматривать это как конкурентное преимущество я бы не стал.
          • 0
            Ну я не совсем это хотел сказать. Фреймворк опираеться на будущий стандарт языка ES2017 (ES8), который еще не принят (поэтому «новое поколение»). С помощью babel на нем разрабатывать можно уже сейчас.

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

            Кроме того я хочу отметить что авторами Koa являються (более 70-ти) человек, часть которых учавствуют в разаработке Express, который значительно консерватавнее к новым возможностям JS.
            • +1
              Эм… я про другое. callback, promise, generators, await, Koa — какое слово в этом ряду лишнее? Koa, потому что первые четыре — синтаксические конструкции языка, которые можно применять не привязываясь к конкретному фреймворку, а пятое — фреймворк, который что-то из этого использует. То, что фрейворк написан с использованием es2017 — это с точки зрения разработки на нём не минус и не плюс — разве что планируется в процессе юза лезть в его кишки. Я вот эксплуатирую связку React / redux и в ней я так же могу использовать es2017 в полный рост. Хотя я не сильно интересуюсь стайлгайдом кишков реакта.
          • 0
            По колено в поколениях.
            • –4

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

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

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

                • –1

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

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

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


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

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


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

              • +2

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

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

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

                    • 0
                      Пожалуй, вы правы насчет заголовка. Но не согласен по поводу кода на 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 и мне неприятно Ваше обобщение.
                      • 0
                        Просто не хотелось погружаться вдетали.

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

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