Как стать автором
Обновить

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

У любого решения есть недостатки. Какие вы видите недостатки вашего решения?
Ваш «реальный» код требует рефакторинга, имхо. Тогда он будет более читабельный.
При использовании Sync.Parallel — как различать результаты функции? Например, мне необходимо скачать данные из файла, получить данные из базы и подгрузить данные с удалённого сайта. Я пускаю их паралельно, они возвращаются неизвестно в каком порядке. Но мне нужны все три аргумента. Как определить, какой из аргументов к какому запросу относится? В моём решении можно использовать args.fs, args.db и args.remote.

Не подумайте, что я критикую, или придираюсь=)
> У любого решения есть недостатки. Какие вы видите недостатки вашего решения?
как минимум то, что нужно стартовать отдельный поток, прежде чем использовать Function.prototype.sync() и другие функции — это не всегда удобно

> Ваш «реальный» код требует рефакторинга, имхо. Тогда он будет более читабельный.
это специально сделано, пусть народ воззреет реалии :)

> При использовании Sync.Parallel — как различать результаты функции? Например, мне необходимо скачать данные из файла, получить данные из базы и подгрузить данные с удалённого сайта. Я пускаю их паралельно, они возвращаются неизвестно в каком порядке. Но мне нужны все три аргумента. Как определить, какой из аргументов к какому запросу относится?

// Ассоциативный вариант
var results = Sync.Parallel(function(callback){
    someAsyncFunction(2, 2, callback('foo')); // assign the result to 'foo'
    someAsyncFunction(5, 5, callback('bar')); // assign the result to 'bar'
});
console.log(results); // { foo: 4, bar: 10 }

// Future
// Запускаем someAsyncFunction, но не блокируем поток
var foo = someAsyncFunction.future(null, 2, 2);
var bar = someAsyncFunction.future(null, 5, 5);
    
// А вот теперь, дожидаемся значений от foo и bar
console.log(foo.value, bar.value); // 4 10 - ровно через секунду (не две)

> Ваш «реальный» код требует рефакторинга, имхо. Тогда он будет более читабельный.
слегка упростил пример
> У любого решения есть недостатки. Какие вы видите недостатки вашего решения?
все же
var result = someObject.someFunction.sync(someObject, arg1, arg2)

не на столько удобно, как:
var result = someObject.someFunction(arg1, arg2)

но в любом случае, удобнее чем:
someObject.someFunction(arg1, arg2, function(err, result){
    if (err) return callback(err);
})

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

>Главная идея в том, что метод Function.prototype.sync()
Т.е. augumenting built-in prototype теперь хорошая идея?

Представленный вами «реальный» код, реален настолько же насколько реальна необходимость в один шаг: открыть соединение с монго, создать в нем новую коллекцию, заполнить ее новыми документами и выбрать из них же что-то обратно. Пример не удачен, хотя в ноде и не мало мест где можно далеко уйти с колбеками. Но все равно не понял как ваша обертка помогает ловить ошибки, в примере с ней даже нет намека на переменную с ошибкой. Вот как здесь:
var cursor = collection.find.sync(collection, {'_id':new ObjectID(«aaaaaaaaaaaa»))
поймать ошибку упавшего соединения и обработать ее не роняя ноду?

Честно говоря, я рассчитывал на бóльшую компетентность читателей.
Вы перед тем, как писать такой комментарий, лучше бы изучили вопрос по подробнее. «Претендуешь — соответствуй».
Это yield — поддержка программных прерываний потока без глобальной блокировки процесса. Найдите мне 10 библиотек, использующих нативный yield.

Если collection.find.sync(collection, {'_id':new ObjectID(«aaaaaaaaaaaa»)) вернет err первым аргументом в callback, который передает ему sync(), то этот err вылетит в поток. Если он не будет словлен потоком, то err попадет в callback, который мы передаем в Sync(fn, callback).

Простой пример:
// Обычная асинхронная функция, вызывает callback через 1 сек с ошибкой
function someAsyncFunction(callback) {
    setTimeout(function(){
        callback('something went wrong');
    }, 1000)
}

// Вариант 1
// Новый поток
Sync(function(){
    try {
        // вызываем функцию синхронно, птичка вылетит отсюда
        someAsyncFunction.sync(); 
    }
    // ловим ошибку. в catch. правда.
    catch (e) {
        console.log(e); // something went wrong
    }
})

// Вариант 2
Sync(function(){
    
    // вызываем функцию синхронно
    someAsyncFunction.sync();

    // или вот так
    throw new Error('something went wrong');

}, function(err){
    // Ловим здесь любой exception из текущего потока
    console.log(e); // something went wrong
})


В примере со статьи, любая ошибка, вылетевшая из любой вызванной там функции, попадет в результирующий callback:
function syncFunction() {
    // 
    // внутри очень красивый синхронный код
    // отсюда можно throw
    // любая ошибка из sync() = throw = попадет в результирующий callback
    // 
}.async()

syncFunction(function(err){
    console.log(err); // вот сюда
})

> Пример не удачен, хотя в ноде и не мало мест где можно далеко уйти с колбеками.
Дайте пример лучше — обновлю статью.
Меня весьма печалят ваши не оправдавшиеся надежды, но давайте отложим пока специальную олимпиаду.

>Это yield — поддержка программных прерываний потока без глобальной блокировки процесса.
Трудно описать радость от появления «yeild» в ноде. Не вдаваясь в излишнее теоретизирование, давайте разберем какую проблему это должно решать. Если необходимо выполненить что-то вне основного потока, то есть воркеры. О преимуществах fiber c нейтивной yeild и их use case'ах я не услышал.

Касательно node-sync в статье вы обозначили три проблемы, которые она призвана решить. Вот решение двух последних, обработка ошибок и «неконфликтность», не совсем очевидно из этой статьи, на что я и указываю.

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

Далее, к третьей проблеме, расширение базовых прототипов это реально плохая идея. Этим: Function.prototype.sync() вы предлагаете всем остальным отказаться от именования своих ф-ий/свойств как sync дабы не перекрывать вашу глобальную. Это никак не улучшает шансы безболезненной интеграции с существующим или будущим кодом.
> О преимуществах fiber c нейтивной yeild и их use case'ах я не услышал.
так о чем мы вообще говорим?

> ничего некорректного в обычной обработке нет
а я говорил, что есть?

> Далее, к третьей проблеме, расширение базовых прототипов это реально плохая идея.
Согласен. Но это чертовски удобно.

Да, и вправду, это syntactic sugar, заисключанием парочки нюансов, о которых вы не знаете (всего-то).
Если используются потоки, разве не нужно как нибудь блокировать доступ к критическим переменным?
В реализации node-fibers для v8, может работать только один поток в один момент времени.
Для node.js это получается костыльно-ориентированное программирование. Пишите осмысленный код — и он будет удобен и понятен в любом случае, хоть там синхронный поток, хоть асинхронные функции.
> Для node.js это получается костыльно-ориентированное программирование
А как вы назовете тогда соглашение о том, что «callback всегда должен быть последним и ошибку принимать первым агрументом»?
Ну тут всё логично.
Если ошибка — значит, в подавляющем большинстве случаев, других данных не будет — следовательно логично первым аргументом.
А callback — это обычно последнее, что выполняет функция, соответственно и идёт оно последним аргументом.
Ну, а что нелогичного в том, чтобы у функции в прототипе был метод sync (аналогичный call и apply), который будет абстрагировать вас от коллбэков?
То что это дублирует уже существующую функциональность, создаёт костыль для неё.
Не дублирует, а абстрагирует.
Тогда функциональное программирование в целом — это один большой костыль?

Библиотека Seq — костыль?
То что делается с целью «асинхронное, но чтобы выглядило типа как синхронное» — костыли. Причем, очень вредные в целом, имхо.

Сейчас есть шанс, что Node.JS превратится в подобие PHP, появятся толпы говноскриптеров, которые прочитали только как вывести alert и побежали сайты создавать.

Люблю Javascript и не хочу чтобы отношение к нему стало такое же как к PHP сейчас.
> То что делается с целью «асинхронное, но чтобы выглядило типа как синхронное» — костыли. Причем, очень вредные в целом, имхо.
Что такое «асинхронное» и как это должно выглядеть? Если есть устоявшееся мнение, что «асинхронное» в nodejs выглядит в виде анонимного коллбэка с ошибкой первым аргументом — это всего лишь последствие синтаксических свойств языка на текущий момент его развития. Если есть способ упростить эту конструкцию в свете развития возможностей технологии — это не костыль.

Вот костыль:
someAsyncFunction(function(err, result){
    if (err) return callback(err); // <-- костыль
})


Нет костыля:
try {
    // костыль? а call, apply, bind - тоже костыли?
    var result = someAsyncFunction.sync();
}
catch (e) {
    // нет костыля
}


> Люблю Javascript и не хочу чтобы отношение к нему стало такое же как к PHP сейчас.
Я тоже люблю Javascript. Но то, что вы будете называть костылями новые его возможности, явно ему не поможет )

> Сейчас есть шанс, что Node.JS превратится в подобие PHP, появятся толпы говноскриптеров, которые прочитали только как вывести alert и побежали сайты создавать.
ниасилят, имхо
Костыль это не новые возможности, а переиначивание уже существующих возможностей для чьего-нибудь удобства. Да и то, удобства субъективного.

Node.js специально сделан асинхронным, без блокирования потока, а тут наоборот возвращаются блокировщики и т.п.
> Костыль это не новые возможности, а переиначивание уже существующих возможностей для чьего-нибудь удобства. Да и то, удобства субъективного.
Согласен. Но это не относится к данному решению.

> Node.js специально сделан асинхронным, без блокирования потока, а тут наоборот возвращаются блокировщики и т.п.
Ну вы уже прям как MagaSoft. Вы хоть поняли, как это работает?

yield — это новые возможности, или костыль?
Тогда продемонстрируйте мне удобный, понятный и безкостыльный nodejs код (с корректной обработкой ошибок), который:
a. Последовательно прочтет 3 файла один за другим, склеит и выведет их исходный код
б. Параллельно прочтет 3 файла, склеит и выведет их исходный код (но в жестко заданной последовательности)

a. на node-sync:
var Sync = require('sync'), fs = require('fs');
Sync(function(){
    try {
        var file1 = fs.readFile.sync(null, 'file1');
        var file2 = fs.readFile.sync(null, 'file2');
        var file3 = fs.readFile.sync(null, 'file3');
        console.log([file1, file2, file3].map(String).join(''));
    }
    catch (e) {
        console.error(e);
    }
})


б. на node-sync:
var Sync = require('sync'), fs = require('fs');
Sync(function(){
    try {
        var file1 = fs.readFile.future(null, 'file1');
        var file2 = fs.readFile.future(null, 'file2');
        var file3 = fs.readFile.future(null, 'file3');
        console.log([file1.result, file2.result, file3.result].map(String).join(''));
    }
    catch (e) {
        console.error(e);
    }
})
a.
var file1 = fs.readFileSync('file1');
var file2 = fs.readFileSync('file2');
var file2 = fs.readFileSync('file3');
console.log(file1+" "+file2+" "+file3); 


;-))
Егор, fs.readFileSync() в отличие от fs.readFile.sync() — блокирует весь процесс, что не есть круто )
Ну в реальной жизни что фибра, что процесс целиком — одна хрень.:)
Да ну, у меня пока твои файлы будут читаться, уже в базу что-то запишется )
Это если ты кодишь фибрами, а я лично не собираюсь:)
Когда сядем за erlang?
Боюсь-боюсь
PS: такие конструкции как «console.log([file1.result, file2.result, file3.result].map(String).join(''));» значительно усложняют понимание кода и стоимость вхождения в него новых программистов без каких-либо заметных выгод.
Доктор, во мне иногда просыпается обфускатор )
Интересное решение, но кардинальных «бонусов» не вижу.
Использую Seq, он для меня решает все три упомянутые задачи. И при этом не меняет прототипы :)
var fs = require('fs');
var exec = require('child_process').exec;
var Seq = require('seq');

Seq()
    .seq(function () {
        exec('whoami', this)
    })
    .par(function (who) {
        exec('groups ' + who, this);
    })
    .par(function (who) {
        fs.readFile(__filename, 'ascii', this);
    })
    .seq(function (groups, src) {
        console.log('Groups: ' + groups.trim());
        console.log('This file has ' + src.length + ' bytes');
    })
;

vs
var fs = require('fs');
var exec = require('child_process').exec;
var Sync = require('sync');

Sync(function(){
    var who = exec.sync(null, 'whoami'); // seq
    var groups = exec.future(null, 'groups ' + who); // par
    var src = fs.readFile.future(null, __filename, 'ascii'); // par
    console.log('Groups: ' + groups.result.trim());
    console.log('This file has ' + src.result.length + ' bytes');
})

(результат идентичен, groups и src работают параллельно)

А в следующей версии планирую добавить автоматическое преобразование объектов:
var Sync = require('sync');
var fs = Sync(require('fs'));
var cp = Sync(require('child_process'));

Sync(function(){
    var who = cp.exec('whoami'); // seq
    var groups = cp.execFuture('groups ' + who); // par
    var src = fs.readFileFuture(__filename, 'ascii'); // par
    console.log('Groups: ' + groups.result.trim());
    console.log('This file has ' + src.result.length + ' bytes');
})
Может быть совсем заменить внедрение в prototype (что всё-таки не есть хорошо) обёртками объектов?

Sync(fs).readFile(__filename, 'ascii')

FutureSync(fs).readFile(__filename, 'ascii')
Ну так об этом и идет речь в последнем примере. Только FutureSync(fs) это уже слишком :)
Была еще мысль вот такого подхода (чтобы не портить Function.prototype):

// Sync(fn [,obj])([arg1, arg2, ...])
var source = Sync(fs.readFile, fs)(__filename, 'ascii');

Но это, как по мне, хуже, чем:
var source = fs.readFile.sync(fs, __filename, 'ascii');
Всё-таки очень уж некрасивы эти припевы-повторы объекта (fs в данном случае).
Ну, это уже специфика языка такая. Те же самые проблемы с call() и apply().
Да, это понятно, но обёртки как раз позволяют от этого избавиться.
Если оборачивать объект полностью — то да. Но в таком случае слишком много избыточных операций. Представьте себе, мы хотим использовать Sync(fs).readFile — а для этого программе нужно обработать и обернуть все функции объекта fs.
Тут два варианта: либо сделать обертку с помощью Proxy из Harmony (реализация для Node.js: github.com/bmeck/node-overload), либо включить в свою библиотеку свои версии методов стандартных библиотек, которых, кстати, не много.
> либо сделать обертку с помощью Proxy из Harmony

Интересненько, что-то вроде:

var Sync = require('sync'),
    fs = Sync.proxy('fs');

Sync(function(){
    var source = fs.readFile(__filename, 'ascii');
})


> либо включить в свою библиотеку свои версии методов стандартных библиотек, которых, кстати, не много.

Это не особо спасет, потому что чаще всего имеешь дело именно с нестандартными библиотеками.
Как минимум,
Код должен отражать выполняемую задачу при минимальном количестве «шума».
Бонус — у node-sync меньше шума.
Хорошая идея и реализация. Не совсем понимаю, откуда столько критиков набежало. Способ действительно значительно упрощает код, хотя и имеет определенные рамки применения. Самым вкусным мне показалась возможность обвязать этот функционал, чтобы обеспечить декларативное задание для той части логики, где это возможно. При этом не сложно представить как большая часть Sync() и .sync() исчезнет из кода, что сделает его еще более читабельным и простым.
вопрос — предположем мне нужно считать файл построчно и для каждой строки выполнить действие — например проверить регуляркой и в некоторых случаях запистьа в базу
Как это можно сделать?
в случае с callbacks все понятно — но в случае с фиберами не очень.
Напишите пример того, что вам нужно на callbacks — а я переведу на sync.
function readFileByLinesAsync(path, callback) {
var input = fs.createReadStream(path);
var remaining = '';

input.on('data', function(data) {
remaining += data;
var index = remaining.indexOf('\n');
while (index > -1) {
var line = remaining.substring(0, index);
remaining = remaining.substring(index + 1);
callback(line);
index = remaining.indexOf('\n');
}
});

input.on('end', function() {
if (remaining.length > 0) {
callback(remaining);
}
});
}
-----------
readFileByLinesAsync('text', function(data){
//add data to mysql if data.type = foo
});
Я бы в данном случае воспользовался Futures API. При этом, саму реализацию построчного чтения файла оставил бы асинхронным.

function readFileByLinesAsync(path, callback) {
    
    var input = fs.createReadStream(path);
    var remaining = '';

    input.on('data', function(data) {

        remaining += data;

        var index = remaining.indexOf('\n');
        while (index > -1) {
            var line = remaining.substring(0, index);
            remaining = remaining.substring(index + 1);
            callback(line);
            index = remaining.indexOf('\n');
        }
    });

    input.on('end', function() {
        if (remaining.length > 0) {
            callback(remaining);
        }
        // End
        callback(null);
    });
}

//----------------
// My sync code
var Sync = require('sync');

Sync(function(){
    // My sync code
    var future = new Sync.Future();
    
    readFileByLinesAsync('text', function(data) {
        // resume
        if (data === null) return future();
        
        //add data to mysql if data.type = foo
        //...
    });
    
    // wait until we read all lines
    future.yield();
    
    // do something else..
    
})


(почему-то комментарии автоматически сдвигаются вправо..)

Ну а если очень хочется «синхронную» реализацию readFileByLinesAsync, тогда:

function readFileByLinesSync(path, fn) {

var future = new Sync.Future();
var input = fs.createReadStream(path);
var remaining = '';

input.on('data', function(data) {

remaining += data;

var index = remaining.indexOf('\n');
while (index > -1) {
var line = remaining.substring(0, index);
remaining = remaining.substring(index + 1);
fn(line);
index = remaining.indexOf('\n');
}
});

input.on('end', function() {
if (remaining.length > 0) {
fn(remaining);
}
// End
future();
});

future.yield();

}.async(); // < — important

//----------------
// My sync code
var Sync = require('sync');

Sync(function(){

// This will block until all lines will be passed
readFileByLinesSync('text', function(data) {
//add data to mysql if data.type = foo
//…
});

// do something else…

})
(случайно отправил)
«синхронная» реализация readFileByLinesAsync:

function readFileByLinesSync(path, fn) {
    
    var future = new Sync.Future();
    var input = fs.createReadStream(path);
    var remaining = '';

    input.on('data', function(data) {

        remaining += data;

        var index = remaining.indexOf('\n');
        while (index > -1) {
            var line = remaining.substring(0, index);
            remaining = remaining.substring(index + 1);
            fn(line);
            index = remaining.indexOf('\n');
        }
    });

    input.on('end', function() {
        if (remaining.length > 0) {
            fn(remaining);
        }
        // End
        future();
    });
    
    future.yield();
    
}.async(); // <-- important


//----------------
// My sync code
var Sync = require('sync');

Sync(function(){
    
    // This will block until all lines will be passed
    readFileByLinesSync('text', function(data) {
        //add data to mysql if data.type = foo
        //...
    });
    
    // do something else..
    
})
супер!!! спасибо!
не думал что есть решение!
однако видимо в данном случае «шума» существенно меньше в асинхронном варианте
расскажите больше о применимости/неприменимости node-sync
Представьте себе, что вам нужно последовательно прочитать три разных файла, вместо одного. Тогда в последнем случае, вы сделаете:

Sync(function(){
    
    readFileByLinesSync('text1', function(data) {
        //...
    });
    
    readFileByLinesSync('text2', function(data) {
        //...
    });
    
    readFileByLinesSync('text3', function(data) {
        //...
    });

    // do somehting else..
    
})


А без sync тут будут макароны.

Кстати, придумал более изящный способ реализации синхронного readFileByLines:

function readFileByLinesSync(path, fn) {
    
    var input = fs.createReadStream(path);
    var remaining = '';

    input.on('data', function(data) {
        remaining += data;

        var index = remaining.indexOf('\n');
        while (index > -1) {
            fn(remaining.substring(0, index)); // process line
            remaining = remaining.substring(index + 1);
            index = remaining.indexOf('\n');
        }
    });
    
    // Yield here
    input.once.sync(input, 'end');
    
    // process last line
    if (remaining.length > 0) {
        fn(remaining);
    }
    
}.async(); // <-- important


Но если серьезно, ваша реализация построчного чтения файла будет глючить с большими строками — нужен буффер. Вот, пример: blog.jaeckel.com/2010/03/i-tried-to-find-example-on-using-node.html
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории