Javascript-форум (https://javascript.ru/forum/)
-   Оффтопик (https://javascript.ru/forum/offtopic/)
-   -   Как вы пишете на сервере (node.js) (https://javascript.ru/forum/offtopic/47170-kak-vy-pishete-na-servere-node-js.html)

dmitry111 12.05.2014 12:31

Как вы пишете на сервере (node.js)
 
Много информации по написанию на node.js. Но чем больше ее изучаешь, тем понимаешь, что строгого подхода к написанию кода в node.js нет.

В этой теме хотелось бы понять при каких обстоятельствах использовать функции обратного вызова и события в node.js


Когда-то можно и не прерывать процесс. Запросили данные, подождали, получили.

Когда-то нужно с вызовом передать функцию обратного вызова (callback). И на другом конце обернуть ее process.nextTick()

Наверное кто-то весь код "склеивает" событиями eventEmitter.


Поделитесь опытом в каких случаях какие подходы задействуете.


Спасибо!

kobezzza 12.05.2014 13:00

Главные правила ноды (из личного опыта, примерно ~ 2 года работы с нодой):

1) Не бросай исключений в асинхронных операциях, а передавай их как параметр callback;

2) Используй паттерны / фреймворки для работы с асинхронностью. Выбор фреймворка и паттерна зависит от предпочтений, я использую Async.

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

Всё остальное уже не так важно, т.е. пиши как нравиться.

cyber 12.05.2014 13:48

Цитата:

Сообщение от kobezzza
3) Нельзя по долгу занимать поток в серверном приложении, т.е. если нужно что-то большое посчитать, то либо используй setImmediate и дроби задачу, либо выноси задачу из Ноды на более предпочтительный инструмент (имхо, более правильное решение).

Все руки не доходят проверить, если написать модуль для ноды на c++, и в нем провести расчеты, то нода блокироваться не будет, а задача будет выполненна асинхронно ?

kobezzza 12.05.2014 13:54

Цитата:

Все руки не доходят проверить, если написать модуль для ноды на c++, и в нем провести расчеты, то нода блокироваться не будет, а задача будет выполненна асинхронно ?
Подозреваю, что это зависит от того, как написано дополнение, ведь для интеграции С/С++ кода как модулей ноды там есть своё АПИ, которое можно юзать и думаю, что есть возможность "фризить" поток выполнения ноды, если это нужно.

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

cyber 12.05.2014 13:58

kobezzza, а можешь дать пример кода, если не тяжело .

kobezzza 12.05.2014 14:05

У тебя есть:

1) Кластер серверов на ноде, который принимает и агрегирует входящие запросы от клиентов;

2) Кластер серверов на Java/C++/Erlang и т.д., которые делают какие то жёсткие вычисления в 100500 потоков и т.д.

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

Собственно по ссылке на википедии дан исчерпывающий ответ:

Цитата:

Данные в REST должны передаваться в виде небольшого количества стандартных форматов (например HTML, XML, JSON). Сетевой протокол (как и HTTP) должен поддерживать кэширование, не должен зависеть от сетевого слоя, не должен сохранять информацию о состоянии между парами «запрос-ответ». Утверждается, что такой подход обеспечивает масштабируемость системы и позволяет ей эволюционировать с новыми требованиями.

Антиподом REST является подход, основанный на вызове удаленных процедур (Remote Procedure Call — RPC). Подход RPC позволяет использовать небольшое количество сетевых ресурсов с большим количеством методов и сложным протоколом. При подходе REST количество методов и сложность протокола строго ограничены, из-за чего количество отдельных ресурсов может быть большим.

cyber 12.05.2014 14:11

Цитата:

Сообщение от kobezzza
Собственно по ссылке на википедии дан исчерпывающий ответ:

Без твоего добавления по написаному , не совсем понятно как это работает .
спасибо.

melky 12.05.2014 19:04

Цитата:

Сообщение от dmitry111
Много информации по написанию на node.js. Но чем больше ее изучаешь, тем понимаешь, что строгого подхода к написанию кода в node.js нет.

его вообще нет.

кстати, что ты имеешь в виду под "node.js" ? веб-сервер? так это ж только один компонент node.js

наверное ты имел в виду express\connect :)

я тоже долго допирал до того, как можно организовать код, чтобы вручную не прописывать route'инги, ибо это ну очень меня выматывает.

решение оказалось простым - это было Ruby on Rails SailsJS. сначала показалось, что это закос под Yii, но потом меня одёрнуло, и я вспомнил, что Yii - это тоже закос под ROR

Цитата:

Сообщение от dmitry111
В этой теме хотелось бы понять при каких обстоятельствах использовать функции обратного вызова и события в node.js

имхо, ответ дан на главной странице Node.js :


я хочу сказать - ответ "везде, где есть ввод\вывод". но это я так думаю :)

Цитата:

Сообщение от kobezzza
1) Не бросай исключений в асинхронных операциях, а передавай их как параметр callback;

причём как первый параметр callback
kobezzza, а почему стали делать именно так ?

Цитата:

Сообщение от kobezzza
2) Используй паттерны / фреймворки для работы с асинхронностью. Выбор фреймворка и паттерна зависит от предпочтений, я использую Async.

скоро будем использовать co и yield :D

kobezzza 12.05.2014 21:17

Цитата:

а почему стали делать именно так ?
Потому что это удобнее, чем плясать с доменами и "нативно" с точки зрения самой ноды.

Или если рассматривать yield подход, то использование try-catch тоже не шибко нравиться, т.к. синтаксис конструкции слишком громоздкий и цена за её использование высока.

Может я что-то не понимаю в этой жизни, но на мой взгяд это супер очевидно и удобно :)
async.map(['file1', 'file2', 'file3'], fs.stat, function (err, results) {
    ...
});

async.parallel(
    [
        function () { ... },
        function () { ... }
    ], 

    function (err, results) {
        ...
    }
);


Цитата:

скоро будем использовать co и yield
Я пока не вижу очевидных плюсов подхода с yield, как замены async, так что с переходом повременю.

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

Octane 12.05.2014 21:25

Цитата:

Сообщение от melky
Цитата:

Сообщение от kobezzza
1) Не бросай исключений в асинхронных операциях, а передавай их как параметр callback;

причём как первый параметр callback
kobezzza, а почему стали делать именно так ?

Это даже не рекомендация, а обязательное правило, потому что catch может поймать только исключение в своем стеке.
try {
    setImmediate(function () {
        throw new Error('test error');
    });
} catch (error) {
    console.log(error); //не поймает test error
}

kobezzza 12.05.2014 21:31

Цитата:

Это даже не рекомендация, а обязательное правило, потому что catch может поймать только исключение в своем стеке.
В node есть модуль domain, который от части решает эту проблему. Также пляски с генераторами могут позволить юзать конструкцию try-catch, но ... это всё изврат, имхо :)

dmitry111 13.05.2014 01:10

Цитата:

Сообщение от kobezzza
2) Используй паттерны / фреймворки для работы с асинхронностью. Выбор фреймворка и паттерна зависит от предпочтений, я использую Async.

вот по поводу Async - как с ним работать?

Я понимаю для чего эти методы, но я не знаю где их применить
Например waterfall:

async.waterfall([
    function(callback){
        callback(null, 'one', 'two');
    },
    function(arg1, arg2, callback){
      // arg1 now equals 'one' and arg2 now equals 'two'
        callback(null, 'three');
    },
    function(arg1, callback){
        // arg1 now equals 'three'
        callback(null, 'done');
    }
], function (err, result) {
   // result now equals 'done'    
});


обходит несколько функций, результат которых идет к следующей и так до конца. Но на деле то, как с этим работать?


У меня много моментов в коде, когда метод одного модуля задействует метод другого модуля.

Вот пример моего кода:

var config = require('../config');

var maxPlayers = config.get('game:maxPlayers');

// проверяет наличие свободные мест
// true: отсутствие мест
exports.check = function (users, cb) {
   var result

  // тут какие-то расчеты

  process.nextTick(function () {
    cb(result);
  });
};



var waiting = require('../lib/waiting');

var allUsers = 10;

waiting.check(allUsers, function (waiting) {
  if (waiting) { 
  } else {
  }
});


Передалось, расчеты сделаны, со следующим тиком callback вернет результат.

Где мне тут использовать этот async?

Также если есть примеры кода (или проект) с использованием async, поделись! А то статей про async много, а в рабочем исполнении не находил. По статьям невозможно понять как это все вместе работает ))

dmitry111 13.05.2014 01:19

Цитата:

Сообщение от melky
кстати, что ты имеешь в виду под "node.js" ? веб-сервер? так это ж только один компонент node.js

наверное ты имел в виду express\connect


имею в виду процесс взаимодействия модулей между собой. Не обязательно express\connect



Цитата:

Сообщение от melky
имхо, ответ дан на главной странице Node.js

я хочу сказать - ответ "везде, где есть ввод\вывод". но это я так думаю

вот я везде callback расставил, но это делает код менее понятным, ИМХО

Octane 13.05.2014 01:40

Цитата:

Сообщение от dmitry111
var config = require('../config');

var maxPlayers = config.get('game:maxPlayers');

// проверяет наличие свободные мест
// true: отсутствие мест
exports.check = function (users, cb) {
   var result

  // тут какие-то расчеты

  process.nextTick(function () {
    cb(result);
  });
};

А зачем колбек просто так асинхронным делать? Вместе с расчетами логично асинхронно запускать.
Так или иначе, скоро в Nodejs будут доступны встроенные ES6 Promises, думаю, это повлияет на API большинства новых модулей, а может быть и самого Nodejs, я бы уже начал писать асинхронные скрипты в таком стиле:
// проверяет наличие свободные мест
exports.check = function (users) {
    return new Promise(function (resolve, reject) {
        setImmediate(function () {
            try {                
                var value;
                // тут какие-то расчеты
                resolve(value);
            } catch (reason) {
                reject(reason);
            }
        });
    });
};

var waiting = require('../lib/waiting');

var allUsers = 10;

waiting.check(allUsers).then(function (value) {
    if (value) {
        //есть свободные места
    } else {
        //нет свободных мест
    }
}, function (reason) {
    //произошла ошибка
});

dmitry111 13.05.2014 02:12

Цитата:

Сообщение от Octane
А зачем колбек просто так асинхронным делать? Вместе с расчетами логично асинхронно запускать.

чтобы была пауза в работе.
До вызова callback происходят некоторые расчеты, далее пауза и вызов callback уже в порядке следующего тика.

melky 13.05.2014 08:58

Цитата:

Сообщение от dmitry111
обходит несколько функций, результат которых идет к следующей и так до конца. Но на деле то, как с этим работать?

везде, где есть асинхронные шаги в логике :) на практике чаще всего требовался parallel

зацени этот говнокод. рылся по истории git'а, чтобы специально показать тебе пример:

Задача: добавить категорию (родительскую\дочернюю - зависит от параметра запроса)

async.waterfall([

                    // тут получаем родительскую категорию, если можно
                    function (done) {
                        if (fields.id == -1) {
                            done(null, null);
                        } else {
                            models.ProductCategoryModel.findById(fields.id, function (err, parentCategory) { done(err, parentCategory); });
                        }
                    },

                    // тут определяем дочернюю категорию 
                    function (parentCategory, done) {
                        var newCategory;
                        if (parentCategory) {
                            newCategory = new models.ProductSubCategoryModel();
                        } else {
                            newCategory = new models.ProductCategoryModel();
                        }
                        newCategory.title = fields.new_category_name;
                        if (parentCategory) {
                            done(null, parentCategory, newCategory);
                        } else {
                            newCategory.save(function (err, newCategory) { done(err, parentCategory, newCategory); });
                        }
                    },

                    // связываем родительскую и дочернюю категории
                    function (parentCategory, childCategory, done) {
                        if (parentCategory) {
                            parentCategory.children.push(childCategory);
                            parentCategory.save(done);
                        } else {
                            done(null, null);
                        }
                    }

                ], function (err, parentCateg) {
                    if (err) return next(err);
                    allIsCompleted(err);
                });


Цитата:

Сообщение от dmitry111
вот я везде callback расставил, но это делает код менее понятным, ИМХО

вот для ухода от callback-hell используют async\Q\другое

kobezzza 13.05.2014 09:30

У меня самые частые - это waterfall и parallel

Пример с waterfall:
// Получение данных пользователя
async.waterfall([
    // Получаем данные пользователя по сессии
    function (cb) {
    },

    // Получаем данные пользователя по запросу
    function (sessionUser, cb) {
    },

    // Делаем проверку доступа и если всё ок, 
    // то отдаём данные
    function (requestUser, sessionUser, cb) {
    },
], callback);


Для задач дробления через setImmediate или nextTick прекрасно подходят циклы async:

var count = 0;

async.whilst(
    function () { return count < 5; },
    
    function (callback) {
        count++;
        
        setImmediate(function () {
            ...
            callback();
        });
    },
    
    function (err) {
        ...
    }
);


Цитата:

Так или иначе, скоро в Nodejs будут доступны встроенные ES6 Promises, думаю, это повлияет на API большинства новых модулей, а может быть и самого Nodejs,
На саму ноду не повлияет никак. Дело в том, что в первых версиях ноды были именно промисы, но от них отказались, т.к. они были чрезвычайно не эффективны для задач ноды.

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

Так что какой-то революции нативные промисы не сделают, но всё же хорошо, что их добавят :)

melky 13.05.2014 09:41

Цитата:

Сообщение от kobezzza
Так что какой-то революции нативные промисы не сделают, но всё же хорошо, что их добавят

что такое "thunk" ?
раньше такого термина не встречал, а теперь он появился, вместе с появлением оператора yield

используется в этой либе: https://github.com/visionmedia/co

я так понял, с yield и thunk асинхронный код пишется, как синхронный. и он без callback'ов

UPD. вру :) thunk - частично исполняемая функция, которая принимает один аргумент - callback.
примерчики :
// read - это thunk
function read(callback) {
  fs.readFile('myfile.md', 'utf8', callback);
}
// readFile - генератор thunk'ов ?
function readFile(filename) {
  return function(callback) {
    fs.readFile(filename, 'utf8', callback);
  };
}

kobezzza 13.05.2014 09:50

Цитата:

что такое "thunk" ?
Тоже хз :) Я думаю, что это реализация либы, а не какая то нативная конструкция.

Цитата:

я так понял, с yield и thunk асинхронный код пишется, как синхронный. и он без callback'ов
Угу, сахарок :) Но, как я уже говорил, что я пока не вижу киллер фич :) И меня смущает, что в реализации на генераторах юзается концепция: sleep-check-sleep-check... и т.д. не будет ли это дополнительной нагрузкой на приложения на сервере при повсеместном использовании? ... Хотя, возможно внутри Co используется другой подход, т.к. в примере вижу, что асинхронная функция предварительно врапится, т.е. теперь вместо колбеков мы получаем другую боль в заднице - следить за проксированием асинхронных функций :)

Да и отлов ошибок через try-catch смущает, т.к. поведение JIT для кода внутри таких конструкций очень осторожно, а иногда и просто не применяется.

***

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

co(function *(){
  var a = yield get('http://google.com');
  var b = yield get('http://yahoo.com');
  var c = yield get('http://cloudup.com');
  console.log(a[0].statusCode);
  console.log(b[0].statusCode);
  console.log(c[0].statusCode);
})()


VS

async.map(['http://google.com', 'http://yahoo.com', 'http://cloudup.com'], get, (err, results) => {
    ...
});


Ну хз, мне второй вариант более по душе :)

melky 13.05.2014 10:17

Цитата:

Сообщение от kobezzza
т.к. это просто наглядней, имхо

да, я сейчас хотел написать функцию факториала на co.. в общем, мне нужна подготовка)

Octane 13.05.2014 19:16

Цитата:

Сообщение от melky
да, я сейчас хотел написать функцию факториала на co.. в общем, мне нужна подготовка)

Все эти co и Q.async на самом деле ничего сложного не делают, а просто автоматически выстраивают такую штуку:
gen.next().value.then(function (value1) {
    gen.next(value1).value.then(function (value2) {
        gen.next(value2).value.then(function (value3) {
            gen.next(value3)...
        });
    })
});


Вот рабочий пример для понимания:
function asyncGet1(value) {
    return new Promise(function (resolve) {
        setImmediate(resolve, value);
    });
}

function asyncGet2(value) {
    return new Promise(function (resolve) {
        setImmediate(resolve, value + 1);
    });
}

function asyncGet3(value) {
    return new Promise(function (resolve) {
        setImmediate(resolve, value * 2);
    });
}

function * genFunc() {

    var x = yield asyncGet1(1);
    console.log('x = ' + x); // x = 1

    var y = yield asyncGet2(x);
    console.log('y = ' + y); // y = 2

    var z = yield asyncGet3(y);
    console.log('z = ' + z); // z = 4

}

//вместо co или Q.async:
var gen = genFunc();

gen.next().value.then(function (x) {
    gen.next(x).value.then(function (y) {
        gen.next(y).value.then(function (z) {
            gen.next(z);
        });
    })
});
yield никакой асинхронностью не занимается, он просто останавливает выполнение генератора до следующего вызова next.

Когда пишешь генератор, можно представлять, что co в паре с yield разбивают genFunc на вот такие «подфункции»:
function next1() {
    asyncGet1(1).then(next2);
}

function next2(value) {
    x = value;
    asyncGet2(x).then(next3);
}

function next3(value) {
    y = value;
    asyncGet3(y).then(next4);
}

function next4(value) {
    z = value;
}
Здесь специально расписал через value, чтобы воспринималось, как promise value.



Кстати, в ES7 возможно будет реализация этого паттерна на уровне языка. Будут введены инструкции async (вместо co, Q.async) и await (вместо yield), и генераторы для это не нужны будут:
async function func() {
    var x = await asyncGet1(1);
    var y = await asyncGet2(x);
    var z = await asyncGet3(y);
}

kobezzza 13.05.2014 21:01

Цитата:

Кстати, в ES7 возможно будет реализация этого паттерна на уровне языка. Будут введены инструкции async (вместо co, Q.async) и await (вместо yield), и генераторы для это не нужны будут:
Вот это здорово! Как в Coffee Script :)

melky 13.05.2014 21:49

Цитата:

Сообщение от kobezzza
Вот это здорово! Как в Coffee Script

nooooooooooooooo

Vantedur 14.05.2014 00:07

по мойму проще сервант на кокока завести, кокока куда лучше с js дружет

cyber 14.05.2014 00:57

Цитата:

Сообщение от kobezzza
Вот это здорово! Как в Coffee Script

да что б они провалились !

Tim 16.05.2014 11:02

Цитата:

Сообщение от kobezzza
1) Не бросай исключений в асинхронных операциях, а передавай их как параметр callback;

Поддержу, мне тоже так удобнее.


Цитата:

Сообщение от Octane
скоро в Nodejs будут доступны встроенные ES6 Promises

Я пока юзаю https://github.com/jakearchibald/ES6-Promises


Цитата:

Сообщение от cyber
да что б они провалились !

Кст, я на CoffeeScript серверный JS пишу. Привык, удобно :)

kobezzza 16.05.2014 12:17

Цитата:

Кст, я на CoffeeScript серверный JS пишу. Привык, удобно
А с IcedCoffeeScript ещё удобнее :)

melky 16.05.2014 19:18

Цитата:

Сообщение от Tim
Кст, я на CoffeeScript серверный JS пишу. Привык, удобно

Цитата:

Сообщение от kobezzza
А с IcedCoffeeScript ещё удобнее

вы убедили меня написать следующий проект на кофе :) фронт+бэк

monolithed 16.05.2014 22:43

Цитата:

Сообщение от kobezzza
А с IcedCoffeeScript ещё удобнее

Цитата:

Сообщение от melky
вы убедили меня написать следующий проект на кофе фронт+бэк

Очень советую посмотреть в сторону LiveScript

melky 16.05.2014 23:33

Цитата:

Сообщение от monolithed
Очень советую посмотреть в сторону LiveScript

хорошее предложение)
давненько хотел начать писать в функциональном стиле на JS
в последний раз видел LiveScript, когда он только появился.
он сейчас готов к продакшену? трудно ли начать писать на нём? выглядит страшновато

vflash 17.05.2014 02:07

c последовательной асинхронностью поступаю также как и dmitry111. Никакой пользы от async в этом моменте на практике не увидел.
когда нужно сделать несколько параллельных запроса тоже не умничаю. Просто отсчитываю количество.
function X(options, end) {
    var J = 3; function complete() {
        if (--J !== 0) return;
        end(true);
    };
    // ...    
    process.nextTick(function () {
        complete();
    });
    process.nextTick(function () {
        complete();
    });
    process.nextTick(function () {
        complete();
    });
}

X({...}, function(status, data) {
    // ...
})

monolithed 17.05.2014 03:47

Цитата:

Сообщение от melky
в последний раз видел LiveScript, когда он только появился.
он сейчас готов к продакшену?

На столько же на сколько и CS
Цитата:

Сообщение от melky
трудно ли начать писать на нём?

приятно
Цитата:

Сообщение от melky
выглядит страшновато

видимо ты не работал с функциональными языками


Часовой пояс GMT +3, время: 19:54.