Pull to refresh

Comments 79

во втором примере не уверен что лучше использовать Array.reduce() как раз связка из filter и map более наглядна.
Не уверен, что в общем случае такое вообще возможно. И еще reduce как однопроходная вероятно будет сильно оптимальнее с точки зрения ресурсов (во всяком случае в типичном для конкретно js случае). Нагляднее (когда это возможно) — наверное да.
Оптимальнее — очевидно обычный цикл. Декларативнее — отдельные filter+map
>Оптимальнее — очевидно обычный цикл
Я имел в виду выбор между reduce в один проход, и filter+map в несколько (с той же оговоркой, что это вообще можно будет сделать). Цикл — да, наверное можно написать оптимальнее. Но и понять потом будет сложнее.
Та я понял что вы имели ввиду. Редьюс всегда понять тяжелее всего.

var hufflepuffList = '<ul>' + wizards.reduce(function (html, wizard) {
  if (wizard.house === 'Hufflepuff') {
    html += '<li>' + wizard.name + '</li>';
  }
  return html;
}, '') + '</ul>';

VS
let hufflepuffList = '<ul>';

for (const wizard of wizards) {
  if (wizard.house === 'Hufflepuff') {
    hufflepuffList += '<li>' + wizard.name + '</li>';
  }
}

hufflepuffList += '</ul>'
В большинстве примеров совершенно непонятно зачем вообще использовать reduce, когда значительно лучше подходит обычный for.

Для себя решил, что единственное, где стоит использовать reduce — это если можешь написать абстрактную чистую функцию под него, которую передать в качестве аргумента. К примеру:

const add = (a, b) => a + b;

// вот тут редьюс адекватно:

const sum = numbers => numbers.reduce(add, 0)


Во всех остальных случаях — он слишком нечитабельный
for — это побочный эффект. Теоретически, внутри цикла вы можете делать все что угодно — и синтаксически это вообще не видно. Так что вопрос читабельности — он спорный. Хотя да, reduce штука достаточно абстрактная, и потому в смысле читабельности тоже далеко не всегда простая. map и filter в этом смысле сильно проще.

>если можешь написать абстрактную чистую функцию
А что вам собственно может помешать?
А что вам собственно может помешать?
Потому что у ваших примеров функции «грязные» — не pure
Ну, во-первых — это не мои примеры. А во-вторых, для reduce функция свертки как раз обычно должна быть чистой. У нее два аргумента, один из которых аккумулятор — вот с ним она может и должна делать что угодно. И это не будет грязной функцией. Если же ей нужно менять что-то другое — я бы сказал, что это явный признак применения reduce не по назначению.
Вот, видите. Вы не пишете абстрактную чистую функцию. Вы или пишете «абстрактную грязную», или «вроде бы как чистую, но только если она применяется в reduce»

И это не будет грязной функцией
Будет, по определению чистой функции. В данном случае — изменение переданного аргумента — побочный эффект
Насколько я знаю, reduce не использует измененный аккумулятор после функции свертки — она использует возвращаемое новое значение аккумулятора. Во всяком случае это было бы логично. Так что написать грязную функцию вы конечно можете, но вы не обязаны делать ее грязной.
Зависит от того, как использовать. Сама редьюс не меняет, но почти все примеры в статье — грязные.
В данном случае — изменение переданного аргумента — побочный эффект

Если сей маленький изъян невидим снаружи, почему это может быть важно? Ловкость рук и никакого мошенничества.

Вы почитайте всю ветку внимательно.
Ну так почитайте определение побочного эффекта:
Например, к побочным эффектам относятся[2]:

изменение (запись) объекта;

Как может изменение не быть побочным эффектом, если это побочный эффект?

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

Вы как золотая рыбка — пока дочитали ветку до конца — забыли что было вначале?

это если можешь написать абстрактную чистую функцию под него


Давайте возьмем одну из функций из топика, оторвём её от редьюса и посмотрим, является ли она чистой:
function addHP(newArr, wizard) {
  if (wizard.house === 'Hufflepuff') {
    newArr.push(wizard.name);
  }
  return newArr;
}

window.myArr = [];

addHP(myArr, { house: 'Hufflepuff', name: 'blabla' });

console.log(window.myArr);


Можно видеть, что функция addHP — грязная. У неё есть побочный эффект — модификация переданных аргументов и мы можем его пронаблюдать.

Вывод — функция не подходит под определение чистой.

Для меня редьюс является допустимым только если функцию-аргумент мы пишем не внутри него, а как внешнюю абстрактную чистую функцию.

Дополнительно
То, что вы используете грязную функцию, которая модифицирует свой код в каком-то контексте, который ограничивает её влияние — не делает её чистой. Тогда можно сказать, что console.log — тоже чистая, ведь она ограничена лишь одной консолью, а если монитор отключен — мы даже не сможем пронаблюдать её побочный эффект.
То, что вы используете грязную функцию, которая модифицирует свой код в каком-то контексте, который ограничивает её влияние — не делает её чистой

Делает, конечно, чистота ф-й всегда определяется modulo какие-то эффекты. Мы, например, не учитываем, что исполнение ф-и греет окружающую среду, потребляет память, занимает процессорное время — это все сайд-эффекты. Но мы этими эффектами договорились пренебрегать. Хотя они есть — и потому любая ф-я, запущенная на реальном железе, является грязной автоматом.
Точно так же мы в каком-то контексте может договориться пренебрегать и выводом в консоль — никаких проблем в этом нет, этот эффект принципиально ничем от нагревания процессора не отличается. На самом деле, мы за счет нагревания можем пользователю доставить ту же информацию, что и за счет вывода в консоль. Только медленно будет. Хотя можно и быстро — через пищание дросселей можно при желании даже аудио воспроизводить, меняя нагрузку.

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

Жаль, что вы ответили только на менее важное дополнение в спойлере, но не на тело комментария.
Увы, этот несомненно интересное теоретическое эссе хорошо подходит к контексту обсуждения философии программирования, но никак не к вопросам организации кода.

Ну, это вы про философию начали.


Что до тела — очевидно, что addHP(myArr, { house: 'Hufflepuff', name: 'blabla' }); — грязная, а addHP([], { house: 'Hufflepuff', name: 'blabla' }); — нет.

Это с какого фига такое очевидно? Очевидно как раз, что достаточно одного побочного эффекта, чтобы показать, что данная функция — грязная. Она не перестает быть грязной от того, что иногда она не вызывает побочных эффектов.


function doSomeStaff (time) {
  if (time % 5 !== 0) {
    global.counter++;
    return true;
  }
  return false;
}

Очевидно, что эта функция грязная. А не "эта функция грязна, но каждые 5 секунд она чистая". Что за бред вообще?


Я вам ещё процитирую:


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

Для того, чтобы функции быть грязной она не обязана модифицировать глобальное значение или аргументы. Ей достаточно мочь модифицировать глобальное значение или аргументы. Может? Грязная. Всё.

Вот ещё:
Другим видом побочных эффектов является модификация переданных в функцию параметров (переменных), когда в процессе вычисления выходного значения функции изменяется и значение входного параметра.

Чистота — понятие относительное. Если вы, например используете arr.push(x) внутри reduce, но при этом на arr только одна единственная ссылка — то вы можете смело считать arr.push(x) обычным сахаром над arr = [ ...arr, x ]. В итоге ф-я является чистой, т.к. мутаций никаких не происходит — вы, с точки зрения функциональной семантики, создаете каждый раз новый массив, а не мутируете старый.
При этом уникальность, конечно, контролировать вам руками придется.


С точки зрения практики для таких кейзов (с мутацией уникального объекта) можно сделать ф-ю обертку, чтобы явно нотировать этот момент для стороннего читателя, т.е. вместо arr.push(x) писать uniq_mutate(arr, (a) => a.push(x)), uniq_mutate x f = f x

Да, я понимаю, что внешняя функция получается чистой. Но почитайте мой изначальный посыл. По моему мнению редьюс стоит использовать тогда, когда можно написать абстрактную чистую функцию под него. Это просто способ понять, подходит ли здесь редьюс. В остальных случаях больше подходит, к примеру, обычный цикл
Да, я понимаю, что внешняя функция получается чистой.

Так и внутренняя чистая, раз сайд-эффект пронаблюдать вы не можете.


Лучше чем forEach оно тут тем, что init не утекает из контекста. Т.е., цикл нужен вам как раз тогда, когда внутренняя функция не может быть сведена к чистой, т.к. вы должны изменить init. Если же у вас одна ссылка на init то вам его менять не надо, тогда внутренняя ф-я чистая и редьюс сам тоже чистый, несмотря на то, что в нем всякие arr.push и прочие вещи.


Прямо ваш собственный пример выше это хорошо демонстрирует — у вас hufflepuffList находится во внешнем по отношению к for контексте, а вот html в варианте с редьюсом никуда за пределы фунарга редьюса не уходит. Вот когда вам надо, чтобы переменная существовала за пределами контекста цикла (а тут этого не надо), то, конечно, предпочтительнее for/forEach. В противном случае лучше использовать редьюс — именно за тем, чтобы показать, что "этот кусок кода не срет наружу". Ну а с циклом — с-но, что наружу срет.

Так и внутренняя чистая, раз сайд-эффект пронаблюдать вы не можете.
Так могу же. Если у меня функция написана отдельно — я могу пронаблюдать сайд-эффект в ней. Я все функции стараюсь писать абстрактно с адекватным именованием. Или хотя бы так, чтобы их к такому состоянию можно было легко отрефакторить. На пример с фильтром и мапом этот подход прекрасно ложится. Редьюс — пованивает. Это не для того, чтобы сделать функцию принципиально чистой. Для меня это признак удачно написанного кода или неудачно. Лично для себя.

function isHufflepuff (wizard) {
  return wizard.house === 'Hufflepuff';
}

function getName (item) {
  return item.name;
}

var hufflepuff = wizards
  .filter(isHufflepuff)
  .map(getName);


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

В противном случае лучше использовать редьюс — именно за тем, чтобы показать, что «этот кусок кода не срет наружу». Ну а с циклом — с-но, что наружу срет.
Для этого вообще-то существует специальный инструмент — вынос кода в отдельную функцию. Так:
function renderHufflepuff (wizards) {
  let hufflepuffList = '<ul>';

  for (const wizard of wizards) {
    if (wizard.house === 'Hufflepuff') {
      hufflepuffList += '<li>' + wizard.name + '</li>';
    }
  }

  return hufflepuffList + '</ul>'
}

Зачем использовать для этого несемантичный редьюс? Хотя, конечно, в этом случае значительно лучше было бы написать так:
function renderWizard (wizard) {
  return '<li>' + wizard.name + '</li>';
}

function renderHufflepuff (wizards) {
  return '<ul>' + wizards.filter(isHufflepuff).map(renderWizard) + '</ul>'
}


Или даже так:
function renderWizard (wizard) {
  return '<li>' + wizard.name + '</li>';
}

function renderWizardsList (wizards) {
  return '<ul>' + wizards.map(renderWizard) + '</ul>'
}

function renderHufflepuffOnly (wizards) {
  return renderWizardsList(wizards.filter(isHufflepuff));
}
// внезапно код стало легко реюзать:
function renderSlytherinOnly (wizards) {
  return renderWizardsList(wizards.filter(isSlytherin));
}


Я вообще не очень представляю, где может быть нужен редьюс. Он всегда лишний и выглядит словно программист просто пишет в стиле: «смотрите как я могу!»

Зачем использовать для этого несемантичный редьюс? Хотя, конечно, в этом случае значительно лучше было бы написать так:


Я никак не могу понять, какое значение вы вкладываете в слово «семантичный»?
У редьюсера есть задача, о которой вы сами говорили. Использование его для сторонних задач — нарушает его семантику. К примеру, использовать filter, чтобы вызвать метод, но не использовать его значение для фильтрации — нарушение семантики. Такой код — воняет, хотя прекрасно справляется с задачей:

function loadAll (images) {
  images.filter(function (img) {
    img.load();
  });
}


Тут не подойдёт ни map, ни filter, ни reduce с точки зрения семантики.
Только for или forEach.
Понял вас. По большей части согласен. Кроме одного момента.

Главная задача reduce, как свёртки — «… преобразование структуры данных к единственному атомарному значению...» (Википедия). То есть для реализации функции renderHufflepuff семантически он подходит идеально — берётся массив, обрабатывается, из него получается строка.

А вот эта реализация как-раз «несемантична» и некорректна:

function renderHufflepuff (wizards) {
  return '<ul>' + wizards.filter(isHufflepuff).map(renderWizard) + '</ul>'
}

Некорректна, потому что пропущен .join('') в конце; несемантична, потому что задача map (в данном случае как функтора массивов) — преобразовать из одного массива в другой с сохранением количества элементов; задача filter — удалить элементы из массива. Но, результатом их работы всегда должен быть массив! А вот если задача из массива получить строку — семантично использовать reduce ).

С другой стороны в JS Array#join — это частный случай свёртки. В конечном итоге получается, что оба варианта реализации renderHufflepuff через wizards.filter(isHufflepuff).map(renderWizard).join('') и reduce одинаково семантичны.
Некорректна, потому что пропущен .join('') в конце
Тут вы правы, забыл.

несемантична, потому что задача map (в данном случае как функтора массивов) — преобразовать из одного массива в другой с сохранением количества элементов
Тут — неправы, ведь именно эта задача и решается. Я из массива сырых объектов получаю массив отрендеренных строк. Что там вызывающий код с этим делать будет — не собачье дело мапа)

и reduce одинаково семантичны.
Нет, ибо редьюс кроме своей прямой обязанности объединения — ещё берёт на себя обязаность по фильтрации и маппингу.
Нет, ибо редьюс кроме своей прямой обязанности объединения — ещё берёт на себя обязаность по фильтрации и маппингу.

Не согласен. Прямая обязанность редьюса — «… преобразование структуры данных к единственному атомарному значению...». Что будет сделано в процессе и каково будет атомарное значение — неважно. Например, поиск минимального и максимального значения массива — это тоже свёртка.

Пруфы: в Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire явным образом в числе примеров катаморфизма (с т. з. теории категорий функция свёртки является катаморфизмом) перечислены и нахождение длины массива и фильтрация.
Ну по такой логике можно вообще всё приложение в одном редьюсере написать

Где-то на хабре валялась статья про "трансдьюсеры".

Redux использует как раз такой подход для реализации модели состояния приложения.

Так могу же.

Нет, в том и дело :)


Если у меня функция написана отдельно — я могу пронаблюдать сайд-эффект в ней

Каким образом?


Да, я понимаю, что комплексно с функцией-вызывателем оно вроде бы как чисто.

Оно вообще в принципе чисто. Без каких-либо условностей. Просто обычная чистая функция, как f x y = x + y, не знаю. Никакой разницы.


Для этого вообще-то существует специальный инструмент — вынос кода в отдельную функцию.

Так нет никакой разницы, лямбда у вас или отдельная ф-я. И та и другая может как срать так и не срать совершенно одинаково. Так что по "лямбда/отдельная функция" тут отделить нельзя никак.


Зачем использовать для этого несемантичный редьюс?

Почему не семантичный? Семантичный — у вас фунаргом идет чистая ф-я, и чтобы это показать, вы используете редьюс вместо цикла. А если бы ф-я была грязная — использовали бы цикл.

Каким образом?

Таким:
function druuPureCallback(newArr, wizard) {
  if (wizard.house === 'Hufflepuff') {
    newArr.push(wizard.name);
  }
  return newArr;
}

function Main () {
	const EmptyArray = [];
	
	let result = wizards.reduce(druuPureCallback, EmptyArray);
	
	assert(0, EmptyArray.length); // failed
}


Почему я передаю «чистую фунцию без сайд-эффектов по определению Druu» в редьюс, а он мне, внезапно меняет аргумент?
Почему я передаю «чистую фунцию без сайд-эффектов по определению Druu» в редьюс, а он мне, внезапно меняет аргумент?

Так у вас ссылка неуникальная, с-но применение reduce запрещено и код не пишется. Надо: wizards.reduce(druuPureCallback, []) ну или EmptyArray = wizards.reduce(druuPureCallback, EmptyArray). Тогда ссылки на старый объект нет и применение корректно.
Во всех примерах из статьи это свойство выполнялось. Ну а если вам надо написать именно в том виде в котором вы написали — получается что колбек нечистый, т.к. срет во внешний контекст. И, раз код с reduce писать запрещено (=> такого кода не существует) используем for.

с-но применение reduce запрещено и код не пишется
Кем запрещено?

Так у вас ссылка неуникальная
Ну и что? В иммутабельном мире ссылка и не должна быть уникальной.

если [...] — получается что колбек нечистый, т.к. срет во внешний контекст
Без всяких «если». Колбек — нечистый.
Вопрос вкуса и привычки. Reduce, как и любая другая фича, быстро становится понятным — надо просто начать им пользоваться.

Плюс, он идеально подходит для Promise. Там функции, тут функции. В таком окружении циклы выглядят чуждыми элементами.

Ну и, спасибо разработчикам, гигантская разница в скорости между for и reduce всё более и более уменьшается.
Использование reduce там, где место циклу — нарушение семантики)
Чем он идеально подходит для промисов, если именно цикл корректно работает с await?

for (const img of imgs) {
  await asyncLoad(img);
}


Попробуйте такое редьюсами написать.
imgs.reduce(
  (akk, img)=>akk.then(()=>asyncLoad(img)),
  Promise.resolve()
);

В общем-то тривиально. Только за скобочками трудно следить.

Как Вам сказать… async/await появился на год позже, чем хотелось бы, и подобные вещи я в своё время для дела писал. А сейчас — я считаю это хорошей разминкой ума.

Это нестандартное использование reduce. На счёт красоты тоже можно спорить. В поддержке же вообще — мрак. Если Вам самому «за скобочками трудно следить», каково другим будет?
Все эти «финты» — не более чем показуха.

Простите, почему "нестандартное"?
reduce предназначен для итеративного сведения массива к единственному значению. И именно это делает мой код. Возвращаемое значение — экземпляр Promise.

Но это не сведение к одному значению — промис то пустой. И это не «выжимка» из массива, а фиктивное значение, нужное лишь для управления последовательностью задач в очереди.

Я не говорю, что делать так нельзя. Можно, но могут возникнуть проблемы. Не каждый сразу поймёт, что там происходит — не очевидно это. Умные люди говорят, что мы чаще код читаем, а читать Ваш код не всем будет легко, желательны комментарии.

Сорвётесь Вы на другой проект, а этот отдадут на сопровождение джунам — они ведь мозг могут сломать :)
Сорвётесь Вы на другой проект, а этот отдадут на сопровождение джунам — они ведь мозг могут сломать :)

А вот это уже вопрос привычки и воспитания. Почему декларативные редьюсеры в JS с таким скрипом воспринимаются опытными программистами? Потому что в большом количестве случаев у них классический императивный бекграунд — они начинали на PHP/Python/Ruby или изучали C/Java в институте. Для них естественно мыслить циклами. Я и сам был такой.

Но. Если какое-то время после циклов писать на Promise/reduce/filter/map, то привычки начинают меняться и декларативное программирование становится куда более понятным, естественным. Начинаешь ценить его за лаконичность и идиоматичность. Приведённый пример, кстати, потому так вымученно выглядит на редьюсерах, что он из другого мира — мира синхронных языков типа PHP. JS — асинхронный язык, где всё исполняется в параллели, и асинхронное решение приведённой задачи (Promise.all(imgs.map(asyncLoad));) смотрится кратко и понятно.

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

Чтобы понимать что ты делаешь, нужно знать, что под капотом у синтаксического сахара.

Почему декларативные редьюсеры в JS с таким скрипом воспринимаются опытными программистами?
Та потому что они не декларативные. В вашем примере с промисами никакой декларативности. Вы костылём, зная как устроен промис написали магию, чтобы оно как-то заработало. Что в этом декларативного?

Если какое-то время после циклов писать на Promise/reduce/filter/map
Я лет восемь назад тоже этим увлекался. Кое-какие практики действительно годные. А кое-какие — реально идут от внутреннего: «а я ещё вот так вот могу!»
Костылём?! Что костыльного или магичного в Promise.all(imgs.map(asyncLoad));? Чистый и понятный код.

Что значит «никакой декларативности»? Я говорю движку — пройдись по элементам массива imgs и к каждому примени функцию asyncLoad, результаты собери в массив и прокинь его в then; как ты это сделаешь — мне не важно. Это абсолютно декларативно.
Что костыльного или магичного в Promise.all(imgs.map
Подмена тезиса. Сначала вы говорите о «декларативных редьюсерах», а в примере приводите map, а не reduce. Фу таким быть.
Прошу прощения, если ввёл в заблужение. Для меня map/reduce как функтор/свёртка неотделимы друг от друга. То есть и map и reduce одинаково могут быть декларативны, если они декларативно реализованы в коде.

И тем не менее, повторюсь, Что костыльного или магичного в Promise.all(imgs.map(asyncLoad));?
В этом — ничего. А в конкретном примере с редьюсом — очень много.
Но это был не мой пример ) Похоже вы спутали меня с его автором.
Но это не сведение к одному значению — промис то пустой.

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


Ну, понимаете,
если бы я считал сумму, я бы написал


(akk, a)=>(akk+a) 

если бы в массиве лежали асинхронные функции, которые нужно вызывать без аргументов, я бы написал


(akk, fun)=>(arr.then(fun))

В массиве лежат аргументы для одной и той же функции, я мог бы написать


imgs.map(img=>()=>asyncLoad(img)).reduce(
  (akk, fun)=>(arr.then(fun)),
  Promise.resolve()
)

но я решил заинлайнить создание функции ()=>asyncLoad(img).


При всём уважении к программистам на других языках, для JavaScripter-a здесь не должно быть трудностей.

И всё равно я остаюсь при своём мнении :)
«Выжать» скаляр из массива -> reduce
Выполнить последовательно функции -> цикл
Выполнить последовательно функции -> цикл

Сложение, например, как в первом примере.
И я решительно не понимаю противопоставления: у reduce под капотом — тот же самый цикл.

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

Тогда я вообще не понимаю, в чём претензия к моему примеру. Редьюсер, который нанизывает промисы в цепочку друг за другом, и возвращает ссылку на последний промис — самый что ни на есть каноничный :)


С тем же успехом это мог бы быть не промис, а любой объект, у которого есть метод, принимающий аналогичный объект, и возвращающий новый аналогичный объект.


Не знаю, ну, скажем:


transforms.reduce(
  (akk, transform)=>(akk.matrixMul(new Matrix(transform)),
  new Matrix(1)
)

Где Matrix — матрица, метод — матричное умножение, а transform — какие-то условные данные, по которым можно создать матрицу,
вопросов не вызывает?

Мои претензии:
— Он слабочитабельный в сравнении с альтернативами
— Он не декларативный
— Использование Promise.resolve — хак

Мы здесь спорим не о том, что этот код плох, а о том, сколько ангелов поместится на острие иглы, если их сажать на неё последовательно каноничное это использование reduce, или нет.


По вашим претензиям


Он слабочитабельный в сравнении с альтернативами

Согласен с поправкой, что ХУЖЕ читаем, но "слабо-" — это Вы явно утрируете.


Он не декларативный

Не вижу в этом проблемы. Цикл — вообще из императивного мира.


Promise.resolve — хак

Не более, чем Array.from. Всего лишь метод, создающий объект.

каноничное это использование reduce
Про каноничность лично у меня претензии к другому примеру.

Во вашим комментариям — этот пример всем хуже цикла. Так зачем его использовать?
Так зачем его использовать?

Бог с Вами! С тех пор, как появились async/await — не надо его использовать.

А зачем такое писать редьюсерами, если есть Promise.all ))

Promise.all(imgs.map(asyncLoad));

Семантика разная — параллельная работа в вашем случае, последовательная в ветке выше. Иногда запускать всю пачку промисов параллельно (дохренадцать запросов к API, к примеру) — всё-таки не лучший вариант.

Ну браузер и не будет выполнить дохренадцать запросов одновременно, он разобьёт на куски. В этом и суть декларативного подхода — ты ставишь задачу, движок/браузер решает как её выполнить.

Async/await в JS, на мой взгляд, выглядят неидиоматично. Потому что JS — асинхронный язык, и эти куски блокирующего кода смотрятся в нём чужеродно.
он разобьёт на куски.

Если бы с этим не было багов — мы бы не писали такие редьюсеры.

Ну браузер и не будет выполнить дохренадцать запросов одновременно, он разобьёт на куски.

Один раз потребовалось выполнить порядка сотни запросов в произвольном порядке и собрать результаты. Сначала написал этот самый Promise.all(queries.map(...)) — запросы таки улетели все параллельно и изрядно затормозили страницу. Переписал так, чтобы отправлялось не более пяти за раз — проблема ушла. Так что не всегда всё так хорошо.

А у меня обычно выходило, что запросы улетают чаще, чем это возможно для сети и сервера, в результате чего ответы приходили только на небольшую их часть. Причём так было всегда, когда я делал Promise.all не к локальным ресурсам.

Зачем писать редьюсами, если есть форич?

Отличная статья! Будет полезна всем начинающим!

Чтобы организовать обработку списка в один проход не надо торопиться делать странные вещи, отказываясь от filter и map.


Например, нужный эффект можно получить с помошью трансдьюсера (есть хорошее интро по теме:
https://www.jeremydaly.com/transducers-supercharge-functional-javascript/ ). В библиотеке lodash, упомянутой в статье, есть похожая фича для filter и map, но с механикой на базе итераторов (chainable methods). В RxJS/IxJS оно так работает по определению.

Красиво, но не очень интуитивно понятно, особенно для сторонних разработчиков. А в ряде случаев вообще больше похоже на «смотри как я еще могу». Поэтому я большинстве случаев я бы выбрал более классические методы. По себе я помню лишь несколько случаев когда reduce() действительно органично вписался в задачу.
UFO just landed and posted this here
Reduce — полезная функция. Но холивар про reduce vs цыкл будет вечным. Как бы единственным недостатком что нельзя reduce прервать. Ну и поменьший недостаток что reduce не асинхронный.
И вот такое работать не будет.

result = await arr.reduce(async ()=> await ...);
....
return result;


А так довольно лаконично можно писать.
result = await imgs.reduce(
  (akk, img)=>akk.then(async ()=>(что-то там))),
  Promise.resolve()
);

Будет. Вполне будет.

Sign up to leave a comment.

Articles