Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   Мой синхронный костыль (https://javascript.ru/forum/project/24115-mojj-sinkhronnyjj-kostyl.html)

FINoM 18.12.2011 05:23

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

Для этого вызываем функцию waiting, затем по цепочке вызываем метод wait. Аргументом waiting и wait служит колбек, определенный в примере, как runNext. Его вызов запускает очередную порцию кода.

Для проверки работы откройте консоль.
(function(){
	window.wait = function(first){
		return new (function(){
			var self = this;
			
			var callback = function(){
				var args;
				if(self.deferred.length) {
					args = [].slice.call(arguments);
					args.unshift(callback);
					self.deferred[0].apply(self, args);
					self.deferred.shift();
				}
			}
			
			this.wait = function(run){
				this.deferred.push(run);
				return self;
			}
			
			this.deferred = [];
			
			first(callback);
		})
	}	
	
	/* Пример */
	wait(function(runNext){
		console.log('Run 1');
		setTimeout(function(){
			runNext(1,2); //передаем какие-нибудь аргументы в следующий вызов
		}, 1000);
		
    }).wait(function(runNext, a, b){
		console.log('Run 2, a='+a+' b='+b ); //используем аргументы из предыдущего вызова
        setTimeout(runNext, 1000);
		
    }).wait(function(runNext){
		console.log('Run 3');
        setTimeout(function(){
			console.log('End 3')
			runNext();
		}, 1000);
		
    }).wait(function(runNext){
		console.log('Run 4');
		setTimeout(runNext, 1000);
		
	}).wait(function(){
		console.log('Last one');
		
	});
})();

Пример с анимациями http://jsfiddle.net/finom/XSGub/37/

B~Vladi 20.12.2011 00:56

Что-нибудь можешь прокомментировать по моей идеи?
http://habrahabr.ru/blogs/javascript...omment_4479560

FINoM 20.12.2011 06:33

B~Vladi, то что ты предложил несколько выходит за рамки моих знаний, поэтому компетентного ответа я дать не могу.

B~Vladi 20.12.2011 08:28

Я всё-таки допилю до юзабельного варианта и выложу.

melky 20.12.2011 10:32

не слишком гибко, когда для вызова следующей функции из цепочки обязательно вызывать runNext. конечно, для передачи аргументов в следующий вызов она как раз подходит, но для того, чтобы просто вызвать следующую функцию (99%), потребутся вводить заветные буквы.

возможно ли ту функцию сделать необязательной ? как в этом случае будет обрабатываться время следующего вызова ?

FINoM 20.12.2011 19:28

melky,
А как узнать, что тот же таймаут завершился? Только по коллбеку.
Цитата:

Сообщение от melky
возможно ли ту функцию сделать необязательной ? как в этом случае будет обрабатываться время следующего вызова ?

Ты имеешь в виду, если не указан колбек, то вызывать следующую функцию сразу?

melky 20.12.2011 21:43

да. в этом случае можно указывать параметр, является ли блок кода полностью синхронным, или нет (ajax, напр.). если код async:false (default), если же он async:true - будьте добры тогда указать сами, когда он закончит исполнение (runNext)

FINoM 20.12.2011 22:39

Не знаю есть ли в этом смысл. В любом случае, придется писать
wait(function(){
    this.async = false;
})


вместо
wait(function(){
    this.next();
});

melky 21.12.2011 00:26

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

vflash 21.12.2011 13:36

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

сейчас подумываю упростить. получается что-та вроде цепочки с паузами.


var sync = new ....;

setTimeout(sync(), 1000);
setTimeout(sync(), 100);

sync(function() {

});

setTimeout(sync(), 100);

var xx = sync();
sync(function() {
    ....
    setTimeout(xx, 1000);
    ....
});

sync(function() {

});

x-yuri 21.12.2011 19:06

Цитата:

Сообщение от vflash
сейчас подумываю упростить. получается что-та вроде цепочки с паузами.

ничего не понял

FINoM, если надо передать данные через несколько функций их прийдется "протаскивать" через аргументы. Лучше наделить цепочку состоянием, что-то типа:
this.state('var', ...);   // изменение переменной
this.state('var')   // получение значения переменной


и... названия какие-то странные, может лучше так:
chain(function() {
    ...
}).then(function() {
    ...
}).then(function() {
    ...
});

devote 21.12.2011 19:32

ниче не понял, а зачем вообще это? вызывать четко по очереди или просто нужна задержка между вызовами функций?

Gozar 21.12.2011 19:33

вызывать четко по очереди

у меня вывод такой FF:
Run 1
undefined
Run 2, a=1 b=2
Run 3
End 3
Run 4
Last one

devote 21.12.2011 19:37

хм... а разве обычный вызов:
func1();
func2();
func3();
не по очереди вызываются?

Gozar 21.12.2011 19:47

devote,
setTimeout в одну из первых двух поставь

devote 21.12.2011 20:02

Цитата:

Сообщение от Gozar
setTimeout в одну из первых двух поставь

А ну понятно.

FINoM 22.12.2011 02:59

Цитата:

Сообщение от x-yuri
this.state('var', ...);   // изменение переменной
this.state('var')   // получение значения переменной

Эм, не понял.
Цитата:

Сообщение от x-yuri
названия какие-то странные, может лучше так

У меня не очень хорошо с английским и мне показалось ждать().ждать().ждать() достаточно логичным. Думаешь бред?

x-yuri 22.12.2011 07:34

devote, это актуальнее для nodejs, хоть и на клиенте может быть надо.

Цитата:

Сообщение от FINoM
Эм, не понял.

this.state = function(name, value) {
    if (arguments.length) {
        this._state[name] = value;
        return this;
    } else {
        return this._state[name];
    }
}


Цитата:

Сообщение от FINoM
У меня не очень хорошо с английским и мне показалось ждать().ждать().ждать() достаточно логичным. Думаешь бред?

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

FINoM 22.12.2011 08:05

Теперь не обязательно заботиться о целостности цепочки: http://jsfiddle.net/finom/XSGub/50/

FINoM 22.12.2011 08:06

x-yuri, я тебе позже отвечу, а то мозг не варит.

vflash 22.12.2011 10:19

Цитата:

Сообщение от x-yuri
ничего не понял

есть цепочка. наполнение идет через вызов функции sync(A). Если A функция sync(function(){...}) то она просто добавляется . если sync() то добавиться блокировку и вернет функцию вызов которой снимет блокировку. дальше просто выполняем функции по цепочке, а если встречается блокировка то ждем пока не разблокируют.

как пример применения. нужно сделать несколько асинх. запросов, дождаться их завершения и выполнить какую-то функцию. Тоесть ставим несколько блокировок, а после них нужную функцию. А вот в методом FINoM такое не сделать.

x-yuri 22.12.2011 13:47

vflash, так тебе не цепочка, а defered тогда нужен

FINoM 22.12.2011 16:54

Цитата:

Сообщение от x-yuri
Во-первых, вызова первой функции ты не ждешь.

Я как бы жду, чтоб она выполнилась.
Цитата:

Сообщение от x-yuri
this.state = function(name, value) {...

Ну гетить и сетить значения чего-либо в методе — это я умею. Вот только не пойму, к чему это.

x-yuri 22.12.2011 17:06

к тому что на данный момент ты подразумеваешь, что значения будут передаваться между соседними функциями
waiting(function(runNext) {
    runNext(1, 2, 3, 4, 5, 6, 7, 8, 9);
})
.wait(function(runNext, v1, v2, v3, v4, v5, v6, v7, v8, v9) {...})
.wait(function(runNext, v1, v2, v3, v4, v5, v6, v7, v8, v9) {...})
.wait(function(runNext, v1, v2, v3, v4, v5, v6, v7, v8, v9) {...})
.wait(function(runNext, v1, v2, v3, v4, v5, v6, v7, v8, v9) {
    // и только здесь нам пригодятся эти значения
})

альтернативный вариант
new AsyncChain()
    .add(function(NEXT) {
        this.state('a1', 1);
        this.state('a2', 2);
        this.state('a3', 3);
        this.state('a4', 4);
        this.state('a5', 5);
        this.state('a6', 6);
        this.state('a7', 7);
        this.state('a8', 8);
        this.state('a9', 9);
        NEXT();
    })
    .add(function(NEXT) {...})
    .add(function(NEXT) {...})
    .add(function(NEXT) {...})
    .add(function(NEXT) {
        // а теперь используем их, this.state('a...');
    })
    .go();

FINoM 22.12.2011 17:20

x-yuri, не вижу смысла городить такой костыль. Человек, если ему захочется передавать через всю цепочку состояния переменных, может просто-напросто передавать объект.
wait(function(next){
  var states = {a: 1, b: 2}
  next(states);
}).wait(function(next, states){
  states.c = 3;
  next(states);
}).wait(function(next, states){
  states.d = 4;
  states.e = 5;
});

FINoM 22.12.2011 18:20

http://jsfiddle.net/finom/XSGub/52/ — сделал сделал this для всей цепочки одним объектом (раньше в первой функции this === window). Функции get и set здесь излишни, как по мне.
wait(function(next){
  this.blabla = {a:1,b:2}
  next();
}).wait(function(next){
  this.blabla.c = 3;
  next()
}).wait(next) {
  log(this.blabla); // {a:1, b:2, c:3}
});

Gozar 22.12.2011 19:00

Можно присоединится?,
сейчас в голову велосипед приехал:

var chain = function () {};

chain.prototype.deferred = [];

chain.prototype.add = function (fn) {
    this.deferred.push(fn);
    return this;
}
chain.prototype.next = function () {
    if (this.deferred.length) {
        this.deferred.shift().apply(this);
    }
}
chain.prototype.start = function () {
    this.deferred.shift().apply(this);
}




new chain().
    add(function(){
        var self = this;
        setTimeout(function () {
            console.log(1);
            self.next();
        }, 1000);
    }).
    add( function(){
        var self = this;
            setTimeout(function () {
                console.log(2);
                self.next();
            }, 1000);
    }).
    add( function(){
        console.log(3);
    }).
    start();

FINoM 22.12.2011 20:11

Цитата:

start();
Лишний метод. Если человек хочет запустить цепь потом, проще сделать так:
w = function(){
  return wait(...).wait(...)
}
w();

Цитата:

self.next();
Я сперва тоже так думал, но
var self = this;
тоже кажется излишним.

+ у тебя теряется целостность. Если последний коллбек сработал, следующие звенья не запустятся.

Gozar 22.12.2011 20:38

Цитата:

Сообщение от FINoM (Сообщение 145376)
Лишний метод. Если человек хочет запустить цепь потом, проще сделать так:
w = function(){
  return wait(...).wait(...)
}
w();

А как по поводу такого? Он не лишний, он позволяет делать так:

var chain = function () {};

chain.prototype.deferred = [];

chain.prototype.add = function (fn) {
    this.deferred.push(fn);
    return this;
}
chain.prototype.next = function () {
    if (this.deferred.length) {
        this.deferred.shift().apply(this);
    }
}
chain.prototype.start = function () {
    this.deferred.shift().apply(this);
}




var a = new chain().
    add(function(){
        var self = this;
        setTimeout(function () {
            console.log(1);
            self.next();
        }, 1000);
    }).
    add( function(){
        var self = this;
            setTimeout(function () {
                console.log(2);
                self.next();
            }, 1000);
    }).
    add( function(){
        console.log(3);
        this.next();
    });

setTimeout(function(){
    console.log('добавим чего-нибудь в цепь');
    a.add(function(){
        console.log(4);
    }).start();
},2000);



Цитата:

Сообщение от FINoM (Сообщение 145376)
Я сперва тоже так думал, но
var self = this;
тоже кажется излишним.

С этим пока не знаю что делать, может забить?

Цитата:

Сообщение от FINoM (Сообщение 145376)
+ у тебя теряется целостность. Если последний коллбек сработал, следующие звенья не запустятся.

Можно пример? Не понимаю.

FINoM 22.12.2011 21:14

Цитата:

А как по поводу такого?
Цитата:

С этим пока не знаю что делать, может забить?
Ну тебе виднее, это твой скрипт. Лично мне кажется лишним вызов конструктора.
Цитата:

Не понял?
Вот пример с большим таймаутом: http://jsfiddle.net/finom/XSGub/55/

Gozar 22.12.2011 21:37

Цитата:

Сообщение от FINoM (Сообщение 145381)
Вот пример с большим таймаутом

Я возможно понял, что ты имеешь в виду.

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

Я к тому веду, что как мне используя твою функцию собрать цепочку, но не запускать её? Обернуть в функцию, а потом ещё раз и ещё раз, добавляя по звену, если мне нужно собирать звенья через таймауты(допустим ajax)?

Примеры получаются какие-то абстрактные.

Gozar 22.12.2011 21:50

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

FINoM 22.12.2011 22:14

Цитата:

Я к тому веду, что как мне используя твою функцию собрать цепочку, но не запускать её?
Я уже говорил:
w = function(last){
  return wait(function(next){...}).wait(function(next){...}).wait(last)
}


потом, если хочешь запустить, добавив еще функций в стек, пишешь:
w(function(next){...}).wait(funciton(next){...}).wait(function(next){...});

Gozar 22.12.2011 22:31

Я уже запутался, что и как вызывать. То w добавляет в цепочку, то wait да ещё и в функцию обернуть.

Останемся каждый при своём, тебе наверное так удобнее.

Кстати:
var self = this;

можно не писать, если сделать так:

var c = new chain();

    c.
    add(function(){
        setTimeout(function () {

            c.next();

        }, 1000);
    }).
...

x-yuri 23.12.2011 06:27

Цитата:

Сообщение от FINoM
x-yuri, не вижу смысла городить такой костыль. Человек, если ему захочется передавать через всю цепочку состояния переменных, может просто-напросто передавать объект.

давай называть вещи своими именами. У тебя цепочка функций без состояния. И если нужно добавить состояние, ты его передаешь через параметры функций. У меня цепочка функций - это объект с состоянием. Внимание вопрос, что из этого костыль? Что есть заворачивание цепочки в функцию, чтобы она не вызвалась?

Цитата:

Сообщение от FINoM
http://jsfiddle.net/finom/XSGub/52/ — сделал сделал this для всей цепочки одним объектом (раньше в первой функции this === window). Функции get и set здесь излишни, как по мне.

а теперь непонятно, откуда взялся this. У тебя там только функции.

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

Цитата:

Сообщение от Gozar
Примеры получаются какие-то абстрактные.

прямо в точку :yes: у кого-нибудь возникала необходимость в этих самых цепочках? Или мы опять решаем абстрактную задачу?

B~Vladi 23.12.2011 10:47

Цитата:

Сообщение от x-yuri
у кого-нибудь возникала необходимость в этих самых цепочках?

Скорее не хватило знаний/опыта и этот вариант им кажется самым логичным и лёгким. Вот и вся необходимость.
Если погуглить, то асинхронные приложения писались задолго до появления NodeJS и уже выработались соответствующие паттерны. Но в JS как всегда всё по-своему :)

x-yuri 23.12.2011 11:30

я считаю, что есть прямые пути, а есть обходные. Я предпочитаю ходить напрямик, если нету причин ходить в обход. Ваши причины для меня не причины (недостаточные причины). Нехватка знаний/опыта ни при чем. Они в лучшем случае (или худшем, это как посмотреть) помогают ходить в обход, но с причинами никак не связаны. И если так, то даже хорошо, что не хватило. ;)

а асинхронное программирование, о котором можно погуглить, - нечто совсем отличное от асинхронного программирования в js.

Цитата:

Сообщение от B~Vladi
Если погуглить, то асинхронные приложения писались задолго до появления NodeJS и уже выработались соответствующие паттерны. Но в JS как всегда всё по-своему

абстрактно: другой контекст - другое решение. Но ты можешь попробовать привести эти паттерны асинхронного программирования, которые js-программисты отказываются принимать.

FINoM 23.12.2011 12:16

Цитата:

Сообщение от x-yuri
а теперь непонятно, откуда взялся this. У тебя там только функции.

Да, и в этих функциях this относится к анонимному классу, а не к window.
Цитата:

Сообщение от x-yuri
Или мы опять решаем абстрактную задачу?

Мы решаем задачу поочередного вызова асинхронных функций. Ты предложил сделать объект состояний и функции-геттеры и -сеттеры, я посчитал, что this.state = {} или this.miyobiekt = {} более подходящее решение, чем функции (главное не переопределить объекты в классе). Что не так?
Цитата:

Сообщение от B~Vladi
Скорее не хватило знаний/опыта и этот вариант им кажется самым логичным и лёгким.

Есть другие варианты (кроме аналогов моего костыля, которых достаточно много)?

x-yuri 23.12.2011 12:31

Цитата:

Сообщение от FINoM
Да, и в этих функциях this относится к анонимному классу, а не к window.

до этого не сложно догадаться, но в моем случае объект присутствует явно, и к чему относится this очевидно. Или по-другому: у тебя там только функции, причем здесь this? А у меня есть объект.

Цитата:

Сообщение от FINoM
Мы решаем задачу поочередного вызова асинхронных функций. Ты предложил сделать объект состояний и функции-геттеры и -сеттеры, я посчитал, что this.state = {} или this.miyobiekt = {} более подходящее решение, чем функции (главное не переопределить объекты в классе). Что не так?

отсутствует связь с реальностью. Где конкретные примеры, которые эта "библиотека" должна решать? Хорошие библиотеки возникают из решения практических задач, а не из теоретических рассуждений. Я тебе советую заняться не библиотеками, а задачами. А библиотеки, они сами появятся. ;)

...продолжу. FINoM, ты стараешься не усложнять. Это твоя причина. Я тоже так старался делать. Но в результате такие решения не выдерживали столкновения с практикой и приходилось их доделывать. B~Vladi, ты делаешь вид что проблемы не существует в ущерб разбиению на классы что ли. Причина: производительность и прямолинейность. Прямолинейность делает реализацию прозрачной, но при этом сложно увидеть намерения. Прозрачность намерений или выразительность - это тот самый прямой путь, о котором я говорил.

FINoM 23.12.2011 13:34

Цитата:

Сообщение от x-yuri
библиотека

Я нигде не называл это библиотекой.
Цитата:

Сообщение от x-yuri
Где конкретные примеры

Примеры я уже приводил, вот хотя-бы этот:
Цитата:

Рассмотрим пример (который взят из головы и в нем возможны ошибки) гипотетического парсера сайта, который после парсинга заносит данные в БД, и, после занесения, вызывает некоторый код.
var html = '';
request.on('response', function (response) {

    response.on('data', function (chunk) {
        html = html + chunk;
    });

    response.on('end', function() {
        //какой-то парсер
        parse(html, function(data){  
                //какая-нибудь функция, добавляющая данные в базу
        addToDatabase(data, function() {  
            doSomething();
        })
    });

    });
});



Много вложенных колбеков — не есть гуд, пробуем по-другому.
var html = '';
var responceOnEnd = function() {
    parse(html, parsed);
}
    
var parsed = function(data){ 
    addToDatabase(data, addedToDatabase)
}

var addedToDatabase = function() {
    doSomething();
}

request.on('response', function (response) {

    response.on('data', function (chunk) {
        html = html + chunk;
    });

    response.on('end', responceOnEnd);
});



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

Я предлагаю сделать вот так:

wait(function(runNext){
    request.on('response', runNext);
}).wait(function(runNext, response){
    response.on('data', function (chunk) {
        html = html + chunk;
    });
    
    response.on('end', function() {
        runNext(html);
    });
}).wait(function(runNext, html){
    parse(html, runNext);
}).wait(function(runNext, data){
    addToDatabase(data, runNext);
}).wait(function(){
    doSomething();
})



Цитата:

Сообщение от x-yuri
до этого не сложно догадаться, но в моем случае объект присутствует явно, и к чему относится this очевидно. Или по-другому: у тебя там только функции, причем здесь this? А у меня есть объект.

Это дело твоё, хочешь, напиши функции setState, getState, я ведь тебе не указ, как и ты мне. Пишем так, как каждый считает лучшим, не без советов, к которым я стараюсь прислушиваться.
Цитата:

Сообщение от x-yuri
ты стараешься не усложнять

Да, ты прав, я люблю, когда всё просто. Самые популярные вещи — просты в использовании, посмотри на ту же "быдло-библиотеку" jQuery.
Цитата:

Сообщение от x-yuri
Но в результате такие решения не выдерживали столкновения с практикой и приходилось их доделывать.

А если усложнить, то, вполне возможно, придется переделывать. Ты сам пишешь:
Цитата:

Сообщение от x-yuri
Хорошие библиотеки возникают из решения практических задач

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


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