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

当前回答

要在ES6中简单地做到这一点:

function(files) {
  // Create a new empty promise (don't do that with real people ;)
  var sequence = Promise.resolve();

  // Loop over each file, and add on a promise to the
  // end of the 'sequence' promise.
  files.forEach(file => {

    // Chain one computation onto the sequence
    sequence = 
      sequence
        .then(() => performComputation(file))
        .then(result => doSomething(result)); 
        // Resolves for each file, one at a time.

  })

  // This will resolve after the entire chain is resolved
  return sequence;
}

其他回答

有一个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

我的答案基于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
});

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

使用Async/Await(如果你有ES7的支持)

function downloadFile(fileUrl) { ... } // This function return a Promise

async function main()
{
  var filesList = [...];

  for (const file of filesList) {
    await downloadFile(file);
  }
}

(你必须使用for循环,而不是forEach,因为async/await在forEach循环中运行有问题)

没有Async/Await(使用Promise)

function downloadFile(fileUrl) { ... } // This function return a Promise

function downloadRecursion(filesList, index)
{
  index = index || 0;
  if (index < filesList.length)
  {
    downloadFile(filesList[index]).then(function()
    {
      index++;
      downloadRecursion(filesList, index); // self invocation - recursion!
    });
  }
  else
  {
    return Promise.resolve();
  }
}

function main()
{
  var filesList = [...];
  downloadRecursion(filesList);
}

下面是我的Angular/TypeScript方法,使用RxJS:

给定一个URL字符串数组,使用from函数将其转换为一个可观察对象。 使用管道包装Ajax请求、即时响应逻辑、任何所需的延迟和错误处理。 在管道内部,使用concatMap序列化请求。否则,使用Javascript forEach或map会同时发出请求。 使用RxJS ajax进行调用,并在每次调用返回后添加所需的延迟。

工作示例:https://stackblitz.com/edit/rxjs-bnrkix?file=index.ts

代码看起来像这样(我留下了一些额外的内容,这样你可以选择保留或丢弃什么):

import { ajax } from 'rxjs/ajax';
import { catchError, concatMap, delay, from, of, map, Observable } from 'rxjs';

const urls = [
  'https://randomuser.me/api/',
  'https://randomuser.me/api/',
  'https://randomuser.me/api/',
];
const delayAfterCall = 500;

from(urls)
  .pipe(
    concatMap((url: string) => {
      return ajax.getJSON(url).pipe(
        map((response) => {
          console.log('Done! Received:', response);
          return response;
        }),
        catchError((error) => {
          console.error('Error: ', error);
          return of(error);
        }),
        delay(delayAfterCall)
      );
    })
  )
  .subscribe((response) => {
    console.log('received email:', response.results[0].email);
  });