Комментарии 40
например, параллельная обработка промежуточного ПО:
А мне всегда казалось, что сама концепция middleware предполагает последовательное выполнение,
потому что middleware по сути как pipe. Данные на выходе одного middleware используются в следующем.
Мне стало интересно заглянуть в ядро koa и узнать, как реализована работа с Promise. Но я был удивлен, т.к. ядро осталось практически таким же, как в предыдущей версии. Авторы обеих библиотек express и koa одни и те же, неудивительно, что и подход остался таким же. Я имею ввиду структуру промежуточного ПО (middleware).
koa принципиально отличается от express и подход там совершенно другой. Работа с промисами там сводится к тому что в обработчике запроса ассинхронные функции вызываются через async/await синтакс со всеми вытекающими плюшками.
По поводу yeps-benchmark, там koa2 запускаеться через node-cluster, что на мой взгляд, не лучшее решение. И что пытаемся доказать, что роуты обрабатываються меделенее? Не спорю, но роуты это не часть koa, koa-router это сторонний middleware, роутинг не входит в базовую функциональность koa.
Про простоту и элегантность я б еще поспорил. А ошибки как ловить?
использует array.slice(0) — наиболее быстрый метод копирования массива.
Зависит от движка и размера массива — https://github.com/nodejs/node/pull/3976
Разъясню ещё раз: каждому языку должен быть свой подход, привычки, вырабатываемые программистом для некоторого языка, непригодны для другого языка, и не должны в него переноситься. А проблем у языков нет. Проблемы есть у программистов, которые спеку не читают.
ИМХО достаточно просто снизить кол-во хипстерских конструкций.
Как-то так?:
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();
}
}
«Банальное каррирование» сводится к функции bind о чем я ниже у упомянул.
А Ваше творчество я переписал в куда более понятную конструкцию
function curry(fn, arr = []) {
return function(...args) {
const a = arr.concat(args);
return fn.length === a.length ? fn(...a) : curry(fn, a);
}
}
Зачем вообще в js каррировать функции? Если хочется частичного применения, есть Function.prototype.bind.
2. В JS есть bind.
3. Реализация ЧЕГО не должна выглядеть как удивительный танец?
Почему Вы считаете, что реализация каррирования должна быть простой? Как по мне, это ОЧЕНЬ странная метрика.
Я нигде не вижу, чтобы JS называли pure functional. И сравнивать его с Хаскелем…
Ну давайте тогда и плюсы сравнивать с Хаскелем. На них тоже можно писать во вполне себе функциональном стиле и это даже не будет похоже на обед кактусом. Даже библиотеки есть для этого (настоящие программисты, правда, не должны бездумно пользовать библиотеки, но зато должны писать их. Благо, для C++ программиста каррирование пишется довольно просто. Хотя и много сложнее вашего варианта).
function sum(a, b) {
return a + b;
}
var sum5 = sum.bind(null, 5);
console.log(sum5(1)); // => 6
Особенность языка, не более. К слову, не худшая…
Это в каком таком всем языке без исключения получение копии массива делается через copy? Насколько я знаю, copy
всегда копировала элементы из одного массива в другой, а не копировала сам массив!
Вот те языки, которые я знаю:
- C++, C#, Java: надо сначала создать новый массив — потом уже делать copy.
- Pascal: переменная объявляется в секции var. А копируется массив оператором присваивания.
- Javascript, Python: массив копируется пустым слайсом.
Ах да, еще в C# и Java можно сделать вызов clone — но он страшно некрасивый потому что возвращает Object, а не массив...
Подождите, а как вы измеряли скорость? Почему здесь не учитыватся тот факт что нода однопоточная (не учитываем либюв и "асинхронность"). У нас есть ивент-луп и чем эффективнее ваш код — тем меньше вы его едите.
После вводной — когда мы делаем промис.алл(мидлвари), это может дать преимущества только если внутри а) одна мидлварь не зависит от данных другой б) мидлвари выполняют ИО, то бишь асинхронную работу. В любом другом случае вы замедляете работу вашего кода, потому что добавляете дополнительную работу в ивент луп.
Так вот, принимая во внимание вышесказанное, следовало бы сделать так чтобы в мидлварях не было ИО, а если это невозможно, то скорее всего от этой мидлвари зависят другие части. Если же независят, то эту мидлварь можно прогнать и не дождаться выпонения (пример — статсд статистика, обработка вашего роута никак не зависит от того сохранилась статистика или нет и нет смысла дожидаться выполнения операции перед переходом к следующей части пайпа).
Несколько сумбурный коммент, но важно понимать что мейн тред он один и вся "псевдо" асинхронщина (к примеру параллельное выполнение синхронных функций через промис.алл, только замедлит работу)
По первому вопросу — как раз я и учел особенности 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.
Вместо последовательного перебора всех правил можно одновременно запустить проверку всех. Этот момент не остался без тестирования производительности и результаты не заставили себя ждать.
А вы как бы в курсе что оно будет все равно в одном потоке выполнятся и никакой параллельности не будет? Если вы конечно не рожаете пулл процессов для проверки роутов.
Фишку с непоследовательным выполнением миддлеваре вообще не понял, в этом как раз и профит что запрос обрабатывается последовательно всеми миддлварями и это последовательность никак не мешает асинхронности.
Это дает отличную возможность избавиться от 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.
Суть мидлвари в том, что это не просто асинхронная функция. Они ходят не только вниз, но и вверх точно в обратном порядке, по моему у вас этот принцип нарушен.
Это немного устаревшая гифка из koa@1, но суть та же:
Но идея была все таки взглянуть по новому на архитектуру с учетом всех новых возможностей 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-библиотека…