考虑下面以串行/顺序方式读取文件数组的代码。readFiles返回一个承诺,只有在顺序读取所有文件后才会解析这个承诺。

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  return new Promise((resolve, reject) => {
    var readSequential = function(index) {
      if (index >= files.length) {
        resolve();
      } else {
        readFile(files[index]).then(function() {
          readSequential(index + 1);
        }).catch(reject);
      }
    };

    readSequential(0); // Start with the first file!
  });
};

上面的代码可以工作,但是我不喜欢为了使事情按顺序发生而进行递归。是否有一种更简单的方法可以重写这段代码,这样我就不必使用奇怪的readSequential函数了?

最初我尝试使用Promise。但是这会导致所有的readFile调用并发发生,这不是我想要的:

var readFiles = function(files) {
  return Promise.all(files.map(function(file) {
    return readFile(file);
  }));
};

当前回答

有一个npm包Promise Serial可以很好地做到这一点:

const Promise_serial = require('promise-serial');

 const promises =
    Array(15).fill()
    .map((_, i) =>
        () => new Promise(resolve => {
            console.log('promise '+i+' start');
            setTimeout(
                () => {
                    console.log('promise '+i+' end');
                    resolve('output-'+i);
                },
                500
            );
        })
    );


console.log('### Run promises in sequence')

Promise_serial(promises)

输出:

promise 0 start
promise 0 end
promise 1 start
promise 1 end
promise 2 start
promise 2 end
promise 3 start
promise 3 end
promise 4 start
promise 4 end
promise 5 start
promise 5 end
promise 6 start
promise 6 end
promise 7 start
promise 7 end

... etc

您还可以批处理或并行化它们。

参见:https://www.npmjs.com/package/promise-serial

其他回答

(function() {
  function sleep(ms) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        return resolve();
      }, ms);
    });
  }

  function serial(arr, index, results) {
    if (index == arr.length) {
      return Promise.resolve(results);
    }
    return new Promise(function(resolve, reject) {
      if (!index) {
        index = 0;
        results = [];
      }
      return arr[index]()
        .then(function(d) {
          return resolve(d);
        })
        .catch(function(err) {
          return reject(err);
        });
    })
      .then(function(result) {
        console.log("here");
        results.push(result);
        return serial(arr, index + 1, results);
      })
      .catch(function(err) {
        throw err;
      });
  }

  const a = [5000, 5000, 5000];

  serial(a.map(x => () => sleep(x)));
})();

这里的关键是如何调用sleep函数。你需要传递一个函数数组,它本身返回一个promise,而不是一个promise数组。

您的方法还不错,但它确实有两个问题:它会吞下错误,并且使用显式承诺构造反模式。

你可以解决这两个问题,并使代码更干净,同时仍然采用相同的一般策略:

var Q = require("q");

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  var readSequential = function(index) {
    if (index < files.length) {
      return readFile(files[index]).then(function() {
        return readSequential(index + 1);
      });
    }
  };

  // using Promise.resolve() here in case files.length is 0
  return Promise.resolve(readSequential(0)); // Start!
};

我不得不运行大量的顺序任务,并使用这些答案来伪造一个函数,将照顾处理任何顺序任务…

function one_by_one(objects_array, iterator, callback) {
    var start_promise = objects_array.reduce(function (prom, object) {
        return prom.then(function () {
            return iterator(object);
        });
    }, Promise.resolve()); // initial
    if(callback){
        start_promise.then(callback);
    }else{
        return start_promise;
    }
}

该函数接受2个参数+ 1个可选参数。第一个参数是我们将要处理的数组。第二个参数是任务本身,一个返回承诺的函数,只有当这个承诺解决时,下一个任务才会开始。第三个参数是在所有任务完成后运行的回调。如果没有传递回调,则函数返回它创建的promise,以便我们可以处理结束。

下面是一个用法示例:

var filenames = ['1.jpg','2.jpg','3.jpg'];
var resize_task = function(filename){
    //return promise of async resizing with filename
};
one_by_one(filenames,resize_task );

希望它能节省一些时间…

下面是我比较喜欢的连续运行任务的方式。

function runSerial() {
    var that = this;
    // task1 is a function that returns a promise (and immediately starts executing)
    // task2 is a function that returns a promise (and immediately starts executing)
    return Promise.resolve()
        .then(function() {
            return that.task1();
        })
        .then(function() {
            return that.task2();
        })
        .then(function() {
            console.log(" ---- done ----");
        });
}

有更多任务的情况会怎样呢?10 ?

function runSerial(tasks) {
  var result = Promise.resolve();
  tasks.forEach(task => {
    result = result.then(() => task());
  });
  return result;
}

这是对如何以更通用的方式处理承诺序列的扩展,支持基于spex的动态/无限序列。序列实现:

var $q = require("q");
var spex = require('spex')($q);

var files = []; // any dynamic source of files;

var readFile = function (file) {
    // returns a promise;
};

function source(index) {
    if (index < files.length) {
        return readFile(files[index]);
    }
}

function dest(index, data) {
    // data = resolved data from readFile;
}

spex.sequence(source, dest)
    .then(function (data) {
        // finished the sequence;
    })
    .catch(function (error) {
        // error;
    });

这种解决方案不仅适用于任何大小的序列,而且还可以轻松地向其添加数据节流和负载平衡。