«Лапша» из callback-ов — будьте проще

    По следам недавних топиков, а также постоянных рассказов в стиле «мой стартап не взлетел, потому что его зохавала лапша из callback-ов».

    Как раз недавно я закончил небольшой проект (ссылку не даю, чтобы не заподозрили — кому надо см. профиль), полностью и на всех этапах написанном только на JS, и притом полностью асинхронный. Разумеется, я столкнулся с пресловутой проблемой «лапши». И, вы не поверите, совершенно спокойно решил её без всяких там фреймворков и хитрых приемов.

    Итак, допустим, у нас есть задача: асинхронно выбрать из базы количество книг, потом асинхронно же выбрать из базы нужную пачку книг, потом асинхронно же выбрать из базы метаданные по книгам, а потом свести всё это в один dataset и отрендерить шаблон. Как это обычно выглядит?



    exports.processRequest = function (request, response) {
        db.query('SELECT COUNT(id) FROM books', function (res1) {
            // do something
            db.query('SELECT * FROM books LIMIT ' + Number(limit) + ' OFFSET' + Number(offset), function (res2) {
                // do something 2
                db.query('SELECT * FROM bookData WHERE bookId IN (' + ids.join(', ') + ')', function (res3) {
                      // И вот наконец формируем как-то dataset
                      response.write(render(dataset));
                });
            });
        });
    }
    


    Если накинуть ещё пару-тройку промежуточных шагов, то всё станет совсем плохо.
    Теперь зададим себе простой вопрос: зачем мы написали эту лапшу? Действительно ли нам необходимы здесь три вложенных замыкания?

    Нет, конечно. У нас нет ровно никакой нужды из третьей анонимной функции иметь доступ к замыканиям второй и первой. Перепишем немного код:

    exports.processRequest = function (request, response) {
        var dataset = {};
        
        getBookCount();
    
        function getBookCount () {
            db.query('SELECT COUNT(id) FROM books', onBookCountReady);
        }
    
        function onBookCountReady (res) {
            // ...заполняем dataset
            getBooks();
        }
    
        function getBooks () {
            db.query('SELECT * FROM books LIMIT ' + dataset.limit + ' OFFSET' + dataset.offset, onBooksReady);
        }
    
        function onBooksReady (res) {
            // ... заполняем dataset
            getMetaData();
        }
    
       function getMetaData () {
           db.query('SELECT * FROM bookData WHERE bookId IN (' + dataset.ids.join(', ') + ')', onMetaDataReady);
       }
    
       function onMetaDataReady (res) {
           // ... заполняем dataset
           finish();
       }
    
       function finish () {
          response.write(render(dataset));
       }
    }
    


    Пожалуйста. Код стал полностью линейным, и, что немаловажно, более структурированным; весь program flow у вас перед глазами, логические блоки кода оформлены отдельными функциями. Никаких фреймворков и хитрых синтаксисов. И никаких подводных камней — dataset замкнут в контексте обработки пары request-response, случайно залезть в какие-то разделяемые между request-ами данные не получится.

    Всё немного усложняется, если нужно что-то запараллелить. У меня такой задачи не было, но если бы была (допустим, есть два набора метаданных), то я решал бы её так:

       function getMetaData () {
          var parallelExecutor = new ParallelExecutor({
              meta1: getMetaData1,
              meta2: getMetaData2
          });
    
          function getMetaData1 () {
              db.query('smthng', onMetaData1Ready);
          }
    
          function getMetaData2 () {
              db.query('smthng', onMetaData2Ready);
          }
    
          function onMetaData1Ready (res) {
              // заполняем dataset
              parallelExecutor.ready('meta1');
          }
    
          function onMetaData2Ready (res) {
              // заполняем dataset
              parallelExecutor.ready('meta2');
          }
    
         parallelExecutor.start(onMetaDataReady);
      }
    
      function onMetaDataReady () {
      }
    


    Смысл всё тот же — создать отдельное замыкание для набора функций, который обычно объединяют в «лапшу», и расписать их последовательно.

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

    Комментарии 119

    • НЛО прилетело и опубликовало эту надпись здесь
      • НЛО прилетело и опубликовало эту надпись здесь
          0
          Почему в двух?
          Если, допустим, у меня между загрузкой книг и загрузкой метаданных появляется дополнительный шаг getSomething, то мне нужно изменить ровно один вызов — в конце onBooksReady вызывать не getMetaData(), а getSomething().
          • НЛО прилетело и опубликовало эту надпись здесь
              –1
              И это очень странно. Если встраивание нового вызова не изменяет предыдущий и последующий вызов — значит, ваша цепочка вызовов не последовательная, а параллельная.

              К тому же, ничего искать не надо, всё прямо перед глазами — функции в данном случае выполняют роль просто именованных блоков кода.
        –9
        Статья для тех кто не читает доку по JS
          –3
          Да, тоже выход, сразу вспомнилась книга фаулера про рефакторинг, где он советует использовать как можно больше функций.

          Такой код пройдет в простых ситуациях. В сложных, когда нужно расшарить какие-то переменные между такими функциями, будут проблемы. И тогда нужно создавать целый класс (не в глобальную же область пихать эти переменные), когда хватило бы одной функции в любом другом языке (java, c, php, python и т.д.).

          Всё это конечно зависит от меры безвыходности. Когда одна монополия — ничего не поделаешь, жуй что дают. А когда есть выбор — java, c, php, python, erlang, go и т.д. — выбирай то, что тебе нравится.

          P.S. «мой стартап не взлетел, потому что его зохавала лапша из callback-ов» — когда запуск стартапа для тебя становится текучкой, хочется, чтобы это текучка была удобна в разработке, без костылей.
            0
            > В сложных, когда нужно расшарить какие-то переменные между такими функциями, будут проблемы.

            Какие проблемы, не понял? Вот же dataset шарится между такими функциями

            > И тогда нужно создавать целый класс (не в глобальную же область пихать эти переменные), когда хватило бы одной функции в любом другом языке (java, c, php, python и т.д.)

            Да почему? И здесь хватит ровно одной функции для создания общего замыкания.
            Давайте конкретно, с примерами. Где это в php хватает одной функции, а в JS нужно класс создавать?
              –3
              В общем-то, можно в вашем стиле решить, но нужно кучу переменных передавать, например:

              function first(foo1, foo2, externalCallback) {
              db.query("...", second.bind(foo1, foo2, externalCallback))
              }

              function second(foo1, foo2, externalCallback, res) {
              var someResult = ... // какой-то крутой алгоритм, которому нужен res
              db.query("...", third.bind(someResult, foo1, foo2, externalCallback, res))
              }

              function third(someResult, foo1, foo2, externalCallback, res) {
              // заканчиваем
              db.query("...", externalCallback.bind(someResult, res))
              }


              Как-то так. Это еще без мяса, когда еще куча всяких локальных переменных. И когда вложенность может уйти еще и на четвертый уровень. Как вариант, чтобы не таскать эти переменные можно создать объект и туда запихать все эти «локальные» переменные. Но в любом случае такой код сложнее, чем просто синхронные вызовы SQL кода.
                –3
                И вот код на java:

                Object[] first(foo1, foo2) {
                Res res = db.query("...");
                List someResult = // крутой алгоритм с res

                res = db.query("...");

                // заканчиваем

                Res res2 = db.query("...");

                return new Object[] {someResult, res2};
                }


                Думаю наглядно видно, какой код проще читается и быстрее пишется.
                  +1
                  Конечно — код без асинхронных вызовов проще читается и быстрее пишется. Язык тут как бы не при чем. Юзайте синхронные варианты функций в node.js, и у вас будет точно такой же код на JS.
                    –2
                    Юзайте синхронные варианты функций в node.js, и у вас будет точно такой же код на JS.

                    Ну раз есть синхронные варианты, зачем использовать асинхронные? Или node.js не умеет многопоточность? Ну тогда синхронные — не вариант. А асинхронные — неудобно. Вот и зачем тогда node.js?
                      +3
                      Пользуйтесь Эрлангом, и будет вам счастье))
                        +2
                        Ок, отлично. Задача следующая: вам нужно сделать два запроса к базе в два потока, а потом срендерить страницу. Напишите код на java, который это делает, и мы сравним, насколько он красив и понятен.
                          +1
                          А можно немного кода на c++? :) resume/wait — макросы вроде этих protothreads

                          struct Example {
                            void *exec_ptr;
                            Task<String> *a;
                            Task<String> *b;
                          
                            void run() {
                              resume();
                              a = db_query("query one");
                              b = db_query("query two");
                              wait(a, b);
                              render("template.html", a->result(), b->result());
                            }
                          };


                          или на питоне?

                          @myubertaskframework.task
                          def run():
                              a = db_query("query one")
                              b = db_query("query two")
                              yield wait(a, b)
                              render("template.html", a, b)
                          
                            +1
                            в C# как-то так:

                            async Task MyAsyncExample() {
                              List<Task<String>> tasks = new List<Task<String>>() {
                                db.query("query one");
                                db.query("query two");
                              }
                              String[] results = await TaskEx.WhenAll<String>(tasks);
                              tpl.render("template.html", results);
                            }
                            
                              0

                              Вариант на ноде:


                              Future( ()=> {
                                  var a = db_query( "query one" )
                                  var b = db_query( "query two" )
                                  render( "template.html" , a.wait(), b.wait() )
                              }).detach()
                      +1
                      Какую кучу переменных? Все функции шарят общий кусок памяти — замыкание внешней функции processRequest, и могут совершенно спокойно создавать и использовать в этом замыкании переменные (= поля переменной dataset в моём примере). Если же какой-то группе функции нужно непременно иметь общие разделяемые переменные, скрытые от всего другого кода — эта группа функций просто оборачивается в анонимную функцию и заводит в замыкании нужные переменные.
                        –2
                        Ну да, я про это и говорил, что тут мы почти целый класс в итоге нагородили. А в java для этого хватит просто одной функции (как в примере выше).
                          +1
                          Где, какой класс, о чём вы?
                          Напишите асинхронный код на любом распространенном web-языке так, чтобы это получилось красивее и читабельнее, чем на js, тогда и поговорим.
                            0
                            >Где, какой класс, о чём вы?
                            О том, что не надо пихать асинхронные функции туда где это не нужно.

                            >Напишите асинхронный код на любом распространенном web-языке
                            Зачем? Я просто, лишь указал еще на одну проблему в JS — лишние сложности при работе с БД. И написал вам красивое решение на той же java, как это делается «обычно» и просто.
                              0
                              Какие лишние сложности? Все функции имеют дубликат с постфиксом Sync()
                                0
                                А можно без дубликатов и без функций. Просто написать линейный код. Который будет работать параллельно другому коду, без всяких лишних телодвижений.
                                  0
                                  Можно — node-sync. Но некоторое количество геморроя всё же будет. Как минимум в 1-ой исходной функции нужно будет сделать обёртку.
                              0
                              И если что, я говорю про клиентский JS, когда выхода нет кроме как юзать только его. А на сервере я уже давно сделал свой выбор. И node.js в нём нет, благодаря тому, что на сервер-сайде есть из чего выбрать, есть надежные и стабильные платформы.
                                +1
                                Ок, приведите пример клиентской задачи, которую удобно решать синхронно, но которую разработчики браузера зачем-то сделали асинхронной. И заодно — решение, как бы это следовало сделать по-Вашему.
                      0
                      Смотря каких костылей. Я бы поставил async, например.
                        +1
                        Фреймворки плохи тем, что заставляют вызовы, принимающие не (err, callback), а какие-то другие аргументы, оборачивать в анонимные функции. Это неудобно и создаёт кучу синтаксического мусора.
                          0
                          Это работает в Chrome, Firefox, IE? Говоря про JS я не говорю про node.js, я говорю про браузерный JS. Ибо, он никуда не годится для работы с SQL, когда необходимы сложные взаимодействия с БД.
                            +3
                            Работает Async и в браузере. Чем хуже JS приведенной выше вами Java при работе с SQL? Поясните разницу.
                        +8
                        Согласен про преувеличенную проблему.
                          –4
                          Вот почему лучше иметь нормальную многопоточность, чем мучиться с вырвиглазной лапшой из коллбэков. Кстати, а чего это запросы клеятся строками? Ещё один недорогой способ словить SQL Injection?
                            +1
                            > Вот почему лучше иметь нормальную многопоточность, чем мучиться с вырвиглазной лапшой из коллбэков.

                            Ок, приведите пример нормального многопоточного кода, и мы сравним его с вырвизглазной лапшой.

                            > Кстати, а чего это запросы клеятся строками? Ещё один недорогой способ словить SQL Injection?

                            Это, как бы, просто условный пример кода. Разумеется, там фреймворк над базой данных в исходниках.
                              –2
                              Ну так выше же уже привели пример кода на Java. Вот примерно так:
                              protected void doGet(HttpServletRequest req, HttpServletResponse resp)  {
                                  QueryResult res1 = db.query("SELECT COUNT(id) FROM books");
                                  QueryResult res2 = db.query("SELECT * FROM books LIMIT " + Integer.parseInt(limit) + " OFFSET " + Integer.parseInt(offset));
                                  QueryResult res3 = db.query("SELECT * FROM bookData WHERE bookId IN (" + StringUtils.join(ids, ",") + ")");
                                  dataset.render(resp);
                              }
                              
                                +3
                                И где же здесь асинхронность с многопоточностью?
                                Не хочешь париться с асинхронностью — так и node.js к каждой функции даёт Sync-вариант.

                                Давайте реальный пример, с callback-ами.
                                  –3
                                  А не нужно мне на серверной Java писать асинхронный код. Это только лишний геморрой, который никак не окупается.
                                    +1
                                    Ну это крутоватое обобщение. Когда надо обраратывать больше тысячи-другой одновременных соединений модель «одно соединение — один поток» просто не вписывается ни в какие бюджеты.

                                    С другой стороны пихать в колбэки операции которые а) должны выполняться asap, б) потребляют очень ограниченный ресурс (вроде запросов в БД) не очень понятно зачем.
                                      +1
                                      Когда нужно обрабатывать 1000 соединений, берут эрланг, где не потоки ОС, а что-то вроде green threads, а не ручной CPS, как в node.js. Ну и вообще, если 1000 соединений, то одна машина замучается обрабатывать запросы, потому балансировщик. Наконец, иногда спасает банальный пул потоков, если 1000 соединений — это случайный пик, а не повседневная закономерность.
                                      +1
                                      Ок, аналогичный код на nodejs:

                                      exports.processRequest = function (request, response) {
                                      var res1 = db.querySync(query1),
                                      res2 = db.querySync(query2),
                                      res3 = db.querySync(query3);

                                      response.end(render(dataset));
                                      }

                                      Ничем не хуже. Просто не используйте асинхронные варианты функций.
                                        –1
                                        Тогда всё-таки ответьте на вопрос: если на node.js можно писать синхронный код, зачем тогда нужен асинхронный стиль, если он предполагает лишний геморрой?
                                          0
                                          Затем, что событийная модель выгоднее многопоточной. См. nginx vs apache
                                            0
                                            Чего? А может, ещё глянуть на сравнение sqlite vs oracle?

                                            По поводу «событийной модели». Сейчас это какой-то buzzword, аргументы в её пользу несколько сомнительны.
                                              0
                                              Вам ниже отлично ответили.
                                            0
                                            Любая статья для ознакомления с nodejs начинается с объяснения этого :). В кратце — для того, чтобы не блокировать процесс ожиданием ответа бд или фс или любого другого внешнего ресурса.

                                            Если перенести на клиент — представьте что вы выполняете синхронный ajax request (в jquery .ajax выставьте опцию async: false) Пока запрос сходит на сервер и вернется, ваш браузер подвиснет — вы ничего не сможете нажать, поскроллить и тп. Остановится выполнение js.

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

                                              Ага, потому можно запрос обрабатывать в отдельном потоке. Тогда пусть себе обработка текущего запроса ждёт БД. Другие-то будут себе работать.
                                              Если перенести на клиент — представьте что вы выполняете синхронный ajax request

                                              Представляю, за синхронные ajax-запросы в приличном обществе принято руки отрывать. Вот только это больше проблема JS. Ну ничего, когда лет через 10 не останется браузера, который не поддерживает Web workers, такой проблемы не будет.
                                              Пока наружные утилиты работают ваш js сервер продолжает обслуживать реквесты и раздавать задачи внешним утилитам.

                                              Мой tomcat сидит и выполняет запросы, пока один из входящих запросов долго ждёт БД.
                                                0
                                                Очень хорошо ответ расписан здесь: habrahabr.ru/blogs/nodejs/128772/ с достоинствами и недостатками асинхронной событийной модели. Грубо говоря, при одновременном подключении 10000 пользователей томкат скушает память (при условии блокирующего IO, не знаю, может он неблокирующий, как nginx) посмотрите там графики расхода памяти для nginx и apache на одновременное количество подключений.

                                                Вы говорите про горизонтальное масштабирование, я про вертикальное. С приходом модуля claster, (это типа серверный вебворкер) nodejs еще и горизонтально неплохо масштабируется.
                                                  0
                                                  А если у вас будет много одновременных запросов, ждущих БД, что будет делать томкэт в случае, когда у него потоки в пуле закончатся, как вы думаете?
                                                    0
                                                    Он будет ждать, пока потоки вернутся в пул, заставляя клиентов ждать.
                                                      0
                                                      Он будет заставлять ждать всех клиентов, в том числе и тех, которые всего-то хотят выполнить простенький запрос или ресурсик получить. В асинхронном случае такого не произойдет.
                                                        0
                                                        В асинхронном стиле на БД свалится 1000 одновременных запросов. Тогда вопрос уже к БД — выдержит ли сервер столько одновременных соедиенений. И как его планировщик будет справляться с таким количеством задач. И при 1000 одновременных задач, посчитайте, сколько времени уйдёт на каждую.
                                                          0
                                                          С чего вы взяли что их там свалится 1000? Эти 1000 запросов вполне могут быть обработаны отдельным пулом из 5 потоков, никто же не заставляет выполнять их все сразу. А клиент, запросивший задачу, подождет, долгая задача в конце концов.
                                                            0
                                                            Ну так всё равно клиентам-то ждать. Или БД мифическим образом приостановит тяжёлый запрос и начнёт делать 998 маленьких? И как она заранее определит «тяжесть» запроса? Я понимаю, что есть план запроса, но от объёма данных и селективности индексов тоже многое зависит, может запрос с простым выполняться долго, а может со сложным планом — моментально.
                                                              0
                                                              Дык ждать-то будут только те клиенты, у которых выполняются тяжелые задачи, а остальные ждать не будут. А вот когда у томкэта потоки из пула закончатся, то ждать будут все без исключения. Естественно, что можно это все сделать и по-другому, но это уже отдельная история.
                                                                0
                                                                Хорошая связка nodejs + mongodb с его шардингом — решение. Вот вам еще пример — 300 одновременных скачек фильма. Неблокирующий процесс будет ограничен жирностью канала, блокирующий памятью и количеством потоков в пуле. Пусть 280 подождут, пока первые 20 фильм скачают?
                                                                  0
                                                                  Ну раздача статики это как раз тот пример, когда необходимость использования событийной модели ни у кого не вызывает сомнений. А вот что вы будете с этой моделью делать, если у вас при обслуживании клиентов возникают сложные вычисления? Из-за одного тяжелого запроса все остальные клиенты будут ждать.
                                                                    0
                                                                    Да, и это проблема. Которая перетерта сотни раз :) Cluster уже включен в дистриб nodejs. частично решает проблему. Теперь 1 физический сервер можно загрузить вертикально и горизонтально.
                                                                    0
                                                                    А как раздавать кинцо? Поставить nginx фронтэндом к tomcat'у, пусть раздаёт видео скачивающим. При этом tomcat будет генерить странички, например, форума, где юзера про это кинцо пишут. Вот там никакого выигрыша от асинхронности нет, зато какой геморрой с js. Кстати, такая шустрая штука, как nginx, писалась на C/C++. Там уже не заморачиваются всякими языковыми средствами вроде замыканий и тупо ювелирными усилиями заставляют всё это работать асинхронно.
                                                                      0
                                                                      То есть вы не понимаете бонусов неболкирующего сервера, но для раздачи статики предлагаете на фронтенд неблок. сервер? :)
                                                                        0
                                                                        Я понимаю бонусы. Мне непонятно, где эти бонусы проявятся в бизнес-логике.
                                                                          0
                                                                          Бизнес логика здесь непричем. Правильный инструмент для правильных задач, вот и все :) + валидация форм на клиенте и сервере одним кодом + переучить верстака со средним js и посадить на серверсайд в случае чего. Вся высшая математика и рассуждения идут лесом, если это некоммерческая инициатива :)
                                                                            0
                                                                            В статье явно было написано о бизнес-логике, судя по коду. А валидацию одним кодом я сделал по-другому — написал декомпилятор java-байткода в javascript, вся валидация делается на java на клиенте и на сервере.
                                                                0
                                                                В задачи веб сервера не входит балансировка загрузки бд, бд имеет встроенные механизмы. Более того, пока бд думает, nodejs может также нагрузить файловую систему и остальное. И не обосрется на десятках тысяч одновременных соединений ( простейший пример — comet чат), как это сделает блокирующий сервер.
                                                                  0
                                                                  Боюсь, проблема comet-чата вовсе не в потоках. Я бы сказал, в чём, но ведь карму же сольют.
                                                +1
                                                Кстати, здесь нет асинхронности, потому что задача не была поставлена — «приведите пример нормального многопоточного кода». Приведённый код вполне себе многопоточный. Действительно, сервлет выполняется в веб-контейнере, который многопоточный и каждый запрос обрабатывает в своём потоке.
                                                  0
                                                  Хорошо, изменим задачу. Напишите код на java, который АСИНХРОННО делает три запроса к базе — два параллельно + один последовательно.
                                                    0
                                                    В синхронном виде это будет страшный код, ибо в java нет анонимных функций, а есть анонимные классы. Но на java такой ерундой никто не занимается. Если нужно параллельно делать два запроса — делаем их в разных потоках. Но в том же вебе такое очень редко нужно. Можно пример ситуации, когда без двух параллельных запросов к БД ну никак?

                                                    В многопоточном коде как-то так:
                                                    class SecondQuery extends Thread {
                                                        public QueryResult result;
                                                        @Override public void run() {
                                                            result = db.query("second query text");
                                                        }
                                                    }
                                                    SecondQuery secondQuery = new SecondQuery();
                                                    secondQuery.start();
                                                    QueryResult firstResult = qb.query("first query text");
                                                    secondQuery.join();
                                                    QueryResult thirdResult = qb.query("third query text");
                                                    


                                                    Сейчас сторонники js мне скажут «ага, длинно и запутанно». Но вот что я скажу. В 99% случаев это не надо, будет короткий и понятный код. А вот в node.js требуется во всех 100% случаев писать страшноватый код, в котором вся экономия памяти от использования событийной модели идёт лесом из-за накладных расходов, связанных с необходимостью поддерживать весь лес замыканий.
                                                      0
                                                      > Можно пример ситуации, когда без двух параллельных запросов к БД ну никак?

                                                      Ну, если общее время ответа вас не заботит — то, конечно, нет таких ситуаций.

                                                      > А вот в node.js требуется во всех 100% случаев писать страшноватый код, в котором вся экономия памяти от использования событийной модели идёт лесом из-за накладных расходов, связанных с необходимостью поддерживать весь лес замыканий.

                                                      Экономия не в памяти, а в неблокирующем доступе к ресурсу.
                                                        0
                                                        Ну, если общее время ответа вас не заботит — то, конечно, нет таких ситуаций.

                                                        Ага, есть большая разница, 10 или 20 миллисекунд? Лично мне пофиг. Редко бывают ситуации, когда разница между 10 и 20 секундами. Это где-нибудь в энтерпрайзе, когда отчёты собираются. Там бывает и по 20 минут делается.
                                                        Экономия не в памяти, а в неблокирующем доступе к ресурсу.

                                                        Не понимаю. Где чего экономим?
                                                          0
                                                          > Это где-нибудь в энтерпрайзе, когда отчёты собираются. Там бывает и по 20 минут делается.

                                                          Действительно, кого он интересует, этот энтерпрайз.

                                                          > Не понимаю. Где чего экономим?

                                                          Слушайте, тут уже не менее 3-4 раз объяснили, где и что экономим.
                                                          Сервисы, обслуживающие реально очень большое число соединений, — nginx, lighttpd — построены на асинхронных событийных моделях. Если это Вас ни в чем не убеждает, то и спорить дальше как бы бесполезно.
                                                  +1
                                                  Вы мешаете всё в кучу. Мнгопоточность и асинхронность разные вещи. В node есть второе но нет первого, в servet'ах есть первое но нет второго.

                                                  Кроме того, асинхронность возможна не только на callback'ах. «реальный пример» может быть и без них.
                                                    0
                                                    > Вы мешаете всё в кучу. Мнгопоточность и асинхронность разные вещи.
                                                    Вообще-то, первым о многопоточности заговорил не я.
                                                      0
                                                      И где же здесь асинхронность с многопоточностью?
                                                        0
                                                        Вот почему лучше иметь нормальную многопоточность, чем мучиться с вырвиглазной лапшой из коллбэков.
                                                    0

                                                    Далеко не к каждой.

                                              0
                                              Нормальный читаемый код. Посмотрите кстати в сторону паттернов программирования js.
                                                +1
                                                А чем плох Sync?
                                                  –1
                                                  Ничем. Он просто не нужен в данной ситуации, вот и всё. Нативный код понятнее и красивее.
                                                    +1
                                                    Вы не путаете Sync с чем-либо ещё? Вот решение вашей задачи:
                                                    exports.processRequest = Sync( function (request, response)
                                                    {
                                                        var res1 = db.query('SELECT COUNT(id) FROM books' );
                                                        // do something
                                                        var res2 = db.query('SELECT * FROM books LIMIT ' + Number(limit) + ' OFFSET' + Number(offset) );
                                                        // do something 2
                                                        var res3 = db.query('SELECT * FROM bookData WHERE bookId IN (' + ids.join(', ') + ')' );
                                                        // И вот наконец формируем как-то dataset
                                                        response.write(render(dataset));
                                                    } );

                                                    Никакой лапши, никаких каллбеков, никаких доп.функций. Просто и удобно.
                                                      0
                                                      По слухам, Sync пока нестабилен. Буду рад, если опровергнете.
                                                        0
                                                        Увы не обрадую, действительно не стабилен. Нужен как минимум forever.
                                                          0
                                                          Пардон, нестабилен*, конечно. Ещё хотелось бы добавить, что нестабилен не Sync, а Fibers (именно он отвечает за волокна). Но в целом задумка шикарная. Месяц аптайма мой почти не посещаемый «бложик» выдержал без проблем (мог бы и больше, но нужно было его обновить).
                                                          0
                                                          Что-то у вас не то с этим решением.
                                                            0
                                                            А что с ним не так? Sync использует волокна (node-fibers). Функцию query следует слегка изменить (убрать калбеки и вызов блок.функции реализовать через .sync. Это достаточно сложная тема, я сам не до конца понимаю принципы работы волокон, но в результате их использования мы можем писать псевдо-синхронный код асинхронно. При вызове блокирующей функции волокно блокируется предоставляя процессорное время «всем желающим». При первой возможности волокно продолжает своё выполнение. На эту тему на хабре была отличная статья от создателя node-sync.

                                                            Насколько я понимаю что-то подобное используется в python-е и erlang-е. Сам с этими языками, увы, не знаком.
                                                      +2
                                                      Уже оставил комментарий по этому поводу в предыдущем посте про «лапшу».

                                                      Учитывая, какое количество js-программистов не знает о существовании этого способа, отдельный топик вполне уместен.
                                                      • НЛО прилетело и опубликовало эту надпись здесь
                                                        0
                                                        Ну так выше же уже привели пример кода на Java. Вот примерно так:
                                                        protected void doGet(HttpServletRequest req, HttpServletResponse resp)  {
                                                            QueryResult res1 = db.query("SELECT COUNT(id) FROM books");
                                                            QueryResult res2 = db.query("SELECT * FROM books LIMIT " + Integer.parseInt(limit) + " OFFSET " + Integer.parseInt(offset));
                                                            QueryResult res3 = db.query("SELECT * FROM bookData WHERE bookId IN (" + StringUtils.join(ids, ",") + ")");
                                                            dataset.render(resp);
                                                        }
                                                        
                                                          0
                                                          Ой, не туда запостил коммент
                                                          +2
                                                          В своём node.js проекте использую такой же принцип и удивлялся каждой новой статье в стиле «пишем async как sync».

                                                          Спасибо автору, статья помогла более лучше сформулировать всё это в голове.
                                                            +3
                                                            1. Проблема преувеличена, согласен.
                                                            2. Превеликое множество flow-control библиотек. Async в свое время очень помог, а сейчас появилась еще более красивая вещь — Step

                                                            Вот что Step c вашим кодом делает:

                                                            exports.processRequest = function (request, response) {
                                                                Step(
                                                                    function getBookCount(){
                                                                        db.query('SELECT COUNT(id) FROM books', this);
                                                                    },
                                                                    function getBooks(err, count){
                                                                         if(err) throw err;
                                                                         db.query('SELECT * FROM bookData WHERE bookId IN (' + ids.join(', ') + ')', this);
                                                                    },
                                                                    function getMetaData(err, ids){
                                                                         if(err) throw err;
                                                                         db.query('SELECT * FROM bookData WHERE bookId IN (' + ids.join(', ') + ')', this);
                                                                    },
                                                                    function result(err, meta){
                                                                          return response(err, meta);
                                                                    }
                                                                );
                                                            


                                                            параллельный запуск еще красивее:
                                                            Step(
                                                                // Loads two files in parallel
                                                                function loadStuff() {
                                                                    fs.readFile(__filename, this.parallel());
                                                                    fs.readFile("/etc/passwd", this.parallel());
                                                                },
                                                                // Show the result when done
                                                                  function showStuff(err, code, users) {
                                                                      if (err) throw err;
                                                                      console.log(code);
                                                                      console.log(users);
                                                                }
                                                            )
                                                            


                                                            Также есть инструмент для параллельного запуска неизвестного заранее количества асинхронных методов и автоматический перехват ошибок степом внутри функций, которые мы можно отдать в финальный коллбек. Но самое лучшее, что происходит при использовании степа — это то, что эта библиотека окончательно формирует понятие о контексте в JS. Ну, при условии, что разработчик разобрался, как она работает :)
                                                              0
                                                              Ссылочки по теме: Async, Step
                                                                0
                                                                Со всеми этими фремворками одна беда — они предписывают всем callback-ам получать на вход пару err, res
                                                                Что неудобно. Да и красота довольно относительная выходит.
                                                                  0
                                                                  Ну, это не беда. Раньше в node вместо обычных колбеков использовальсь промисы. Когда переходили на коллбеки, сделали convention — один коллбек, передавать два параметра — ошибку и респонс. Все библиотеки работают по этому принципу, но если вы привыкли иметь по индивидуальному коллбеку на ошибку и на респонс — пишется обертка в 5 строчек и вы пишете в своем привычном стиле:

                                                                  Либо посмотрите модуль Do — Все высокоуровневые чейны, параллелы и индивидуальные коллбеки для ошибок и ответов.

                                                                  Вы можете писать в любом удобном для вас стиле, оборачивать все, как вам удобно.
                                                                    0
                                                                    Разработчики библиотек — молодцы, что поддерживают этот стандарт.
                                                                    Я в своём коде этого делать не хочу, а кое-где и не могу.
                                                                +4
                                                                Может просто javascript не очень подходит для асинхронного стиля?
                                                                  0
                                                                  Ох ты ж. А кто подходит?
                                                                    +2
                                                                    Честно, не знаю. Но когда возникают такие вопросы/проблемы/холивары, на ум приходит только одно — а тот ли инструмент используется?
                                                                    На сегодня я пользовался только twisted и знаю, что благодаря питоновским генераторам можно писать асинхронный код в привычном последовательном стиле. Ещё есть akka, erlang и уверен, что куча других фреймворков для асинхронного программирования, но их не юзал.
                                                                    Ничего не имею конкретного против асинхронной модели node.js, но очень похоже, что javascript не лучший кандидат для асинхронного программирования.
                                                                      –2
                                                                      ОК, перепишите пример на twisted — посмотрим, насколько стало красивее и удобнее. Установка та же — у вас асинхронный веб-сервер, нужно сделать три запроса к базе и выплюнуть ответ в response.
                                                                        +5
                                                                        Конечно, уже пошёл писать :) Приведу лишь пример с одного блога:
                                                                        @defer.inlineCallbacks
                                                                        def someFunction():
                                                                            a = 1
                                                                            b = yield deferredReturningFunction(a)
                                                                            c = yield anotherDeferredReturningFunction(a, b)
                                                                            defer.returnValue( c )
                                                                        

                                                                        Отличие от синхронного стиля в двух вещах: @defer.inlineCallbacks аннотация и yield перед вызовом асинхронного метода.
                                                                          –2
                                                                          Пока я вижу только накладные расходы и никаких преимуществ.
                                                                            +2
                                                                            А в js как бы нет расходов на поддержку замыканий, да ещё и на GC, которому всё это замкнутое мракобесие потом из памяти выковыривать?
                                                                              –1
                                                                              Я про синтаксис.
                                                                                +5
                                                                                Вот этот ужас:
                                                                                exports.processRequest = function (request, response) {
                                                                                    var dataset = {};
                                                                                    
                                                                                    getBookCount();
                                                                                
                                                                                    function getBookCount () {
                                                                                        db.query('SELECT COUNT(id) FROM books', onBookCountReady);
                                                                                    }
                                                                                
                                                                                    function onBookCountReady (res) {
                                                                                        // ...заполняем dataset
                                                                                        getBooks();
                                                                                    }
                                                                                    // поскипано
                                                                                }
                                                                                

                                                                                содержит меньше накладных расходов, чем вот эта няшка?
                                                                                @defer.inlineCallbacks
                                                                                def someFunction():
                                                                                    a = 1
                                                                                    b = yield deferredReturningFunction(a)
                                                                                    c = yield anotherDeferredReturningFunction(a, b)
                                                                                    defer.returnValue( c )
                                                                                

                                                                                O_o
                                                                                Что-то явно в этом мире от меня скрыто.
                                                                                  –1
                                                                                  Вы в няшке раскройте содержание функций + добавьте обвязку request-response.
                                                                                    +2
                                                                                    @defer.inlineCallbacks
                                                                                    def processRequest(request, response):
                                                                                        res1 = yield query('SELECT COUNT(id) FROM books', onBookCountReady);
                                                                                        res2 = yield query('SELECT COUNT(id) FROM books');
                                                                                        response.write(render(dataset))
                                                                                    
                                                                              +2
                                                                              Для вас возможно, но мне последовательная запись привычнее «лапши» или объекта с N методов.
                                                                              Я не говорю что python идеальный претендент для асинхронного языка, там своих костылей хватает, но пока смело могу объявить 1:0 в пользу twisted перед node.js в номинации «асинхронный стиль» (как минимум в моём личном рейтинге).
                                                                              Асинхронно можно писать на многих языках, но на одних код будет простым, красивым и понятным, а на других придётся изобретать свои стили, чтобы хоть как-то оформить получившуюся кучу. Возможно такого языка ещё нет, возможно мы просто про него не слышали.
                                                                                –2
                                                                                Допишите свой пример (добавьте условные тела функций и обвязку response-request), и мы сравним.
                                                                                  +2
                                                                                  Вот блин, умеете уговаривать. Я на twisted не писал HTTP аппликухи и с mysql не работал, но приблизительно это будет выглядить так:
                                                                                  @defer.inlineCallbacks
                                                                                  def processRequest(request):
                                                                                    dataset = Dataset()
                                                                                    count = yield db.query('SELECT COUNT(id) FROM books')
                                                                                    // заполняем dataset
                                                                                    books = yield db.query('SELECT * FROM books LIMIT ' + dataset.limit + ' OFFSET ' + dataset.offset)
                                                                                    // заполняем dataset
                                                                                    meta = yield db.query('SELECT * FROM bookData WHERE bookId IN (' + dataset.ids.join(', ') + ')');
                                                                                    // ... заполняем dataset
                                                                                    defer.returnValue(Render(dataset));
                                                                                  

                                                                                  Насчёт response не уверен, но должно быть так, достаточно просто вернуть объект типа Response. В крайнем случае будет выглядить как у вас: второй аргумет и вызов .write()
                                                                                    –2
                                                                                    Тэээк. И как теперь запараллелить пару вызовов?
                                                                                      +1
                                                                                      Кстати, а как это (запараллелить) можно сделать в JS? Можно примерчик?
                                                                                        +1
                                                                                        Ну началось. Я вам показал ваш пример на twisted/python. Я не хочу начинать холивар node.js vs twisted. Я хочу заставить вас задуматься, а подходит ли javascript для асинхронного программирования, хочу вызвать желание посмотреть на другие инструменты. Может какой-нибудь из них вам покажется удобнее и не придётся изобретать «комфортный стиль кода», а просто брать и писать.
                                                                                +2
                                                                                О да, если погуглить на тему ruby coroutines и lua coroutines, то можно обнаружить, что там тоже есть легковесные потоки с кооперативной многозадачностью. А js, как всегда, впереди планеты всей. Кстати, как я понимаю, в ruby и python даже поддержки специальной не нужно было — просто библиотечка, использующая штатные механизмы языка.
                                                                                  –1
                                                                                  А есть погуглить, то можно как бы узнать про существование Rhino и Ringo.
                                                                                    0
                                                                                    Ну про первый я, как явист, давно знаю. А второй тут при чём? Они добавляют поддержку fibers в javascript?
                                                                              +1
                                                                              erlang — это не фреймворк, это язык
                                                                                +1
                                                                                Да, знаю, сорри, глупое предложение получилось :) Имелось ввиду «инструментов»
                                                                          +1
                                                                          Помоему тут автор просто переделал анонимные коллбеки в обычные (именованные) функции. Код стал чуть просторнее и чуть более редактируемым, но выяснить что после чего выполняется стало сложнее, имхо.

                                                                          Помоему для таких задач можно использовать разного рода Lazy вычисления (типа Deferred из jQuery). Код при этом будет выглядить намного приятнее.
                                                                            0
                                                                            Первые три последовательных запуска намного читабельнее и структурированнее выглядят, чем вторые плоские куча функций.
                                                                              0
                                                                                0
                                                                                а что такое «ParallelExecutor»?

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

                                                                                Самое читаемое