Pull to refresh

Comments 40

например, параллельная обработка промежуточного ПО:

А мне всегда казалось, что сама концепция middleware предполагает последовательное выполнение,
потому что middleware по сути как pipe. Данные на выходе одного middleware используются в следующем.

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

Мне стало интересно заглянуть в ядро koa и узнать, как реализована работа с Promise. Но я был удивлен, т.к. ядро осталось практически таким же, как в предыдущей версии. Авторы обеих библиотек express и koa одни и те же, неудивительно, что и подход остался таким же. Я имею ввиду структуру промежуточного ПО (middleware).

koa принципиально отличается от express и подход там совершенно другой. Работа с промисами там сводится к тому что в обработчике запроса ассинхронные функции вызываются через async/await синтакс со всеми вытекающими плюшками.


По поводу yeps-benchmark, там koa2 запускаеться через node-cluster, что на мой взгляд, не лучшее решение. И что пытаемся доказать, что роуты обрабатываються меделенее? Не спорю, но роуты это не часть koa, koa-router это сторонний middleware, роутинг не входит в базовую функциональность koa.


Про простоту и элегантность я б еще поспорил. А ошибки как ловить?

UFO just landed and posted this here
А почему бы ей не укладываться? Весь язык уложился — и такая мелочь тоже уложится. Писать на одном языке, на ходу выискивая разницу с другими — это не профессионально.
UFO just landed and posted this here
Подумал было, что мы друг друга не поняли, но нет. Вы сознательно прицепились к формулировке.
Разъясню ещё раз: каждому языку должен быть свой подход, привычки, вырабатываемые программистом для некоторого языка, непригодны для другого языка, и не должны в него переноситься. А проблем у языков нет. Проблемы есть у программистов, которые спеку не читают.
UFO just landed and posted this here
«Убогость архитектуры» любого отдельно взятого языка сводится к непривычности его для отдельно взятого программиста. Неужели трудно врубиться, что языки — разные? И если работать с умом — то на любом языке всё прекрасно пишется.
UFO just landed and posted this here
Я могу ещё страшнее каррирование написать. Что это доказывает?
UFO just landed and posted this here

ИМХО достаточно просто снизить кол-во хипстерских конструкций.
Как-то так?:


const curry = function(f, arr = []){
    return funciton inner(...args){
        function inner2(a){
            if (a.length == f.length) {
               return f(...a);
            } else {
                return curry(f,a);
            }
        }
        return inner2();
    }
}
UFO just landed and posted this here
Я прекрасно понимаю что ваш код делает, но то что вы его отформатировали в таком не читаемом виде это проблема не языка.

«Банальное каррирование» сводится к функции bind о чем я ниже у упомянул.

А Ваше творчество я переписал в куда более понятную конструкцию

function curry(fn, arr = []) {
	return function(...args) {
		const a = arr.concat(args);
		return fn.length === a.length ? fn(...a) : curry(fn, a);
	}
}
UFO just landed and posted this here
Причем тут function и return? В моей реализации на одну функцию меньше, а Ваш способ конкатенации аргументов вносит основную путаницу в код и читать его становиться сложнее.

А что не так с кодом? Он вполне понятный, как мне кажется.

В clojure автокаррирования нет. Получается, что всё, в функциональной парадигме его не попользовать?
UFO just landed and posted this here

Зачем вообще в js каррировать функции? Если хочется частичного применения, есть Function.prototype.bind.

1. В clojure функции вариативны и поэтому автокаррирования нет принципиально. Вот JS — да, совсем другое дело.
2. В JS есть bind.
3. Реализация ЧЕГО не должна выглядеть как удивительный танец?
Почему Вы считаете, что реализация каррирования должна быть простой? Как по мне, это ОЧЕНЬ странная метрика.
Я нигде не вижу, чтобы JS называли pure functional. И сравнивать его с Хаскелем…
Ну давайте тогда и плюсы сравнивать с Хаскелем. На них тоже можно писать во вполне себе функциональном стиле и это даже не будет похоже на обед кактусом. Даже библиотеки есть для этого (настоящие программисты, правда, не должны бездумно пользовать библиотеки, но зато должны писать их. Благо, для C++ программиста каррирование пишется довольно просто. Хотя и много сложнее вашего варианта).
Вот вам каррирование в одну строчку стандартным методом в js и даже еще не es6

function sum(a, b) {
  return a + b;
}

var sum5 = sum.bind(null, 5);


console.log(sum5(1)); // => 6

Это не каррирование, а частичное применение. Каррирование — это все же перевод функции к каррированному виду, с пока что еще неопределенными параметрами.


Хотя вы правы в том смысле, что чистое каррирование на js нафиг не нужно.

Ну так случилось, что вместо нескольких методов в работы с массивами js есть один — slice
Особенность языка, не более. К слову, не худшая…
UFO just landed and posted this here

Это в каком таком всем языке без исключения получение копии массива делается через copy? Насколько я знаю, copy всегда копировала элементы из одного массива в другой, а не копировала сам массив!


Вот те языки, которые я знаю:


  • C++, C#, Java: надо сначала создать новый массив — потом уже делать copy.
  • Pascal: переменная объявляется в секции var. А копируется массив оператором присваивания.
  • Javascript, Python: массив копируется пустым слайсом.

Ах да, еще в C# и Java можно сделать вызов clone — но он страшно некрасивый потому что возвращает Object, а не массив...

Подождите, а как вы измеряли скорость? Почему здесь не учитыватся тот факт что нода однопоточная (не учитываем либюв и "асинхронность"). У нас есть ивент-луп и чем эффективнее ваш код — тем меньше вы его едите.


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


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


Несколько сумбурный коммент, но важно понимать что мейн тред он один и вся "псевдо" асинхронщина (к примеру параллельное выполнение синхронных функций через промис.алл, только замедлит работу)

Спасибо за комментарий. Как по мне он самый интересный и показывает несовершенство yesp документации.

По первому вопросу — как раз я и учел особенности node.js.
Node.js не однопоточная, она работает в одном процессе (если не учитывать кластеризацию и child_process). Многопоточность и обеспечивает libuv.

Библиотека yesp позволяет контролировать последовательность или параллельность выполнения кода.
app.then();
app.all();
app.all();
app.then();
app.catch()

фактически даст нам
Promise.resolve()
.then()
.then(() => Promise.all())
.then(() => Promise.all())
.then()
.catch();


Поэтому можно группировать промежуточное ПО по смыслу и сделать их работу параллельной (например сервер статики и favicon запустить параллельно, если они смотрят в разные папки, затем параллельно запустить создание logger, error handler, redis client, mysql client...). Пример можно посмотреть в yeps-boilerplate.

Зачем сервер статики и favicon запускать параллельно, если они никогда не будут обрабатывать один и тот же запрос одновременно?

Вместо последовательного перебора всех правил можно одновременно запустить проверку всех. Этот момент не остался без тестирования производительности и результаты не заставили себя ждать.

А вы как бы в курсе что оно будет все равно в одном потоке выполнятся и никакой параллельности не будет? Если вы конечно не рожаете пулл процессов для проверки роутов.
Фишку с непоследовательным выполнением миддлеваре вообще не понял, в этом как раз и профит что запрос обрабатывается последовательно всеми миддлварями и это последовательность никак не мешает асинхронности.

Это дает отличную возможность избавиться от callback hell и постоянной обработки ошибок на всех уровнях, как, например, реализовано в express.

Это не избавляет вас от необходимости обрабатывать ошибки асинхронных функций. Напишите trow Error в таймауте и Promise.all его не в поймает.
Это не избавляет вас от необходимости обрабатывать ошибки асинхронных функций. Напишите trow Error в таймауте и Promise.all его не в поймает.


Я старался как раз и избавиться от callback, заменив их на Promise / aasync / await. Ошибки в такой реализации не теряются. А заменить setTimeout можно Promise обберткой, например promise-pause-timeout.

Фишку с непоследовательным выполнением миддлеваре вообще не понял, в этом как раз и профит что запрос обрабатывается последовательно всеми миддлварями и это последовательность никак не мешает асинхронности.


И да и нет. Если промежуточное ПО является, например, static server, здесь результат параллельной работы очевиден (обращение к файловой системе). Если нам нужно создать клиенты, например к  mysql / redis, и дождаться их соединения — тоже (сетевые запросы). Но если нам нужно обработать например request (body-parser), здесь особо выигрыша не будет, но мы можем этим пожертвовать ради единой архитектуры и выигрыша от предыдущих примеров. В итоге суть подхода делать неблокирующие операции везде — один из важнейших паттернов асинхронной работы node.js.
Если нам нужно создать клиенты, например к mysql / redis, и дождаться их соединения — тоже (сетевые запросы).

Вы действительно на каждый запрос в мидлвари делаете новое подключение к db/redis/etc?
Итог — получаем выигрыш в раздаче статики, что, мягко говоря, не рекомендуется в проде.

Суть мидлвари в том, что это не просто асинхронная функция. Они ходят не только вниз, но и вверх точно в обратном порядке, по моему у вас этот принцип нарушен.


Это немного устаревшая гифка из koa@1, но суть та же:
image

Если сравнивать с  koa, то все верно.
Но идея была все таки взглянуть по новому на архитектуру с учетом всех новых возможностей node.js.
Здесь ближе подход Promise based.
Пример с промежуточным ПО (middleware) я привел для сравнения с express / koa. Но это не сама суть идеи.
Чтобы объяснить что-то новое, легче взять что-то старое и показать отличие.

И я представил, как должен выглядеть действительно «фреймворк нового поколения»:
const server = http.createServer( (req, res) => {
    Promise.resolve({ req, res }).then(ctx => {

        ctx.res.writeHead(200, {'Content-Type': 'text/plain'});
        ctx.res.end('OK');

        return ctx;
    });
});


Как по мне так, это можно было решить проще. Все что должно выполняться параллельно помещаем в Promise.all. Т.е. всю вашу библиотеку можно свести к небольшому врапперу для нескольких middleware.

Все правильно.
Это подход, реализующий здравый смысл и лучшие практики работы с node.js, оформленный в небольшую библиотеку.
Sign up to leave a comment.

Articles

Change theme settings