Полезно ли всегда возвращать обещание

Я использую bluebird для разработки некоторой оболочки API-интерфейса nodejs для службы http. Многие функции в этой оболочке являются асинхронными, поэтому имеет смысл возвращать промисы из этой реализации.

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

Пример:

function parseArray(someArray){
    var result;
    // synchronous implementation
    return Promise.resolve(result);           
}

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

Считается ли это плохой практикой, есть ли причины, по которым мы не должны этого делать?


person Willem D'Haeseleer    schedule 18.03.2014    source источник
comment
Это выглядит бесполезно тяжелым. В случае сомнений вы всегда можете cast вернуть функцию, я не вижу причин не предоставлять непосредственно используемое значение.   -  person Denys Séguret    schedule 18.03.2014
comment
Кстати, разве это основной вопрос, основанный на мнении?   -  person Denys Séguret    schedule 18.03.2014
comment
На самом деле это довольно распространенный анти-шаблон, я думаю, что этот вопрос полезен, так как это анти-шаблон для этого.   -  person Benjamin Gruenbaum    schedule 18.03.2014
comment
Цель обещаний — сделать ваш код проще и понятнее. Если использование промисов заставляет вас добавлять бесполезный повторяющийся код во многие функции, то вы делаете это неправильно.   -  person Denys Séguret    schedule 18.03.2014
comment
Это обманывает пользователя API, заставляя его поверить, что он реализован асинхронно.   -  person RemcoGerlich    schedule 18.03.2014
comment
Я думаю, что задал этот вопрос, чтобы узнать опыт и «мнение» других людей, в первую очередь в качестве аргументации для обсуждения с моим коллегой позже, потому что, очевидно, мы не должны этого делать. Где было бы более уместно задать этот вопрос?   -  person Willem D'Haeseleer    schedule 18.03.2014
comment
@WillemD'haeseleer, насколько я могу судить, это вовсе не мнение. Это чрезвычайно распространенный анти-паттерн, с которым я сталкиваюсь постоянно, постоянно отслеживая в библиотеках промисов и JS-вопросах. Вы явно не должны этого делать - (OTOH, если бы промисы были монадическими, а JS имел бы нотацию do - это могла бы быть другая история с другим средством бокса (не промисами)). Если у вашего коллеги есть опыт работы с Haskell (или другим языком FP), это может иметь смысл для него, но не работает в JS.   -  person Benjamin Gruenbaum    schedule 18.03.2014


Ответы (2)


Нет смысла возвращать промис в синхронных методах.

Обещания обеспечивают абстракцию параллелизма. Когда параллелизм не задействован, например, при предоставлении массива. Возврат промиса ухудшает управление потоком и значительно замедляет его.

Это также передает неправильное сообщение. На самом деле обещание чего-то без причины — довольно распространенный антипаттерн.

Один из случаев, когда он полезен, — это когда метод может быть асинхронным, например: получение чего-либо из кеша или выполнение запроса, если его там нет:

function getData(id){
     if(cache.has(id) return Promise.cast(cache.get(id));
     return AsyncService.fetch(id).tap(cache.put);
}
person Benjamin Gruenbaum    schedule 18.03.2014
comment
значительно медленнее? Это должно быть едва заметно при хорошей реализации обещания. - person Bergi; 18.03.2014
comment
@Bergi, чем выполнять синхронизацию? Обратите внимание, я говорю, что это значительно медленнее, чем выполнение синхронизации, а не обратные вызовы формы :) - person Benjamin Gruenbaum; 18.03.2014
comment
Да, хорошая реализация обещания должна быть в состоянии справиться с этим так же быстро, как синхронное выполнение, плюс только постоянные (и небольшие) накладные расходы. Медленнее, да, но не намного. - person Bergi; 18.03.2014
comment
Здесь сделан (вероятно, сломанный) микротест jsperf.com/return-3-vs-bluebird -promise , что неудивительно, даже если значение не используется и требуются дополнительные накладные расходы на вызов then (и он добавляет свои собственные потери) - это почти в 50 раз быстрее. - person Benjamin Gruenbaum; 18.03.2014
comment
Хорошо, по сравнению с одним добавлением создание объекта обещания происходит намного медленнее. Но предполагая нетривиальную функцию (например, parseArray, как в OP), разница не должна быть такой большой. - person Bergi; 18.03.2014
comment
@Bergi TBH меня больше беспокоят накладные расходы памяти (и время GC, которое это вызывает). Если вы вызываете что-то, генерирующее обещание 10000 раз в цикле, это более 100 МБ дополнительной памяти, которую я должен использовать и должен быть собран даже в самой эффективной реализации обещания. Как вы думаете, я должен уточнить это в ответе или сформулировать немного по-другому? - person Benjamin Gruenbaum; 18.03.2014
comment
Да, может быть. Тем не менее, я по-прежнему считаю, что большинство операций имеют дело с большим количеством данных, где один промис больше или меньше не должен иметь большого значения. Взгляните на этот тест для примера: jsperf.com/return-3- vs-bluebird-promise/3 - person Bergi; 18.03.2014
comment
Интересно. Хотя в вашем случае оба создают обещание. В любом случае, я думаю, что просто оставлю комментарии здесь для чтения на случай, если кому-то будет интересно :) Кстати, хорошая работа над тегом обещания - в последнее время я скрывался, пытаясь помочь, когда мог, и заметил вашу положительную активность! - person Benjamin Gruenbaum; 18.03.2014
comment
@BenjaminGruenbaum В вашем тесте синхронная версия DCEd - добавьте такие огромные комментарии для предотвращения встраивания (и, следовательно, DCE из цикла) - person Esailija; 18.03.2014
comment
@Esailija, не разбивайте мой бенчмарк - я сказал, что у меня сломанный микро-бенчмарк, и я намерен, чтобы он оставался сломанным :( Плюс, встраивание будет происходить и в реальном сценарии, поэтому я не вижу, какой в ​​этом вред . - person Benjamin Gruenbaum; 18.03.2014
comment
@BenjaminGruenbaum, вы не видите вреда в бенчмаркинге воздуха? Wat - person Esailija; 18.03.2014
comment
@Esailija Спустя столько времени я должен был понять, что ты никогда не понимаешь моих шуток -_- Плохо :P - person Benjamin Gruenbaum; 18.03.2014
comment
@BenjaminGruenbaum Когда параллелизм отсутствует, например, при предоставлении массива. Что, если я создаю структуру данных с несколькими массивами, и эта структура данных строится рекурсивно? Операции с массивами (такие как push()) являются синхронными операциями, но имеет ли смысл разрабатывать рекурсивную функцию сборки асинхронно? Я боюсь, что бэкэнд заблокирован в случае, если многие из этих конструкций строятся одновременно из-за множества операций с массивами во время встроенных процессов этих структур данных. - person Vegaaaa; 09.01.2018
comment
@Vegaaaa нет - это еще более очевидный случай избежать асинхронного программирования - если у вас есть работа, связанная с процессором, используйте веб-воркер. - person Benjamin Gruenbaum; 09.01.2018
comment
@BenjaminGruenbaum, так что можно всегда избегать асинхронного программирования в рекурсивном потоке программ в JavaScript? - person Vegaaaa; 10.01.2018
comment
@Vegaaaa нет, иногда это требуется - совет состоит в том, чтобы просто избегать этого, когда это не требуется. - person Benjamin Gruenbaum; 10.01.2018

Если мы можем игнорировать производительность, это плохо только в том случае, если используется пример реализации:

function parseArray(someArray) {
    var result;
    // synchronous implementation
    return Promise.resolve(result);           
}

Эта функция — беспорядок, потому что она может генерировать синхронно, но также возвращает промис. Функция, которая возвращает промис, никогда не должна бросать вызов, иначе вы потеряете большую часть преимуществ промисов, потому что теперь вам придется использовать и try-catch, и .catch().

Правильный способ реализации - "аннотировать" функцию с помощью .method:

var parseArray = Promise.method(function() { 
    var result;
    //Promise.resolve(result) is unnecessary now
    return result;
});

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

person Esailija    schedule 18.03.2014
comment
Обратите внимание, что Promise.method действительно крут и преобразует выброшенные исключения в отклонения, но специфичен для Bluebird. - person Benjamin Gruenbaum; 18.03.2014
comment
Заявление о том, что функция, возвращающая обещание, никогда не должна бросать вызов, не совсем правильно, например. любая функция должна вызывать исключение, если предоставленные аргументы недействительны, это относится ко всем другим асинхронным API, это не должно отличаться от промисов. - person Mariusz Nowak; 19.03.2014
comment
@MariuszNowak любая функция должна вызываться, если предоставленные аргументы недействительны. Я думаю, что «недействительный» - очень суетливая спецификация. Что делать, если функции необходимо выполнить асинхронный вызов для проверки аргументов? Для меня гораздо разумнее бросать async (отклонять), если функция имеет хотя бы 1 асинхронную операцию. - person Willem D'Haeseleer; 20.03.2014
comment
@WillemD'haeseleer недействителен на уровне языка, например. мы ожидаем функцию и получаем null, все, что мешает нам инициализировать асинхронный запрос. Конечно, любой результат ошибки асинхронной операции обязательно должен быть отклонен через промис, но это совсем другое. - person Mariusz Nowak; 20.03.2014
comment
Функции @MariuszNowak, которые возвращают обещания, никогда не должны выбрасывать. Если они это сделают, это может испортить ваш код во многих многих отношениях. Когда у вас есть массив функций или вы сопоставляете список функций. В языке без деструкторов или какого-либо разумного способа детерминированного управления ресурсами выброс из функций, выполняющих асинхронные операции, чрезвычайно опасен. Если вы хотите сообщить об ошибке обещания, вы можете отклонить. В противном случае вам понадобятся два предложения catch. Это также не будет хорошо работать в цепях. - person Benjamin Gruenbaum; 24.03.2014
comment
@BenjaminGruenbaum Речь идет об очевидных ошибках, которые можно обнаружить с помощью простого анализа кода. Точно так же, как вы не пытаетесь/перехватываете каждый вызов синхронизации, точно так же вы не будете пытаться/перехватывать асинхронный вызов, но, конечно, оба типа функций будут генерировать ошибки, если вы используете их явно неправильным образом. Все современные асинхронные API ведут себя таким образом, и обещания, возвращающие API, не должны быть исключением. - person Mariusz Nowak; 24.03.2014
comment
Если вы не попытаетесь поймать промис (что просто означает - не добавляйте к нему .catch(handler или .then(..,handler), вы получите точно такой же эффект в хороших библиотеках промисов - он будет записываться (по умолчанию) в консоль, информируя вас о твоя ошибка. Вы также можете использовать типизированные или основанные на предикатах предложения .catch в Bluebird. Если вы позволите функции возврата промисов бросать вас, вы открываете врата ада (см. аналогию :)?) - person Benjamin Gruenbaum; 25.03.2014
comment
@BenjaminGruenbaum Эта статья хорошо подводит итог joyent.com/developers/node/design/errors См. различие между операционными ошибками и ошибками программиста. Я пытался сказать, что ошибки программиста всегда должны вызывать сбой, независимо от того, с каким типом асинхронной обработки вы работаете, и промисы не должны быть исключением. - person Mariusz Nowak; 29.03.2014