Pull to refresh

Comments 88

>И наконец, последний метод решения, который я и ожидал от всех кандидатов.
Хм. Не, ну метод в общем вполне возможный, но с другой стороны — это способ из мира ФП, ожидать его от всех — ну это выглядит немного как перебор. Ну и потом, в таком языке, как js, когда система типов вам многое не подсказывает, сломать нетривиальный reduce при рефакторинге — раз плюнуть.

Примитивнее reduce — разве что только map. Что там трудного и причем тут вообще типы?

>Что там трудного и причем тут вообще типы?
Посмотрите скажем вот сюда, тут есть примерчик, когда reduce используется на слегка более сложных типах, чем числа, и что из этого может получиться. Насколько автор легко забирается в дебри, и насколько мало язык ему в этом помогает.

А типы тут при том, что функция в reduce да, сложнее, чем в map. И если аргументы (и результат) не просто числа или строки (а например объекты js) — то сломать, а потом починить это будет совсем не просто. Да и читать тоже. В языке со статической типизацией (хотя бы на уровне Java) компилятор обычно подсказывает, что вы функцию сломали, и сигнатура не соответствует — а тут в js этого не будет.

Я не против reduce вовсе — я за то, чтобы в js его применять аккуратно.
тут есть примерчик

В этом примерчике используются функции с побочкой.
Если использовать чистые функции — то всё просто и понятно не зависимо от типа возвращаемого значения.


а тут в js этого не будет.

Ну да. Приходится самому смотреть и километрами комментов код покрывать.

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

А иммутабельную структуру и чистую функцию построить не совсем тривиально.

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

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

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

reduce:
— читается гораздо хуже чем «for of»
— выделяет лишнюю память на функцию аккумуляции, то есть GC лишний раз будет дергаться
— выделяет лишнюю память на promise обертки, то есть GC лишний раз будет дергаться
— не короче чем «for of»:
async function seriesFetch(urls, callback, params) {
    for (const url of urls) params = await fakeFetch(url, params);
    callback(params);
}

Новичков всегда можно было определить по неумению работы с reduce
Новичков считающих себя «не новичками» можно определить по использованию reduce там где этого не требуется.

Да и, судя по бенчмаркам на всяких jsperf, нативные Array.* (в том числе и reduce) функции в браузерах все так же медленее альтернатив.
Один из тестов: https://jsperf.com/foreach-vs-reduce-vs-for-loop (можно сравнить разные ревизии этих бенчмарков еще)
Плюс, в Firefox результаты значительно хуже, чем в Chrome.


Медленнее они потому, что делают значительно больше, чем тот же for (но, думаю, вы это и сами знаете).

Ну тут скорее упертость автора, чем скорость.
Вряд ли будет реальная разница на 5-10 элементах массива. Ведь не будет же адекватный человек делать последовательно миллионы запросов.
Я не большой знаток js, подскажите, а какое поведение будет у такой цепочки фетчей, если например на 2й или 3й урл из списка, сервер внезапно вернет ответ 301 и location указывающий на 1й урл?
Ничего необычного. Запрос пройдет как должен. Массив и редиректы не связаны.
Не могу не согласиться. Никто не поддает сомнению, что императивные for быстрее в несколько раз функций высшего порядка.

И forEach, в свою очередь, быстрее map (хотя в случае с forEach это происходит скорее из-за того, что forEach просто вызывает callBack функцию не дожидаясь результатов).

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

Это через-чур субъективно. Скажем for-of цикл вместо forEach, имхо, значительно более нагляден. А вот for-of цикл вместо map — наоборот. С reduce надо смотреть на конкретные примеры. До ввода в язык => функций практически любая FP-конструкция выглядела ужасно. С чейнингом да, только FP вариант. Зато с async-await FP это прямо БОООООЛЬ. Ну и т.д. Зависит от конкретного примера.


Очень сильно читаемость уходит в сторону функций когда начинаешь применять |> оператор, особенно с подстановками (#, ?). Но его уже сколько лет в стандарт не добавляют. И в TS нет ни одного способа с ним жить (приходится даже в импортах из js файлов всё воротить назад).


А вот вопрос производительности почти никогда не стоит в таких вопросах. Ибо спички. Надо сильно постараться чтобы бутылочным горлышком стал именно выбор .map вместо for-of.

>Это через-чур субъективно. Скажем for-of цикл вместо forEach, имхо, значительно более нагляден. А вот for-of цикл вместо map — наоборот.
Ну это смотря что считать наглядностью. Скажем, если не нужно анализировать код на отсутствие побочных эффектов, то это сильно упрощает анализ. В этом смысле map сильно проще, так как мы знаем заранее, каков тип результата. А для forEach мы знаем, что весь результат состоит из побочных эффектов. Если наглядность — это легкое понимание назначения кода, то чем это для вас не она?

>С reduce надо смотреть на конкретные примеры.
У reduce вполне конкретная и не сложная сигнатура. В языках с нормальной статической типизацией вам компилятор прекрасно выведет тип результата из типа исходных данных и сигнатуры функции в reduce. Смотреть на примеры нужно имено потому, что в js с типами все слабовато.

Ваш аргумент "когда я вижу forEach я точно знаю, что там side-effect-ы, а когда я вижу for-of их там может не быть" признаю. Честно говоря я даже не задумывался над этим. Но мне кажется он всё же не пересиливает визуальную составляющую и читаемость. Меньше лишних скобок и прочих символов. Но да, это субъективщина пошла.


Но вот как быть с аргументом асинхронности? Нагромождение .then уж точно читается хуже прямолинейного кода с for-of.
Как быть со стек-трейсами? Они почти всегда за for-of.


Всё же мне кажется, что в зависимости от конкретного кусочка кода читаемость того и другого решения будут разными. Где-то выигрывает for-of, где-то forEach. А где-то даже for(...; ...; ...).


У reduce вполне конкретная и не сложная сигнатура

По моему опыту начинающим и иногда продолжающим разработчикам .reduce сложен. Условно код где есть for-of, внутри какая-нибудь проверка или две, какое-нибудь простое вветвление, а внутри push в массив — всё это воспринимается куда проще, чем нагромождение логики в reduce с возвратом того же массива. Привести пример?


P.S. минусовал не я

>он всё же не пересиливает визуальную составляющую
А я ни разу и не настаивал. Я всегда старался уточнить, что это зависит от исходной подготовки, опыта, и т.п. Да даже и вкусов, наверное. Ну т.е. наверное найдутся люди, кому читать другие варианты проще. С учетом стектрейсов и т.п. — возможно таких даже много. Вообще мой основной пункт был в том, что reduce штука в целом несложная, и для описанной тут задачи подходит вполне, так что на места автора это решение я бы ожилал увидеть тоже. Но на пару других вполне бы согласился.
>reduce:
>— читается гораздо хуже чем «for of»
>использованию reduce там где этого не требуется
Ну, тут вопрос вкуса/подготовки. Причем с кучей дополнительных ограничений.

Я бы несколько иначе выразился. В данном случае reduce применяется по делу — т.е. ровно для того, для чего оно в общем-то и задумано. И в текущем виде код (для меня) выглядит вполне прозрачно.

Но, в тоже время, сделать так, чтобы это вообще не читалось — очень легко. Мне сразу вспоминается пост под названием вроде «Как Яндекс научил меня проводить собеседования» (не могу к сожалению его найти), где понять reduce на несколько строк уже было невозможно совершенно. Ну то есть, грань между «читается хорошо» и «не читается вообще» — довольно тонкая.
В данном случае reduce применяется по делу — т.е. ровно для того, для чего оно в общем-то и задумано. И в текущем виде код (для меня) выглядит вполне прозрачно.
for-of:
— лучше читается
— меньше кода
— меньше потребления памяти / дерганий GC
— выше скорость

Единственный плюс reduce это изолированность переменной аккумулятора.
>— лучше читается
Я таки повторюсь, что это дело привычки, опыта, вкуса.

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

Вы какой-то бред городите. Тот же async/await гораздо затратнее фича, если сравнивать с reduce. Асинк функция все равно завернет это в Promise. Про GC вообще не понятно откуда вы это взяли. Тем более — сейчас оптимизатор работает лучше именно с функциональными паттернами — так как он прекрасно представляет что и где можно ожидать. Чем императивнее (а for-of это именно оно) — тем больше неизвестных для оптимизатора. Reduce — одна из самых банальных функций, которую можно представить и тут она, кстати, вполне к месту.

Разве на подобных собеседованиях не принято искать того кто будет разгребать говны написанные собеседующими

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

выделяет лишнюю память на promise обертки

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

— выделяет лишнюю память на функцию аккумуляции, то есть GC лишний раз будет дергаться

Для переменной const url в цикле for, тоже выделяется память, при чем она выделяется на каждой итерации кода.

— не короче чем «for of»:

«for of» тоже может быть решением, но тогда давайте объективно оценивать. А для этого нам этот код надо привести в общий вид, произведя форматирование кода. Потому что reduce тоже можно превратить в однострочник, только это будет не объективно.
И тогда код приобретет примерно следующий вид:
async function seriesFetch(callback) {
  let result;
  for (const url of urls) {
    result = await fakeFetch(url, result);
  };
  callback(result);
}

В таком варианте уже можно сравнивать объективнее.

Новичков считающих себя «не новичками» можно определить по использованию reduce там где этого не требуется.

Оценил ваш сарказм

Новичков считающих себя «не новичками» можно определить по использованию reduce там где этого не требуется.

Оценил ваш сарказм

А вы зря думаете что это сарказм или шутка, так и есть на самом деле, к большому сожалению. И речь не только о reduce конечно.
В таком варианте уже можно сравнивать объективнее

Имхо ваш вариант через for-of лучше:


  • все лишние абстракции (вроде .then) остались пределами кода, осталась только суть
  • в случае ошибки вы получите хороший stack-trace, а не чушь
  • в случае расширения функциональности это будет сделать проще
  • начинающие разработчики легко понимают такой код, а вот с reduce уже у многих проблемы

На абсолютную объективность не претендую

в случае ошибки вы получите хороший stack-trace, а не чушь

Я всегда получаю хороший stack-trace, даже когда не было никакого await и всё писалось на then. Просто не надо забывать обрабатывать catch.

Мне кажется вы не очень понимаете о чём я говорю :) .catch даст вам отловить ошибку
в нужном месте, но вот причём тут её stack trace? В случае async-await он будет примерно таким же, как если бы, условно, код был синхронным. Никаких дырок, никакой рванины и оборванных трейсов, а простая и понятная чёткая последовательность что где кого вызвало.

но вот причём тут её stack trace

Потому что в catch есть ошибка, в зависимости от того, где мы её ловим, мы либо бросаем её дальше, либо пишем её stack в консоль.
Не припомню ни одного случая, когда я не смог получить таким способом достаточно информации для локализации ошибки.

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


return someFunc()
  .then(someOtherFunc)
  .catch(err => {
    console.log(err.message, err.stack);
    throw err;
  })

Вы имеете ввиду нечто вроде этого для всех применений promise в вашем приложении? Прямо коммитите такой код в репозиторий?

Вы имеете ввиду нечто вроде этого для всех применений promise в вашем приложении?

Кажется, я понял, что Вас смущает. Я пишу почти исключительно для cli. Не для браузера. И консоль для меня — это stdout.
Процедура верхнего уровня должна в конечном итоге написать ошибку в консоль. Внутренние процедуры — должны правильно сгенерировать ошибку. Для этого, там где это необходимо, нужно добавить обработчик catch, оборачивающий ошибку.
Я имею в виду, что в случае возникновения ошибки выполнения, я увижу в консоли достаточно данных для того, чтобы её локализовать.

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

В случае async-await вы получаете эти данные просто по-умолчанию, сразу. Ошибка уже локализована.
Но возвращаясь к вашему случаю. Тут дело не в том куда вы её выводите. А в том, что для локализации вам потребуется прописать лог-обработку ваших ошибок абсолютно во всех местах, где вы используете promise-ы. Ну и в итоге вы получите ГОРУ стектрейсов. А дальше как детектив будете по ним продираться. Не спорю, что это куда лучше, чем никак. Но:


Я всегда получаю хороший stack-trace, даже когда не было никакого await и всё писалось на then. Просто не надо забывать обрабатывать catch.

^ это отнюдь не "хороший stack-trace" и не "просто не забывать обрабатывать catch" :)


P.S. Вам не проще было сам прототип у всех видов promise поменять, чтобы не писать такое руками?

абсолютно во всех местах

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

Отдельно отмечу, что на nodejs в сложных приложениях бывает очень сложный механизм обработки ошибок. С разными соглашениями и слоями. Т.е. это не сводится к тому, чтобы везде поставить .catch и вывести в консоль а затем за-rethrow-ить :)


Но CLI решения, действительно, могут быть куда проще, чем серверные.

let result = await fetch():
while(result){ // correct condition there
result=await fetch(result);
}


Если не провтыкал, покрывает все пункты.

Или немножко изврата


let res=await fetch();
class Test{
get t() {
const r = await fetch(res);
return r?this.t:res;
}
}


new Test().t.
Немножко неявный изврат на тему рекурсий.

А, ну да, забыл еще передать в качестве аргумента элемент массива, но это уже тривиально

Ну и совсем изврат — конкатенация строк с последующим исполнением)
let res;
for(let i=0;i<urls.length;i++) {
res = await fakeFetch(urls[i], res);
}


Не читал еще недостатков всех методов, но для решения поставленных условий этого вполне хватит.

Если вас чем-то не устраивают решения на собеседовании, причем «выясняется потом», значит у вас задача криво поставлена. В реальности то решение пишет под конкретные условия.

UPD: Прочитал все решения. Автор какую-то дичь несет. Зачем все это? Чем результат async функции будет отличаться от функции, возвращающей Promise? Зачем вам нечитаемый reduce, когда это просто перебор массива? Тут не с собеседуемыми проблема, а с собеседованием.
Автору очень не нравится что Приходится объявлять функцию как Async!

По мне, лучший вариант. Разве что на for of можно заменить.

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


Если сильно хочется иметь на выходе именно синхронную функцию, то наиболее читабельный вариант мне видится так


function makeRequests(urls, initialData, callback) {
  let result = Promise.resolve(initialData);
  for (const url of urls) {
    result = result.then(res => fakeFetch(url, res));
  }

  return result.then(res => callback(null, res).catch(callback);
}
Если сильно хочется иметь на выходе именно синхронную функцию

не было такого непреодолимого желания. Ваш вариант тоже имеет место быть, только тогда надо его привести в общий вид, примерно, так:
function makeRequests(urls, initialData, callback) {
  let result = Promise.resolve(initialData);
  for (const url of urls) {
    result = result
      .then(res => fakeFetch(url, res));
  }

  return result
    .then(res => callback(null, res);
}


и потом уже сравнивать
Ну, дожили. Люди, не осилившие циклы while и for по массиву из трёх элементов, учат проводить собеседования программистов.
(ирония если что. горькая).

Мы видимо с вами на разных планетах живём, судите сами:


  • Ваш вариант с reduce это как раз то, что мне пришло в голову в самую первую очередь, как самое очевидное и довольно простое решение. Я бы с него как раз начал а дальше, исходя из вашей реакции, двигался бы в сторону решения которое нравится интервьюверу
  • Я решительно не понимаю где вы находите людей умеющих аж в ручную обработку асинхронных генераторов, но которые при этом применяют их не по делу. Те кого приходится мне собеседовать таких слов никогда и не слыхали. На позицию синьёров. Не знают даже азов языка.
  • Что вы такое вообще делаете с вашими кандидатами что они вместо 3-4 строчек пишут такие сложные итерационные решения? Если человек умеет разобрать по кусочкам и проитерировать асинхронный генератор руками (велкам к нам в Мюнхен кстати), то как надо его запугать, чтобы он начал городить такую дичь?

ИМХО, вот этот вариант (если я вообще правильно понял задачу), лучший


async function solution<T>(urls: string[]): Promise<T> {
  let result: any;
  for (const url of urls)
    result = await fetchQuery(url);
  return result;
}

почему?


  • предельно простой код (легко дебажить, легко расширять, легко понимать)
  • возвращет Promise (как вам вообще в голову идея с callback пришла в 2020)
  • принимает urls как аргумент (а вы его неизвестно откуда берёте)

Или я таки вообще не понял задачи? Тут… 4 строки кода же.

Или я таки вообще не понял задачи? Тут… 4 строки кода же.
Нужно обязательно reduce использовать, потому что интервьюер вчера читал книжку о функциональном программировании.
Прям интересно стало. Что такого в «асинхронных генераторах»? Почему это вообще выделяется в отдельное понятие и в чем такая уж редкость умения понимать генераторы и промисы?
и в чем такая уж редкость умения понимать генераторы и промисы

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


Асинхронные генераторы как тема подразумевает, что человек понимает: асинхронность в js, promises, iterators, generators, и как вишенка на торте async generators. Ну т.е. с точки зрения навыков владения языка это уже ближе к продвинутому уровню. Обычно народ валится уже на простых замыканиях

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

Ваш вариант с reduce это как раз то, что мне пришло в голову в самую первую очередь

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

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

возвращет Promise (как вам вообще в голову идея с callback пришла в 2020)

callback для возвращения использовался только для того, чтоб постараться поставить все решения в одинаковые условия (и делалось это только для заметки на habr), потому что в случае к возвращением промиса, например, рекурсия или reduce получат преимущества в виде уменьшения кода на одну строку, так как можно будет просто сделать:
return url.reduce

В случае же с генераторами и асинхронными генераторами этого не выйдет.
Но тем не менее, даже в 2020 году callback все еще используется, как минимум на стороне сервера, например, загляните посмотрите в сторону NodeJs: nodejs.org/api/fs.html
то что интервьювер ожидал и что ему нравится — разные вещи

В идеальном мире да. На практике далеко не всегда. Если есть желание взять эту позицию, то приходится играть в детектива\психолога, и пытаться понять что "зайдёт" конкретному интервьюверу, а от чего он поморщит нос.


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


обсуждаются и решаются у доски (так называемый white board interview)

Худшее, что я пробовал. Дайте вашему кандидату хотя бы ноутбук. Когда человек долго не пользуется мелом и не пишет на вертикальных поверхностях то это дополнительный стресс. Особенно если он вообще отвык что-то писать руками. Недавно проходил вайтбординг — на мой взгляд это большая глупость, если ваша задача не посмотреть как отдельно взятый погромист умеет справляться с внезапным стрессом.


преимущества в виде уменьшения кода на одну строку

Одним из больших откровений которое я давно уже для себя открыл было: не надо бороться с количеством строк. Надо бороться со сложностью кода. Если вы можете сделать код проще написав на 50% больше кода — пишите. Именно сложный код плодит баги, которые тратят потом кучу времени, именно сложный код потом тяжело рефакторить и изменять, что приводит к костылям, именно сложный код потом помешает вам нанять джуниора, вместо миддла, т.к. первый просто не сдюжит с вашей кодовой базой.


Это я не конкретно этот пример описываю, а вообще. Когда вы начинаете считать строчки кода, задумайтесь — а оно вам надо?

Это я не конкретно этот пример описываю, а вообще. Когда вы начинаете считать строчки кода, задумайтесь — а оно вам надо?


И я полностью согласен с вашими доводами по сложности кода и количеству строк. Просто нужно было хоть как-то оценить разные подходы в сравнении(и это только в рамках заметки на habr). Как я и написал, я не придумал другого способа оценки. Но если вы можете предложить нечто более подходящее — я с радостью перепишу раздел с выводами.
Но тем не менее, даже в 2020 году callback все еще используется, как минимум на стороне сервера, например, загляните посмотрите в сторону NodeJs: nodejs.org/api/fs.html

чуть чуть промотать:
nodejs.org/api/fs.html#fs_fs_promises_api

Я просто оставлю это здесь:


const syncFakeFetch = $mol_fiber_sync( fakeFetch )

const fiberWay = $mol_fiber_root( ()=> {
    return urls.reduce( ( arg , url )=> syncFakeFetch( url , arg ) , undefined )
} )
Вы точно знаете что такое простой, легко и быстро воспринимаемый и читаемый код?
Вы точно знаете что такое простой, легко и быстро воспринимаемый и читаемый код?

Думаю, это довольно субъективное мнение. Но все, надеюсь, согласятся с тем, что решение с использованием генераторов намного более громоздкое и не наглядное (или все же кто-то считает иначе?). Именно это и было целью этой заметки. А еще рассуждения на тему того, что у любой задачи есть несколько решений, и в каждом конкретном случае, какие-то подходы могут быть избыточны или громоздки.
Скорее тут генераторы просто не к месту.
Они нужны когда вам требуется что-то делать с промежуточным результатом (т.е. зачем-то получать результат yield). Таких задач не так уж много.

В этой задаче же нужно получить только итоговый результат. Если кто-то решит на таком использовать генераторы — у него явно мания использовать генераторы ради генераторов.
Я с вами полностью согласен, но я попытался реализовать ровно те способы решения, которые предлагали сходу кандидаты.
В любом случае, если вам необходимо еще проверить умение работы с гененераторами/рекурсиями/AsyncAwait/Reduce/For of, то это можно сделать в рамках одной задачи, нежели на каждый случай формулировать новую.

В любом случае, если вам необходимо еще проверить умение работы с гененераторами/рекурсиями/AsyncAwait/Reduce/For of, то это можно сделать в рамках одной задачи, нежели на каждый случай формулировать новую.

1) Если так нужны генераторы, то да. Но они с 2016 года не актуальны, т.к. async/await.
2) А вот всё остальное, вообще не подходит к такой задаче, потому что это просто бессмыслица и никто в здравом уме никогда такую задачу такими способами решать не будет. Мы уже давно не живём в эпоху callback hell'a и работа с асинхронщиной проста и приятна как никогда, благодаря async/await.
Все просто:
1) Для всех последовательных асинхронных операцией async/await, для параллельных Promise.all.
2) Если для того, чтобы код был более простым, понятным и читаемым нужно написать на 1 или несколько строк кода больше, то лучше сделать так.

Решение: codesandbox.io/s/frosty-ramanujan-x8yci

Код функции:
async function asyncAwaitWay() {
  let lastResult = '-';
  for (const url of urls) {
    lastResult = await fakeFetch(url, lastResult);
  }
  return lastResult;
}

(async () => {
  const result = await asyncAwaitWay();
  log(`result: ${result}`);
})();

Кстати в вашей погоней за кол-вом строк кода, их тут 5, что является лидером в вашей табличке.

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

Более того, смешно в 2020 году для асинхронщины использовать генераторы и все остальные варианты предложенные вами. Это так же смешно было и в 2016 году, потому что уже тогда babel и typescript знали что такое async/await и транспайлили его в понятный браузеру код.

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

А есди человек спросил "как лучше? Можно так, а можно сяк, можно даже генераторы натянуть… Что вы сейчас проверить хотите?"

И именно тот способ, который я ожидал изначально услышать, никогда не назывался (речь идет о методе reduce, он будет последним решением).

Автор как бы уже сказал, что для него является самым лучшим решением. И его удивляет, почему для остальных это решение не лучшее. Меня удивляет наоборот то, почему для него async/await не является лучшим решением и вместо этого он предпочитает reduce. Отсюда я боюсь представить даже какой в целом код на проектах под его началом.
> его удивляет, почему для остальных это решение не лучшее
Это вы так поняли.
>именно тот способ, который я ожидал изначально услышать, никогда не назывался
А так у автора.

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

Async/await значительно более сложный вариант? Менее подходящий? Устаревший? Интересные размышления однако…
А вот если он начинает предлагать reduce, генераторы и т.п., то я бы от таких людей держался подальше и ни когда бы не взял себе в команду

Ахахахах. Серьёзно? Т.е. приходит к вам фронтенд-архитект с 15 годами опыта и задвигает .reduce + .then, потому что у него FP головного мозга (в хорошем смысле), а вы ему — фи, пшёл вон?


Вы правда проводите собеседования? Или это фантазии на тему: "если бы мне доверили собеседовать людей себе в команду"?

потому что у него FP головного мозга (в хорошем смысле), а вы ему — фи, пшёл вон?

Люди страдающие FP головного мозга(в хорошем смысле) не сочетаются с людьми не страдающими этим недугом, поэтому они не сработаются, т.к их мировозрение и понимание хорошего кода кардинально разные, так зачем же тратить время друг друга в пустую?
Ахахахах. Серьёзно? Т.е. приходит к вам фронтенд-архитект с 15 годами опыта и задвигает .reduce + .then, потому что у него FP головного мозга (в хорошем смысле), а вы ему — фи, пшёл вон?

Серьёзно.
Вы правда проводите собеседования? Или это фантазии на тему: «если бы мне доверили собеседовать людей себе в команду»?

Правда.

Чудесно. Искренне надеюсь что ваше поведение в реальной жизни полностью соответствует вашему образу на хабре. И в особенности во время интервью. В противном случае было бы очень обидно случайно попасть к такому человеку, а потом выяснить с кем же тебе придётся работать.

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


Кнопка сброса кармы одноразовая, кажется, да?

То есть люди, у которых есть свое мнение, которые его обосновывают, готовые вступать в споры, это не те люди с кем можно работать и набираться от них полезного для себя опыта и открывать глаза на некоторые вещи да?
Я свою точку зрения пересматриваю каждый раз, когда вижу альтернативы или когда кто-то меня убеждает в чем-то ином. Я не принципиальный, просто у меня есть свое мнение и оно корректируется постоянно с течением времени.
Когда-то меня бесил React, но потом я узнал о том, что такое MobX в связке с React и это стало моим любимым инструментом.
Когда-то меня бесил Node.js в качестве бэкенда, но с появлением async/await я его полюбил.
И так далее и тому подобное…
А ещё иногда я могу потролить.
То есть люди,… течением времени.

Ну вы же понимаете, что так говорят ВСЕ. Включая тех, кто так не делает.


Я не принципиальный

всё так, да:


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

:-D, совсем не принципиальный. Просто "есть два мнения, моё и неправильное", да? :)

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

:-D, совсем не принципиальный. Просто «есть два мнения, моё и неправильное», да? :)

Это мое мнение. И оно не просто так взято с воздуха, а из опыта и из практики. При чем тут принципы?
Ну вы же понимаете, что так говорят ВСЕ. Включая тех, кто так не делает.

Вам виднее, вы похоже эксперт в людях.
Когда-то меня бесил React, но потом я узнал о том, что такое MobX в связке с React и это стало моим любимым инструментом.
Когда-то меня бесил Node.js в качестве бэкенда, но с появлением async/await я его полюбил.
А вот если плотно подсесть на TypeScript, то и JavaScript в целом можно полюбить.
ИМХО, так себе "'эталонное" решение.
Синхронный reduce — это по сути конструктор цепочки then, то есть его результат в конечном итоге равнозначен Promise.resolve().then().then().then()....then(). А значит:
1 — он сразу выделяет память под кучу дополнительных анонимных функций
2 — впихнуть туда какую-то дополнительную логику будет сильно сложнее
3 — компилятору это всё будет сильно сложнее оптимизировать
Зато потом можно сказать что я делаю как написано в книгах по ФП и в блогах 5+ летней давности (когда еще не было for-of). А то что под капотом это «ад» дойдет когда человек сам попадет на нормальное код-ривью.
простите, я до сих пор пишу на олдскульном JS и использую var вместо let и const потому что некоторые устройства падают от введения этих самых слов. Поэтому все ваши новомодные фишки не особо стремлюсь изучать. Но мне интересно другое.
>>> приближено к реальной задаче
можете пару примеров реальных задач накидать, где нужно хотя бы три последовательных вызова сделать? Ну так просто для общего образования. Если не сложно. Спасибо.
пару примеров реальных задач накидать, где нужно хотя бы три последовательных вызова сделать?
На API-шках часто бывает нужно. Особенно на тех, которые делали не вы, и которые никто под вас доделывать не собирается.
В самом ядрёном случае, который мне попадался, нужно было для графика вытащить список категорий, по нему список временных рядов, а по нему для каждого ряда отдельно запросить последовательность значений. То есть даже ещё сложнее, не просто 3 вызова последовательно, а лавинообразно нарастающий на каждом шаге массив параллельных запросов, который нужно было ещё и в ширину ограничивать.
некоторые устройства падают от введения этих самых слов

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

да нет, зачем что-то подключать? вводить дополнительный слой абстракции, дополнительный шаг. Все это не сделает код проще. Я смотрю на тенденцию, все только усложняется и усложняется. И не понимаю зачем, все работает и так. Новые фичи добавляются и так. Того базового функционала хватает за глаза. Я не понимаю зачем люди делают простые вещи сложными. Не подумайте что я против всего этого. Мне на данном этапе увеличивать сложность ни к чему, добавлять процесс для бабеля перевода в старый ES5 тоже не надо, если можно писать прямо на нем. Мне просто интересны были кейсы когда и где нужны несколько последовательных вызовов к API из JS.
если данные на разных серверах, собираются отдельно. все равно зависимость увеличивается, если один из серверов не ответит, данных не будет. Ну мне просто были интересны случаи. А не вот это вот все.

Чтобы начать писать короткие стрелочные функции вместо длинных анонимных.


Чтобы избавиться от надоевшего паттерна var that = this;


Чтобы перестать писать IIFE в цикле.


Всё это делает код намного более простым.

Я смотрю на тенденцию, все только усложняется и усложняется. И не понимаю зачем, все работает и так. Новые фичи добавляются и так. Того базового функционала хватает за глаза. Я не понимаю зачем люди делают простые вещи сложными.
увеличивать сложность ни к чему, добавлять процесс для бабеля перевода в старый ES5 тоже не надо, если можно писать прямо на нем.
То есть вы правда считаете, что
такой код
function Foo() {
    
    this.asd = 3;
}


Foo.prototype.bar = function (param, resClb) {
    var that = this;
    fetchOne(param, function (error1, res1) {
        if (!error1) {
            fetchTwo(res1, function (error2, res2) {
                if (!error2) {
                    fetchThree(res1, res2, function (error3, res3) {
                        if (!error3) {
                            var res = res3.map(function (item) {
                                return item.zxc * that.asd;
                            });
                            resClb(res);
                        } else {
                            resClb(null);
                        }
                    })
                } else {
                    resClb(null);
                }
            })
        } else {
            resClb(null);
        }
    })
};

проще чем такой?
class Foo {
    asd = 3;
    
    async bar(param) {
        try {
            const res1 = await fetchOne(param);
            const res2 = await fetchTwo(res1);
            const res3 = await fetchThree(res1, res2);
            
            return res3.map(item => item.zxc * this.asd);
        } catch (e) {
            return null
        }
    }
}



Да и вообще, большая часть сложностей возникла как раз из-за тех, кому «всего хватало». Вместо постепенного внедрения всё это копилось годами, а когда прорвало, оказалось что «всех устраивающие» платформы не пригодны для работы, и ради них приходится городить костыли.
я не говорю что по старому проще, я говорю что я не готов ввести в эту формулу еще дополнительный этап компиляции. Потому что устройства не понимают ES6. Я просто просил рассказать о кейсах, когда нужны такие последовательные вызовы. Я понял что пишу много лишнего ) прошу прощения. Я понимаю что async await это лучшее решение и более простое чем callback я не спорю именно с этим. Но мне не нужен дополнительный процесс перевода из одного языка в другой, через какой то дополнительный инструмент. потому что я знаю что потом, когда что-то пойдет не так, мне будет сильно больно, от того что я не буду знать куда смотреть и виноват будет этот babel а не я, хотя возможно и я, но я буду винить во всем его. Поэтому я не хочу перекладывать ответственность на что-то другое, и потом когда приходится дебажить ты понимаешь что виноват только ты и никто другой.
Т.е. ты пишешь вот здесь одно, оно все работает. прогоняешь через преобразователь ES6->ES5 а на выходе оно работает не так как ты хочешь, не так как должен был быть. И вот тут начинается вся боль. Я этого не хочу, а это будет.
Поэтому я с Вами согласен полностью, что оно может быть проще в целом и представленный Вами пример тоже сильно проще. Но первый вариант я понимаю как работает, и он работает. Но если я напишу как на втором варианте потом скормлю его условно babel'ю, который мне сделает первый вариант. Который я со временем перестану понимать, и потом там что-то не будет работать. В общем. вот так у меня все плохо. но это данность. Которую я не хочу ухудшать. И переход на ES6 в моем случае не сделает лучше. сделает хуже.

Возможно если вы пересилите себя и попробуете, то через полгода вы уже без тошноты на ES5 смотреть не сможете. Просто ввиду того, что old school JS очень неудобный. А если вы ещё всяких линтеров завезёте и CI/CD воткнёте… DX не пустое место в общем. Другое дело, что без опыта в этом деле это будет сделать не просто. И если JS не ваша проф. ориентация, то возможно лучше будет и дальше ковырять old school.

old school JS очень неудобный
JS в любом виде неудобный когда кодовая база большая, TS с включенными стрикт режимами — спасение.

Я не фанат ФП и лямбда-исчисление никогда серьёзно не изучал, но всегда глаз дёргается, когда вижу использование функциональный подхода для исполнения строго заданных последовательностей создания побочных эффектов.

А без Promise/async/await считается? :-) Я заменил fakeFetch на realFetch, чтобы убрать Promise.
3 строки:
Callback HELL!!! Do not open!!!
function realFetch ( url, data, cb ) {
	console.log ( 'data to send:', data, ' url:', url );
	$.ajax ({
		type: "POST", contentType : 'application/json', dataType: "json",
		data: data, url: url,
		success: function ( newData ) {
			cb ( newData );
		}
	})
}

function fetchSeries ( urls, data, cb ) {
	if ( !urls.length ) { cb ( data ); return }
	var urlsCopy = urls.slice ();
	realFetch ( urlsCopy.shift (), data, function ( newData ) { fetchSeries ( urlsCopy, newData, cb ) })
}


fetchSeries ( urls, null, function ( data ) {
	console.log ( "Done, last data received = ", data );
})

Sign up to leave a comment.

Articles

Change theme settings