javascriptで非同期処理をfor文で回したい。
- 普通に書くと
setTimeout( () =>{console.log('a');},4000); setTimeout( () =>{console.log('b');},3000); setTimeout( () =>{console.log('c');},2000); setTimeout( () =>{console.log('d');},1000);
非同期なので出力はd,c,b,aになる。
- 同期的に書くと例によってコールバック地獄になる。
setTimeout( () =>{ console.log('a'); setTimeout( () =>{ console.log('b'); setTimeout( () =>{ console.log('c'); setTimeout( () =>{ console.log('d'); },1000) },2000) },3000) },4000)
- 非同期の場合コールバックの部分をfor文にすると
var arr = ['a','b','c','d']; for(var i=0;i<arr.length;i++){ setTimeout( function(x){ console.log(x);}.bind(null,arr[i]),(4-i)*1000); }
- 同期の場合はfor文でなく再帰になってしまう。
var arr = ['a','b','c','d']; setTimeout( function loop(arr){ console.log(arr.shift()); if(arr.length!=0){ setTimeout(loop.bind(null,arr),arr.length*1000) } }.bind(null,arr),arr.length*1000);
- これをjQueryの$.Defferedを使って書き直すと
var arr = ['a','b','c','d']; var d = new $.Deferred(); d.resolve(arr); for(var i=0;i<arr.length;i++){ d = d.then(function(arr) { var d = new $.Deferred(); setTimeout(function(arr){ console.log(arr.shift()); d.resolve(arr); }.bind(null,arr) ,arr.length*1000) return d.promise(); }) }
for文で書けるが、これだと再帰とあんまり変わらないかも
- ES6 Promiseの場合
(function(){ var arr = ['a','b','c','d']; var d = Promise.resolve(); for(var i=0;i<arr.length;i++){ d = d.then( function(i){ return new Promise((resolve,reject)=>{ setTimeout(()=>{ console.log(arr[i]); resolve(); } ,(4-i)*1000) }) }.bind(null,i)) } })();
たかだかsetTimeoutでconsole.logを出力するだけの同期処置がなんでこんなに大変になるんだろう....。
ちなみに、コールバック関数に引数を渡すときはbindする必要があるので注意すること。ここ重要
そのまま普通に引数を渡すと、渡した値を引数に即時実行される。
- 間違い
function(arr){ console.log(arr.shift()); resolve(arr); }(arr)
- 正しい
function(arr){ console.log(arr.shift()); resolve(arr); }.bind(null,arr)
また、アロー関数はbindできないんで、普通の匿名関数function(){}使ってください。
- これはできない
arr => { console.log(arr.shift()); resolve(arr); }.bind(null,arr)
とかできません。
アロー関数はbindできないというか、thisも含めて
外のブロックがbindされるため引数にarrを渡しちゃダメだった。
下のように関数の引数arrを定義しなければオッケー
() => { console.log(arr.shift()); resolve(arr); }
でも、ちゃんと値が渡らないことがあるので、その場合は通常の匿名関数function()でbindした方がいい。
アロー関数のスコープがイマイチ分からん。
- そしてgenerator Promise版
function co(g){ var p = g.next(); if(p.done) return; p.value.then(()=>{ co(g); }); } var arr = ['a','b','c','d']; co(function* gen(arr){ for(var i=0;i<arr.length;i++){ yield new Promise( resolve =>{ setTimeout(resolve,(4-i)*1000); }); console.log(arr[i]); } }(arr))
ちゃんとfor文になっている感じがします。
ちょっとco関数が邪魔ですが...。