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関数が邪魔ですが...。