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

当前回答

下面是我的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);
  });

其他回答

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

2017年更新:如果环境支持,我会使用异步函数:

async function readFiles(files) {
  for(const file of files) {
    await readFile(file);
  }
};

如果你愿意,你可以延迟读取文件,直到你需要使用异步生成器(如果你的环境支持它):

async function* readFiles(files) {
  for(const file of files) {
    yield await readFile(file);
  }
};

更新:在第二个想法-我可能会用一个for循环代替:

var readFiles = function(files) {
  var p = Promise.resolve(); // Q() in q

  files.forEach(file =>
      p = p.then(() => readFile(file)); 
  );
  return p;
};

或者更确切地说,用reduce:

var readFiles = function(files) {
  return files.reduce((p, file) => {
     return p.then(() => readFile(file));
  }, Promise.resolve()); // initial
};

在其他承诺库(如when和Bluebird)中,您有用于此的实用程序方法。

例如,蓝鸟将是:

var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));

var readAll = Promise.resolve(files).map(fs.readFileAsync,{concurrency: 1 });
// if the order matters, you can use Promise.each instead and omit concurrency param

readAll.then(function(allFileContents){
    // do stuff to read files.
});

尽管现在没有理由不使用async await。

另外的例子

const addTwo = async () => 2; const addThree = async (inValue) => new Promise((resolve) => setTimeout(resolve(inValue + 3), 2000)); const addFour = (inValue) => new Promise((res) => setTimeout(res(inValue + 4), 1000)); const addFive = async (inValue) => inValue + 5; // Function which handles promises from above async function sequenceAddition() { let sum = await [addTwo, addThree, addFour, addFive].reduce( (promise, currPromise) => promise.then((val) => currPromise(val)), Promise.resolve() ); console.log('sum:', sum); // 2 + 3 + 4 + 5 = 14 } // Run function. See console for result. sequenceAddition();

使用reduce()的通用语法

function sequence(tasks, fn) {
    return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}

更新

items-promise是一个准备使用NPM包做同样的事情。

我想重复很多人说过的,解决这个问题的最好方法是使用async/await函数。我想重申这个解决方案,但是,也指出另一个解决方案,其中async/await不存在:

函数readFile(文件){ 返回新的Promise(函数(解析,拒绝){ console.log('模拟读取文件${file} '); setTimeout(决心,1000); }); } (async函数(){ Let files = ["file1.txt", "file2.txt", "file3.txt"]; For (let file of files) 等待readFile(文件); }) ();

对于不支持async/await的JavaScript环境,我们可以选择使用https://babeljs.io来编译上面的内容,但要使用babel-plugin-transform-async-to-generator之类的插件。下面是使用插件的v6.24.1生成的:

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } function readFile(file) { return new Promise(function (resolve, reject) { console.log(`Simulate reading of file ${file}`); setTimeout(resolve, 1000); }); } _asyncToGenerator(function* () { let files = ["file1.txt", "file2.txt", "file3.txt"]; for (let file of files) yield readFile(file); })();

如果你研究一下_asyncToGenerator()中发生了什么,你会发现它正在使用递归使用Promise链。您可以通过函数生成器提供Promise,因此,您可以专注于业务逻辑。