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

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。


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


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

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;
}

我在Promise对象上创建了这个简单的方法:

创建并添加承诺。sequence方法添加到Promise对象

Promise.sequence = function (chain) {
    var results = [];
    var entries = chain;
    if (entries.entries) entries = entries.entries();
    return new Promise(function (yes, no) {
        var next = function () {
            var entry = entries.next();
            if(entry.done) yes(results);
            else {
                results.push(entry.value[1]().then(next, function() { no(results); } ));
            }
        };
        next();
    });
};

用法:

var todo = [];

todo.push(firstPromise);
if (someCriterium) todo.push(optionalPromise);
todo.push(lastPromise);

// Invoking them
Promise.sequence(todo)
    .then(function(results) {}, function(results) {});

Promise对象的这个扩展最好的一点是,它与Promise的风格一致。的承诺。一切和承诺。序列以同样的方式调用,但具有不同的语义。

谨慎

连续运行承诺通常不是使用承诺的好方法。通常使用Promise会更好。所有这些,并让浏览器尽可能快地运行代码。然而,它也有一些实际的用例——例如在使用javascript编写移动应用程序时。


根据问题的标题“一个接一个地(即顺序地)解决承诺?”,我们可以理解为OP更感兴趣的是对结算承诺的顺序处理,而不是顺序调用本身。

给出的答案是:

演示顺序调用对于响应的顺序处理是不必要的。 向本页的访问者展示可行的替代模式——包括OP,如果他在一年后仍然感兴趣的话。 尽管OP断言他不想同时拨打电话,这可能是真的,但同样也可能是基于标题所暗示的对连续处理响应的渴望的假设。

如果并发调用真的不需要,那么请参阅Benjamin Gruenbaum的回答,其中全面涵盖了顺序调用(等等)。

但是,如果您对允许并发调用然后依次处理响应的模式感兴趣(为了提高性能),那么请继续阅读。

你很容易认为你必须使用Promise.all(arr.map(fn)).then(fn)(我已经做过很多次了)或Promise库的花哨糖(尤其是Bluebird的),然而(这篇文章的功劳)arr.map(fn).reduce(fn)模式将完成这项工作,其优点是:

适用于任何承诺库——甚至是预兼容版本的jQuery——只使用.then()。 提供了灵活性,跳过错误或停止错误,无论你想用一行mod。

这就是,为Q写的。

var readFiles = function(files) {
    return files.map(readFile) //Make calls in parallel.
    .reduce(function(sequence, filePromise) {
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//skip-over-error. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

注意:只有一个片段Q()是特定于Q的。对于jQuery,您需要确保readFile()返回jQuery承诺。有了A+,国外的承诺就会被同化。

这里的关键是还原的序列承诺,它对readFile承诺的处理进行排序,而不是对它们的创建进行排序。

一旦你理解了这一点,当你意识到.map()阶段实际上是不必要的时,可能会有点令人兴奋!整个工作,并行调用加上正确顺序的串行处理,可以通过reduce()单独实现,再加上进一步灵活的额外优势:

通过简单地移动一行,从并行异步调用转换为串行异步调用-在开发过程中可能有用。

这是Q。

var readFiles = function(files) {
    return files.reduce(function(sequence, f) {
        var filePromise = readFile(f);//Make calls in parallel. To call sequentially, move this line down one.
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//Skip over any errors. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

这是基本模式。如果您还想向调用者交付数据(例如文件或它们的一些转换),则需要一个温和的变体。


这是对如何以更通用的方式处理承诺序列的扩展,支持基于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;
    });

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


要在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;
}

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

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 );

希望它能节省一些时间…


这个问题很老了,但我们生活在ES6和函数式JavaScript的世界里,所以让我们看看如何改进。

因为承诺会立即执行,所以我们不能只创建一个承诺数组,它们会同时发射。

相反,我们需要创建一个返回承诺的函数数组。然后依次执行每个函数,然后在其中启动promise。

我们可以用几种方法来解决这个问题,但我最喜欢的方法是reduce。

把“减少”和“承诺”结合起来使用有点棘手,所以我把这句话分成了几个易于理解的小部分。

这个函数的本质是使用以promise .resolve([])或包含空数组的promise开头的reduce。

然后,这个承诺将作为承诺传递给reduce方法。这是将每个承诺按顺序连接在一起的关键。要执行的下一个promise是func,当then触发时,结果被连接起来,然后返回该promise,使用下一个promise函数执行reduce循环。

一旦执行了所有的promise,返回的promise将包含每个promise的所有结果的数组。

ES6示例(一行)

/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs =>
    funcs.reduce((promise, func) =>
        promise.then(result => func().then(Array.prototype.concat.bind(result))), Promise.resolve([]))

ES6示例(分解)

// broken down to for easier understanding

const concat = list => Array.prototype.concat.bind(list)
const promiseConcat = f => x => f().then(concat(x))
const promiseReduce = (acc, x) => acc.then(promiseConcat(x))
/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs => funcs.reduce(promiseReduce, Promise.resolve([]))

用法:

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const funcs = urls.map(url => () => $.ajax(url))

// execute them serially
serial(funcs)
    .then(console.log.bind(console))

你可以使用这个函数来获取promiseFactories List:

function executeSequentially(promiseFactories) {
    var result = Promise.resolve();
    promiseFactories.forEach(function (promiseFactory) {
        result = result.then(promiseFactory);
    });
    return result;
}

Promise Factory是一个简单的函数,返回一个Promise:

function myPromiseFactory() {
    return somethingThatCreatesAPromise();
}

它之所以有效,是因为承诺工厂直到被要求才创建承诺。它的工作方式与then函数相同——事实上,它是一样的!

你根本不想在一组承诺上操作。根据Promise规范,一旦创建了Promise,它就开始执行。所以你真正想要的是一系列的承诺工厂…

如果你想了解更多的承诺,你应该检查这个链接: https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html


我使用以下代码扩展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);

如果你愿意,你可以用reduce来做出一个连续的承诺,例如:

[2,3,4,5,6,7,8,9].reduce((promises, page) => {
    return promises.then((page) => {
        console.log(page);
        return Promise.resolve(page+1);
    });
  }, Promise.resolve(1));

它总是按顺序工作。


另外的例子

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包做同样的事情。


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

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

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!
};

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

这是上面另一个答案的轻微变化。使用原生承诺:

function inSequence(tasks) {
    return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}

解释

如果你有这些任务[t1, t2, t3],那么上面的等价于Promise.resolve().then(t1).then(t2).then(t3)。这是约简的行为。

如何使用

首先你需要构造一个任务列表!任务是不接受实参的函数。如果需要将参数传递给函数,则使用bind或其他方法创建任务。例如:

var tasks = files.map(file => processFile.bind(null, file))
inSequence(tasks).then(...)

我喜欢的解决方案:

function processArray(arr, fn) {
    return arr.reduce(
        (p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
        Promise.resolve([])
    );
}

它与这里发表的其他文章没有本质区别,但是:

将函数应用于序列中的项 解析为结果数组 不需要async/await(支持仍然非常有限,大约在2017年) 使用箭头函数;非常简洁

使用示例:

const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));

// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);

在当前合理的Chrome (v59)和NodeJS (v8.1.2)上测试。


我真的很喜欢@joelnet的回答,但对我来说,这种编码风格有点难以消化,所以我花了几天时间试图弄清楚如何以更可读的方式表达相同的解决方案,这就是我的想法,只是使用了不同的语法和一些注释。

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const functions = urls.map((url) => {
  // For every url we return a new function
  return () => {
    return new Promise((resolve) => {
      // random wait in milliseconds
      const randomWait = parseInt((Math.random() * 1000),10)
      console.log('waiting to resolve in ms', randomWait)
      setTimeout(()=>resolve({randomWait, url}),randomWait)
    })
  }
})


const promiseReduce = (acc, next) => {
  // we wait for the accumulator to resolve it's promise
  return acc.then((accResult) => {
    // and then we return a new promise that will become
    // the new value for the accumulator
    return next().then((nextResult) => {
      // that eventually will resolve to a new array containing
      // the value of the two promises
      return accResult.concat(nextResult)
    })
  })
};
// the accumulator will always be a promise that resolves to an array
const accumulator = Promise.resolve([])

// we call reduce with the reduce function and the accumulator initial value
functions.reduce(promiseReduce, accumulator)
  .then((result) => {
    // let's display the final value here
    console.log('=== The final result ===')
    console.log(result)
  })

使用Array.prototype。Reduce,并记住将承诺包装在函数中,否则它们将已经在运行!

// array of Promise providers

const providers = [
  function(){
     return Promise.resolve(1);
  },
  function(){
     return Promise.resolve(2);
  },
  function(){
     return Promise.resolve(3);
  }
]


const inSeries = function(providers){

  const seed = Promise.resolve(null); 

  return providers.reduce(function(a,b){
      return a.then(b);
  }, seed);
};

很好很简单…… 您应该能够为性能等重新使用相同的种子。

在使用reduce时,防止空数组或只有一个元素的数组是很重要的,所以这个技术是你最好的选择:

   const providers = [
      function(v){
         return Promise.resolve(v+1);
      },
      function(v){
         return Promise.resolve(v+2);
      },
      function(v){
         return Promise.resolve(v+3);
      }
    ]

    const inSeries = function(providers, initialVal){

        if(providers.length < 1){
            return Promise.resolve(null)
        }

        return providers.reduce((a,b) => a.then(b), providers.shift()(initialVal));
    };

然后像这样叫它:

inSeries(providers, 1).then(v => {
   console.log(v);  // 7
});

如果其他人在执行CRUD操作时需要一种有保证的严格顺序的方法来解析promise,您也可以使用以下代码作为基础。

只要你在调用每个函数之前添加'return',描述一个Promise,并使用这个例子作为基础,下一个.then()函数调用将在前一个函数完成后consistent启动:

getRidOfOlderShoutsPromise = () => {
    return readShoutsPromise('BEFORE')
    .then(() => {
        return deleteOlderShoutsPromise();
    })
    .then(() => {
        return readShoutsPromise('AFTER')
    })
    .catch(err => console.log(err.message));
}

deleteOlderShoutsPromise = () => {
    return new Promise ( (resolve, reject) => {
        console.log("in deleteOlderShouts");
        let d = new Date();
        let TwoMinuteAgo = d - 1000 * 90 ;
        All_Shouts.deleteMany({ dateTime: {$lt: TwoMinuteAgo}}, function(err) {
            if (err) reject();
            console.log("DELETED OLDs at "+d);
            resolve();        
        });
    });
}

readShoutsPromise = (tex) => {
    return new Promise( (resolve, reject) => {
        console.log("in readShoutsPromise -"+tex);
        All_Shouts
        .find({})
        .sort([['dateTime', 'ascending']])
        .exec(function (err, data){
            if (err) reject();
            let d = new Date();
            console.log("shouts "+tex+" delete PROMISE = "+data.length +"; date ="+d);
            resolve(data);
        });    
    });
}

使用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);
}

正如Bergi所注意到的,我认为最好的和明确的解决方案是使用蓝鸟。每个,代码如下:

const BlueBird = require('bluebird');
BlueBird.each(files, fs.readFileAsync);

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

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

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


数组push和pop方法可用于承诺序列。当你需要额外的数据时,你也可以推出新的承诺。这是代码,我将使用在React无限加载器加载页面序列。

var promises = [Promise.resolve()]; 函数methodThatReturnsAPromise(page) { 返回新的承诺((resolve, reject) => { setTimeout(() => { console.log(“解决- ${页面}!${new Date()} '); 解决(); }, 1000); }); } 函数pushPromise(page) { promises.push (promises.pop()。然后(function () { 返回methodThatReturnsAPromise(页面) })); } pushPromise (1); pushPromise (2); pushPromise (3);


使用现代ES:

const series = async (tasks) => {
  const results = [];

  for (const task of tasks) {
    const result = await task;

    results.push(result);
  }

  return results;
};

//...

const readFiles = await series(files.map(readFile));

首先,您需要了解承诺是在创建时执行的。 例如,如果你有一个代码:

["a","b","c"].map(x => returnsPromise(x))

您需要更改为:

["a","b","c"].map(x => () => returnsPromise(x))

然后,我们需要按顺序链接承诺:

["a", "b", "c"].map(x => () => returnsPromise(x))
    .reduce(
        (before, after) => before.then(_ => after()),
        Promise.resolve()
    )

执行after(),将确保只有在promise到期时才创建(并执行)promise。


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


nodejs中有承诺序列。

const promiseSequence = require('promise-sequence');
return promiseSequence(arr.map(el => () => doPromise(el)));

使用ES2016的async/await(可能还有ES2018的一些特性),这可以简化为这样的形式:

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

async function readFiles(files) {
  for (file in files) {
     await readFile(file)
  }
}

I haven't seen another answer express that simplicity. The OP said parallel execution of readFile was not desired. However, with IO like this it really makes sense to not be blocking on a single file read, while keeping the loop execution synchronous (you don't want to do the next step until all files have been read). Since I just learned about this and am a bit excited about it, I'll share that approach of parallel asynchronous execution of readFile with overall synchronous execution of readFiles.

async function readFiles(files) {
  await Promise.all(files.map(readFile))
}

这难道不是一件美好的事情吗?


这是我在各种项目中使用的顺序实现:

const file = [file1, file2, file3];
const fileContents = sequentially(readFile, files);

// somewhere else in the code:

export const sequentially = async <T, P>(
  toPromise: (element: T) => Promise<P>,
  elements: T[]
): Promise<P[]> => {
  const results: P[] = [];
  await elements.reduce(async (sequence, element) => {
    await sequence;
    results.push(await toPromise(element));
  }, Promise.resolve());

  return results;
};


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


只需使用.then(resPrevTask => nextTask())

(顺便说一下,下一个代码需要4秒。)

函数task1() { return new Promise((resolve) => { setTimeout(() => { 解决(console.log(任务1)) }, 3000) }) } 函数task2() { return new Promise((resolve) => { setTimeout(() => { 解决(console.log(任务2)) }, 1000) }) } 函数seqTasks () { task1 () .then(() => task2()) } seqTasks ();


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

我想重复很多人说过的,解决这个问题的最好方法是使用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,因此,您可以专注于业务逻辑。


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

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

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>