考虑下面以串行/顺序方式读取文件数组的代码。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);
  }));
};

当前回答

我发现自己多次回到这个问题上,答案并没有给我我想要的,所以把这个放在这里给任何需要这个问题的人。

下面的代码执行顺序承诺执行(一个接一个),每一轮由多个调用组成:

async function sequence(list, cb) {
  const result = [];
  await list.reduce(async (promise, item) => promise
    .then(() => cb(item))
    .then((res) => result.push(res)
  ), Promise.resolve());
  return result;
}

展示:

<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.3/axios.min.js"></script> <script src="https://unpkg.com/@babel/standalone@7/babel.min.js"></script> <script type="text/babel"> function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function readFile(url, index) { console.log('Running index: ', index); // First action const firstTime = await axios.get(url); console.log('First API response: ', firstTime.data.activity); // Second action await sleep(1000); // Third action const secondTime = await axios.get(url); console.log('Second API response: ', secondTime.data.activity); // Fourth action await sleep(1000); return secondTime.data; } async function sequence(urls, fn) { const result = []; await urls.reduce(async (promise, url, index) => promise.then(() => fn(url, index)).then((res) => result.push(res)), Promise.resolve()); return result; } const urls = [ 'https://www.boredapi.com/api/activity', 'https://www.boredapi.com/api/activity', 'https://www.boredapi.com/api/activity', ]; (async function init() { const result = await sequence(urls, readFile); console.log('result', result); })() </script>

其他回答

(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数组。

我的答案基于https://stackoverflow.com/a/31070150/7542429。

Promise.series = function series(arrayOfPromises) {
    var results = [];
    return arrayOfPromises.reduce(function(seriesPromise, promise) {
      return seriesPromise.then(function() {
        return promise
        .then(function(result) {
          results.push(result);
        });
      });
    }, Promise.resolve())
    .then(function() {
      return results;
    });
  };

该解决方案以Promise.all()等数组的形式返回结果。

用法:

Promise.series([array of promises])
.then(function(results) { 
  // do stuff with results here
});

这里有很多答案,但我没有看到这个简单的解决方案:

await array.reduce(
  async (promise, member) => await myLongSequentialPromise(member),
  array[0]
)

证明:https://jsbin.com/nulafus/1/edit?js,控制台

我能想到的最好的解决办法就是蓝鸟承诺。你可以执行Promise.resolve(files).each(fs.readFileAsync);这保证了承诺是按顺序被解决的。

我使用以下代码扩展Promise对象。它处理承诺的拒绝并返回一个结果数组

Code

/*
    Runs tasks in sequence and resolves a promise upon finish

    tasks: an array of functions that return a promise upon call.
    parameters: an array of arrays corresponding to the parameters to be passed on each function call.
    context: Object to use as context to call each function. (The 'this' keyword that may be used inside the function definition)
*/
Promise.sequence = function(tasks, parameters = [], context = null) {
    return new Promise((resolve, reject)=>{

        var nextTask = tasks.splice(0,1)[0].apply(context, parameters[0]); //Dequeue and call the first task
        var output = new Array(tasks.length + 1);
        var errorFlag = false;

        tasks.forEach((task, index) => {
            nextTask = nextTask.then(r => {
                output[index] = r;
                return task.apply(context, parameters[index+1]);
            }, e=>{
                output[index] = e;
                errorFlag = true;
                return task.apply(context, parameters[index+1]);
            });
        });

        // Last task
        nextTask.then(r=>{
            output[output.length - 1] = r;
            if (errorFlag) reject(output); else resolve(output);
        })
        .catch(e=>{
            output[output.length - 1] = e;
            reject(output);
        });
    });
};

例子

function functionThatReturnsAPromise(n) {
    return new Promise((resolve, reject)=>{
        //Emulating real life delays, like a web request
        setTimeout(()=>{
            resolve(n);
        }, 1000);
    });
}

var arrayOfArguments = [['a'],['b'],['c'],['d']];
var arrayOfFunctions = (new Array(4)).fill(functionThatReturnsAPromise);


Promise.sequence(arrayOfFunctions, arrayOfArguments)
.then(console.log)
.catch(console.error);