Skip to content

Instantly share code, notes, and snippets.

@monjudoh
Created February 7, 2011 07:54
Show Gist options
  • Save monjudoh/814121 to your computer and use it in GitHub Desktop.
Save monjudoh/814121 to your computer and use it in GitHub Desktop.
長時間かかる反復メソッド(each,map,reduce等)の実行をtimerで飛ばしながら休み休み行うUnderscore.js用mixin
/*
* Underscore.js plugin
* http://documentcloud.github.com/underscore/
*/
(function(){
var root = this;
var console = root.console;
var optionsTable = {};
var setResultTable = {};
var lib = {};
var internal = {};
/*
* no operation function
*/
function noop(){}
/*
* class definition utility
*/
function extend(C,Super,methods){
function Class(){
}
Class.prototype = Super.prototype;
C.prototype = new Class();
if( !methods ) {
return;
}
var methodName;
for (methodName in methods) {
C.prototype[methodName] = methods[methodName];
}
}
function ResultsAggregator(){}
extend(ResultsAggregator, Object, {
setEachResult : noop
,returnResult : noop
,iteratorCall : noop
});
function EachResultsAggregator(){
}
extend(EachResultsAggregator, ResultsAggregator, {
returnResult : function returnResult(resultCallback) {
console || console.info('EachResultsAggregator#returnResult(this._results)',this._results);
(resultCallback || noop)();
}
,iteratorCall : function iteratorCall(params){
params.iterator.call(params.context, params.element, params.index, params.list, params.setResult, params.id);
}
});
function MapResultsAggregator(){
this._results = [];
}
extend(MapResultsAggregator, ResultsAggregator, {
setEachResult : function setEachResult(index,result){
this._results[index] = result;
}
,returnResult : function returnResult(resultCallback){
console || console.info('MapResultsAggregator#returnResult(this._results)',this._results);
(resultCallback || noop)(this._results);
}
,iteratorCall : function iteratorCall(params){
params.iterator.call(params.context, params.element, params.index, params.list, params.setResult, params.id);
}
});
function ReduceResultsAggregator(){
this._memo = undefined;
}
extend(ReduceResultsAggregator, ResultsAggregator, {
setEachResult : function setEachResult(index, result, element){
if (index === 0) {
// initialValue
this._memo = element;
} else {
this._memo = result;
}
}
,returnResult : function returnResult(resultCallback){
console || console.info('ReduceResultsAggregator#returnResult(this._memo)',this._memo);
(resultCallback || noop)(this._memo);
}
,iteratorCall :function iteratorCall(params){
params.iterator.call(params.context, this._memo, params.element, params.setResult, params.id);
}
});
function Wrapper(obj,opions){
this._wrapped = obj;
this._queue = [];
this._opions = opions;
}
(function() {
function Task(args,AggregatorClass){
this._iterator = _.toArray(args)[0];
this._class = AggregatorClass;
}
extend(Task, Object, {
createFn : function createFn(params){
var AggregatorClass = this._class;
params.iterator = this._iterator;
function __value_fn(result){
intervalIterateInternal.apply(null,[result,params,new AggregatorClass()]);
}
return __value_fn;
}
});
extend(Wrapper, Object, {
each : function each(){
var task = new Task(arguments,EachResultsAggregator);
this._queue.push(task);
return this;
}
,map : function map(){
var task = new Task(arguments,MapResultsAggregator);
this._queue.push(task);
return this;
}
,reduce : function reduce(){
var task = new Task(arguments,ReduceResultsAggregator);
this._queue.push(task);
return this;
}
,value : function value(resultCallback){
var tasks = this._queue.reverse();
var firstTask = tasks[tasks.length - 1];
//var chainId = _.uniqueId('chain_id_');
var options = this._opions;
var isTop = typeof options === 'object';
if ( !isTop ) {
var iteratorId = options;
options = optionsTable[iteratorId];
resultCallback = setResultTable[iteratorId];
}
tasks.unshift(resultCallback);
_(tasks).reduce(function(fn,task){
var isFirst = firstTask === task;
var params = {
isChained :true
,isFirst : isFirst
,isTop : isTop && isFirst
//,chainId : chainId
,options : options
};
params.resultCallback = fn;
return task.createFn(params);
})(this._wrapped);
}
});
})();
lib.intervalChain = intervalChain;
function intervalChain(obj,opions){
return new Wrapper(obj,opions);
}
lib.intervalEach = intervalEach;
function intervalEach(obj, iterator, resultCallback, options/* execTime,interval,context*/) {
var params = {};
var isTop = params.isTop = typeof resultCallback === 'function';
params.iterator = iterator;
if (isTop) {
params.options = options || {};
params.resultCallback = resultCallback;
} else {
var iteratorId = params.iteratorId = resultCallback;
params.options = _(optionsTable[iteratorId]).clone();
params.resultCallback = setResultTable[iteratorId];
}
intervalIterateInternal(obj,params,new EachResultsAggregator());
}
lib.intervalMap = intervalMap;
// Topレベルの場合(obj, iterator, resultCallback, options)
// 内側の場合(obj, iterator, iteratorId)
function intervalMap(obj, iterator, resultCallback, options/* execTime,interval,context*/) {
var params = {};
var isTop = params.isTop = typeof resultCallback === 'function';
params.iterator = iterator;
if (isTop) {
params.options = options || {};
params.resultCallback = resultCallback;
} else {
var iteratorId = params.iteratorId = resultCallback;
params.options = _(optionsTable[iteratorId]).clone();
params.resultCallback = setResultTable[iteratorId];
}
intervalIterateInternal(obj,params,new MapResultsAggregator());
}
lib.intervalReduce = intervalReduce;
function intervalReduce(obj, iterator, resultCallback, options/* execTime,interval,context*/) {
var params = {};
var isTop = params.isTop = typeof resultCallback === 'function';
params.iterator = iterator;
if (isTop) {
params.options = options || {};
params.resultCallback = resultCallback;
} else {
var iteratorId = params.iteratorId = resultCallback;
params.options = _(optionsTable[iteratorId]).clone();
params.resultCallback = setResultTable[iteratorId];
}
intervalIterateInternal(obj,params,new ReduceResultsAggregator());
}
function intervalIterateInternal(obj, params, resultAggregator) {
var isTop = params.isTop;
var iterator = params.iterator;
var resultCallback = params.resultCallback;
var options = params.options;
var execTime = options.execTime || 1000;
var interval = options.interval || 100;
var checkAllowExec;
if (isTop) {
checkAllowExec = createAllowExecChecker(execTime, interval);
options.__checkAllowExec = checkAllowExec;
} else {
checkAllowExec = options.__checkAllowExec;
}
var context = options.context;
var length = obj.length;
var i = 0;
var recursiveCallNum = 0;
function exec() {
var element = obj[i]
,index = i
,list = obj;
var id = _.uniqueId('iterator_id_');
// ToDo setResultが呼ばれた時点で再帰的な呼び出しから戻ってきていると言えるのでsetResultTable[id],optionsTable[id]を削除して良い
setResultTable[id] = setResult;
optionsTable[id] = options;
function setResult(result) {
resultAggregator.setEachResult(index, result, element);
i++;
console && console.log('intervalMap setResult (result,index,length,count)', result, index, length, i);
// last
if (length === i) {
resultAggregator.returnResult(resultCallback);
} else {
// If exec isn't allowed,wait by timer.
if (!checkAllowExec()) {
recursiveCallNum = 0;
checkAllowExec(exec);
return;
}
if (recursiveCallNum < 100) {
recursiveCallNum++;
exec();
} else {
recursiveCallNum = 0;
_.defer(exec);
}
}
}
// each iterator (element, index, list)
// map iterator (element, index, list)
// reduce iterator (memo, element)
resultAggregator.iteratorCall({
iterator:iterator
,context:context
,element:element
,index:index
,list:list
,setResult:setResult
,id:id
});
}
exec();
}
/*
* checkAllowExec関数を返す関数
* checkAllowExec関数は今処理を実行して
* いいならtrueを、ダメならfalseを返す
* 関数を渡した場合、実行していいなら即実行、ダメなら実行していい時間になったら実行されるようにtimerで投げる
* 処理を実行していい時間、ダメな時間はそれぞれexecTime,intervalTime ミリ秒交互
*/
function createAllowExecChecker(execTime,intervalTime) {
var id =_.uniqueId();
var supportNow = !!Date.now;
var startTime = supportNow ? Date.now() : new Date - 0;
function checkAllowExec(fn){
var timeDelta = (supportNow ? Date.now() : new Date - 0) - startTime;
var alowExec = (timeDelta % (execTime + intervalTime)) < execTime;
console && console.log('intervalMap checkAllowExec (id,execTime,intervalTime,timeDelta,alowExec)'
,id,execTime,intervalTime,timeDelta,alowExec);
if (!!fn) {
if (alowExec) {
fn();
} else {
_.delay(fn,(execTime + intervalTime) - (timeDelta % (execTime + intervalTime)));
}
return;
}
return alowExec;
}
return checkAllowExec;
}
_.mixin(lib);
})();
// }).call({}); // no console
function format(time){
return [time.getMinutes(),time.getSeconds(),time.getMilliseconds()].join(':');
}
function __wait(num) {
_.range(num).map(function ___wait() {
});
}
var array = _.range(500);
_(array).intervalEach(
function __iterator(n,i,list,setResult){
__wait(10000);
setResult(n * 2);
}
,function(results){
console.info('complete');
}
,{
execTime : 300
,interval : 1000
}
);
_(array).intervalMap(
function __iterator(n,i,list,setResult){
__wait(10000);
setResult(n * 2);
}
,function(results){
console.info(results);
}
,{
execTime : 300
,interval : 1000
}
);
_(array).intervalReduce(
function __iterator(memo,n,setResult){
__wait(10000);
setResult(memo+n);
}
,function(result){
console.info(result);
}
,{
execTime : 300
,interval : 1000
}
);
function format(time){
return [time.getMinutes(),time.getSeconds(),time.getMilliseconds()].join(':');
}
function __wait(num) {
_.range(num).map(function ___wait() {
});
}
var array = _.range(500);
_(array).intervalChain({
execTime : 300
,interval : 1000
}).map(function __iterator1(n,i,list,setResult){
__wait(10000);
setResult(n * 2);
}).map(function __iterator2(n,i,list,setResult){
__wait(10000);
setResult(n * 2);
}).reduce(function __iterator3(memo,n,setResult){
__wait(10000);
setResult(memo + n);
}).value(function(results){
console.info(results);
});
_(array).intervalChain({
execTime : 300
,interval : 1000
}).map(function __iterator1(n,i,list,setResult){
__wait(10000);
setResult(n * 2);
}).each(function __iterator2(n,i,list,setResult){
__wait(10000);
setResult(n * 2);
}).value(function(results){
console.info('complete');
});
function __wait(num) {
_.range(num).map(function ___wait() {
});
}
var nestedArray = _(_.range(5)).map(function(n){
return _(_.range(200)).map(function(n2){
return n*200+n2;
});
});
function format(time){
return [time.getMinutes(),time.getSeconds(),time.getMilliseconds()].join(':');
}
// ネストしたintervalMapを使えるようにはした
_(nestedArray).intervalMap(
function __outer_iterator(n,i,list,setResult,iteratorId){
_(n).intervalMap(function __inner_iterator(n,i,list,setResult,iteratorId){
__wait(10000);
setResult(n*2);
}
,iteratorId);
}
,function(results){
console.info(results);
}
,{
execTime : 300
,interval : 1000
}
);
function __wait(num) {
_.range(num).map(function ___wait() {
});
}
var nestedArray = _(_.range(5)).map(function(n){
return _(_.range(200)).map(function(n2){
return n*200+n2;
});
});
function format(time){
return [time.getMinutes(),time.getSeconds(),time.getMilliseconds()].join(':');
}
// nestした中でchainするのに対応
_(nestedArray).intervalMap(
function __outer_iterator(n,i,list,setResult,iteratorId){
_(n).intervalChain(iteratorId)
.map(function __iterator1(n,i,list,setResult){
__wait(10000);
setResult(n * 2);
}).map(function __iterator2(n,i,list,setResult){
__wait(10000);
setResult(n * 2);
}).value();
}
,function(results){
console.info(results);
}
,{
execTime : 300
,interval : 1000
}
);
function format(time){
return [time.getMinutes(),time.getSeconds(),time.getMilliseconds()].join(':');
}
var array = _.range(5000);
// 179ms/5000
_(array).intervalMap(
function __iterator(n,i,list,setResult){
var start = (new Date);
// wait
// 635ms/814ms
(function __wait() {
_.range(100).map(function ___wait() {
});
})();
var end = (new Date);
// console.debug(n, end - start, format(start), format(end));
setResult(n * 2);
}
,function(results){
console.info(results);
}
,{
execTime : 300
,interval : 1000
}
);
function format(time){
return [time.getMinutes(),time.getSeconds(),time.getMilliseconds()].join(':');
}
var array = _.range(5000);
// 29ms/5000
_(array).map(
function __iterator(n,i,list,setResult){
var start = (new Date);
// wait
// 616ms/645ms
(function __wait() {
_.range(100).map(function ___wait() {
});
})();
var end = (new Date);
// console.debug(n, end - start, format(start), format(end));
return n * 2;
});
@monjudoh
Copy link
Author

monjudoh commented Feb 9, 2011

一応非同期対応しネストしたintervalMapを使えるようにはした。
残課題多し
・ネストしたsetResult(result)を自力で呼んでやらないといけない
・非同期の場合そもそもたくさんの処理を休み休みやるというのが実現出来ていない

@monjudoh
Copy link
Author

ネストしたsetResult(result)を自動で解決するようにした
今処理を実行していいならtrueを、ダメならfalseを返す関数を返す関数を使い、非同期でも休み休み実行出来るようにした。

@monjudoh
Copy link
Author

ネスト非対応版のコードを削除

@monjudoh
Copy link
Author

outerOptionsを晒さずにnestさせるようにした

@monjudoh
Copy link
Author

chain対応について考えていなかった

@monjudoh
Copy link
Author

intervalChain(),intervalValue()メソッドをつくろう

@monjudoh
Copy link
Author

nestとchainの組み合わせに対応

@monjudoh
Copy link
Author

intervalEachを実装した。課題はeachの中断等。

@monjudoh
Copy link
Author

何であるか
→長時間かかる反復メソッド(each,map,reduce等)の実行をtimerで飛ばしながら休み休み行うUnderscore.js用mixin

_.each,map,reduceとAPI的に変えざるをえない所
→どう休み休み実行するか(連続実行時間、休む時間)をoptionsとして渡す必要がある
→全件分の実行が完了するまでに休みが入る可能性があるので非同期処理になる可能性がある。つまり結果をreturnできないので、結果を渡すcallbackが必要になる。

二つのAPI
・_に生やしたメソッドintervalEach,intervalMap,intervalReduce。
_(array).intervalEachなどして使える。
options,resultCallbackを各interval* メソッドの引数として渡さないといけないので、引数が多くなって分かりにくい。

使用例:z.1.demo-interval.js等

・_.intervalChainメソッドによるメソッドチェーン版
_(array)
.intervalChain(options)
.map(iteratorFn)
.reduce(iteratorFn)
.value(resultCallback);
以上のように使う。
optionsはintervalChainメソッドの引数、resultCallbackはvalueメソッドの引数になっているため、各反復メソッドの引数は繰り返し実行する対象の関数のみ
反復メソッドの所属自体は_ではないので、_に生えている通常の反復メソッドと同じメソッド名になっている
反復メソッドをチェーンさせるつもりがなくても最低限intervalChain,valueで挟むためメソッドチェーンを書く必要がある

使用例:z.2.demo-interval-chained.js等

この2系統APIのうち、_の各interval* メソッドを廃止して、メソッドチェーン版のみ残そうかと思っているのですが、
JavaScripterの皆様にご意見を伺いたいところ

@Constellation
Copy link

jquery deferredなどをつかえばsetResultとかを消せるかなーとか思ったので一から書いてみました.

https://gist.github.com/844020

ご期待に添えればなのですが...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment