7

今天碰到一个需要用Promise做无穷循环then的一个案例,顿时脑洞大开。
事情是这样的,有这样的一群异步函数,

var func1 = function(callback){
    setTimeout(function(){
      console.log('foo');
      typeof(callback) !== 'function' || callback();
    }, 499);
};

var func2 = function(callback){
    setTimeout(function(){
      console.log('bar');
      typeof(callback) !== 'function' || callback();
    }, 500);
};

var func3 = function(callback){
    setTimeout(function(){
      console.log('foobar');
      typeof(callback) !== 'function' || callback();
    }, 501);
};
// ... more ...

将它们封装成Promise,依次放入一个数组内:

// promisify those callback functions
var promisify = function(func){
  return function(){
    return new Promise(function(resolve){
      func(resolve);
    });
  }
}

// array can be infinitely long
var func_arr = [promisify(func1), promisify(func2), promisify(func3)];

需要让数组里的每一个Promise依次进行,最后一个执行完就结束。
我的第一个想法是这样的:

// solution 1 failed
var master = [];

for (var i = 0; i < func_arr.length; i++) {
  master[i] = function(){
    if (i == 0) {
      return func_arr[i]();
    }
    else {
      return master[i-1]().then(function(){
        return func_arr[i]();
      })
    }
  };
};

master[master.length-1]();

乍看没有问题啊,每一个新的master子函数的值是它上一个子函数的值加上一个then,但是一直报错——Maximum call stack size exceeded (node) / too much recursion (firefox)。
(12/24圣诞夜更新:已解决,修改了部分代码见下文)

僵了一会,还是谷哥帮助了我,搜到一个非常简洁的方法:

// solution 2 // success
func_arr.reduce(function(cur, next) {
    return cur.then(next);
}, Promise.resolve()).then(function() {
    console.log('job finished');
});

看得我腿都拍出坑了,其实是我想复杂了,没有必要在循环里不断地返回闭包函数等到最后调用,可以直接在循环里调用。
于是心有不甘,reduce()能实现的我大for岂有实现不了的道理,实则也不难,正确方法如下:

// solution 3 // success
var master = [];
master[0] = func_arr[0]();
for (var i = 1; i < func_arr.length; i++) {
  master[i] = master[i-1].then(func_arr[i]);
};

以下再提供一种重复调用函数式(就是递归)的方法:

// solution 4 // success
function loop(i) {
  if (i != func_arr.length) {
    return func_arr[i]()
    .then(function() {
      return loop(i+1);
    });
  }
  return Promise.resolve(i);
}

loop(0).then(function(loop_times){
  console.log('job finished');
});

不知道大家还有没有更好的解决方案,或者能告知我第一个方案出错的原因吧

================

12/24更新

感谢wowhy的提示,将我的solution 1的代码加入一层闭包,已经解决了因内外函数变量产生的BUG:

// solution 1 // success now
var master = [];

for (var i = 0; i < func_arr.length; i++) {
  master[i] = (function(j){
    return function(){
      if (j == 0) {
        return func_arr[j]();
      }
      else {
        return master[j-1]().then(function(){
          return func_arr[j]();
        })
      }
    }
  })(i);
};

var execute = master[master.length-1];
execute();

乍看这一串代码很长,对比之前的方案显得完全多余,在我看来,可以生成在外部其他的上下文内进行独立调用的函数要更符合我大脑的思维方式:)。


tomzhu
120 声望5 粉丝

不抽象会死星人