247

I would like to clarify this point, as the documentation is not too clear about it;

Q1: Is Promise.all(iterable) processing all promises sequentially or in parallel? Or, more specifically, is it the equivalent of running chained promises like

p1.then(p2).then(p3).then(p4).then(p5)....

or is it some other kind of algorithm where all p1, p2, p3, p4, p5, etc. are being called at the same time (in parallel) and results are returned as soon as all resolve (or one rejects)?

Q2: If Promise.all runs in parallel, is there a convenient way to run an iterable sequencially?

Note: I don't want to use Q, or Bluebird, but all native ES6 specs.

4

14 回答 14

334

正在Promise.all(iterable)执行所有承诺吗?

不,承诺不能“被执行”。它们在创建时开始它们的任务——它们只代表结果——并且甚至在将它们传递给Promise.all.

Promise.all等待多个承诺。它不关心它们以什么顺序解析,或者计算是否并行运行。

有没有一种方便的方法可以按顺序运行迭代?

如果你已经有你的承诺,你不能做太多但是Promise.all([p1, p2, p3, …])(它没有序列的概念)。但是如果你确实有一个可迭代的异步函数,你确实可以按顺序运行它们。基本上你需要从

[fn1, fn2, fn3, …]

fn1().then(fn2).then(fn3).then(…)

解决方案是使用Array::reduce

iterable.reduce((p, fn) => p.then(fn), Promise.resolve())
于 2015-06-13T21:28:23.347 回答
124

在平行下

await Promise.all(items.map(async (item) => { 
  await fetchItem(item) 
}))

优点:更快。即使稍后失败,也将开始所有迭代。但是,它会“快速失败”。使用Promise.allSettled, 以并行完成所有迭代,即使有些迭代失败。

按顺序

for (const item of items) {
  await fetchItem(item)
}

优点:循环中的变量可以被每次迭代共享。表现得像普通的命令式同步代码。

于 2017-09-07T00:15:32.937 回答
27

NodeJS 不会并行运行 Promise,而是并发运行它们,因为它是单线程事件循环架构。通过创建一个新的子进程来利用多核 CPU,可以并行运行。

并行与并发

实际上,Promise.all所做的是,将 Promise 函数堆叠在适当的队列中(参见事件循环架构)同时运行它们(调用 P1、P2、...)然后等待每个结果,然后使用所有 Promise 解析 Promise.all结果。Promise.all 将在第一个失败的承诺处失败,除非您必须自己管理拒绝。

并行和并发之间有一个主要区别,第一个将在同一时间在单独的进程中运行不同的计算,并且它们将按照自己的节奏进行,而另一个将一个接一个地执行不同的计算,而无需等待先前的计算同时完成和进行,而不相互依赖。

最后,要回答您的问题,Promise.all既不会并行执行,也不会顺序执行,而是同时执行。

于 2020-01-03T23:12:32.880 回答
13

Bergi回答让我使用Array.reduce.

然而,要真正让函数返回我的承诺以一个接一个地执行,我必须添加更多的嵌套。

我的真实用例是一组文件,由于下游限制,我需要一个接一个地传输...

这是我最终得到的结果:

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(() => {
            return transferFile(theFile); //function returns a promise
        });
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

正如之前的答案所暗示的,使用:

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(transferFile(theFile));
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

在开始另一个之前没有等待传输完成,而且“所有文件传输”文本甚至在第一个文件传输开始之前就出现了。

不知道我做错了什么,但想分享对我有用的东西。

编辑:自从我写了这篇文章后,我现在明白了为什么第一个版本不起作用。then()期望一个函数返回一个承诺。所以,你应该传入不带括号的函数名!现在,我的函数需要一个参数,所以我需要包含在一个不带参数的匿名函数中!

于 2017-02-06T09:47:53.237 回答
8

您还可以使用递归函数使用异步函数顺序处理迭代。例如,给定一个a要使用异步函数处理的数组someAsyncFunction()

var a = [1, 2, 3, 4, 5, 6]

function someAsyncFunction(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("someAsyncFunction: ", n)
      resolve(n)
    }, Math.random() * 1500)
  })
}

//You can run each array sequentially with: 

function sequential(arr, index = 0) {
  if (index >= arr.length) return Promise.resolve()
  return someAsyncFunction(arr[index])
    .then(r => {
      console.log("got value: ", r)
      return sequential(arr, index + 1)
    })
}

sequential(a).then(() => console.log("done"))

于 2017-08-17T02:48:21.117 回答
5

只是为了详细说明@Bergi答案(非常简洁,但很难理解;)

此代码将运行数组中的每个项目并将下一个“然后链”添加到末尾:

function eachorder(prev,order) {
        return prev.then(function() {
          return get_order(order)
            .then(check_order)
            .then(update_order);
        });
    }
orderArray.reduce(eachorder,Promise.resolve());
于 2016-09-08T13:58:04.810 回答
4

使用async await可以轻松地按顺序执行一系列 Promise:

let a = [promise1, promise2, promise3];

async function func() {
  for(let i=0; i<a.length; i++){
    await a[i]();
  }  
}

func();

注意:在上面的实现中,如果一个promise被拒绝,其余的不会被执行。如果你想让你的所有promise都被执行,那么将你的await a[i]();内部包装起来try catch

于 2019-06-22T07:42:56.343 回答
3

平行

看这个例子

const resolveAfterTimeout = async i => {
  return new Promise(resolve => {
    console.log("CALLED");
    setTimeout(() => {
      resolve("RESOLVED", i);
    }, 5000);
  });
};

const call = async () => {
  const res = await Promise.all([
    resolveAfterTimeout(1),
    resolveAfterTimeout(2),
    resolveAfterTimeout(3),
    resolveAfterTimeout(4),
    resolveAfterTimeout(5),
    resolveAfterTimeout(6)
  ]);
  console.log({ res });
};

call();

通过运行代码,它将控制所有六个承诺的“CALLED”,当它们被解决时,它将在超时后同时控制每 6 个响应

于 2020-01-29T06:17:12.710 回答
2

你可以通过 for 循环来做到这一点。

异步函数返回承诺:

async function createClient(client) {
    return await Client.create(client);
}

let clients = [client1, client2, client3];

如果您编写以下代码,则并行创建客户端:

const createdClientsArray = yield Promise.all(clients.map((client) =>
    createClient(client);
));

但是如果你想按顺序创建客户端,那么你应该使用 for 循环:

const createdClientsArray = [];
for(let i = 0; i < clients.length; i++) {
    const createdClient = yield createClient(clients[i]);
    createdClientsArray.push(createdClient);
}
于 2016-02-25T11:36:27.360 回答
2

我在尝试解决 NodeJS 中的一个问题时偶然发现了这个页面:重新组装文件块。基本上:我有一个文件名数组。我需要以正确的顺序附加所有这些文件以创建一个大文件。我必须异步执行此操作。

Node 的“fs”模块确实提供appendFileSync了,但我不想在此操作期间阻止服务器。我想使用该fs.promises模块并找到一种将这些东西链接在一起的方法。此页面上的示例对我来说不太适用,因为我实际上需要两个操作:fsPromises.read()读取文件块,并fsPromises.appendFile()连接到目标文件。也许如果我对 JavaScript 更好,我可以让以前的答案对我有用。;-)

我偶然发现了这一点,我能够一起破解一个可行的解决方案:

/**
 * sequentially append a list of files into a specified destination file
 */
exports.append_files = function (destinationFile, arrayOfFilenames) {
    return arrayOfFilenames.reduce((previousPromise, currentFile) => {
        return previousPromise.then(() => {
            return fsPromises.readFile(currentFile).then(fileContents => {
                return fsPromises.appendFile(destinationFile, fileContents);
            });
        });
    }, Promise.resolve());
};

这是一个茉莉花单元测试:

const fsPromises = require('fs').promises;
const fsUtils = require( ... );
const TEMPDIR = 'temp';

describe("test append_files", function() {
    it('append_files should work', async function(done) {
        try {
            // setup: create some files
            await fsPromises.mkdir(TEMPDIR);
            await fsPromises.writeFile(path.join(TEMPDIR, '1'), 'one');
            await fsPromises.writeFile(path.join(TEMPDIR, '2'), 'two');
            await fsPromises.writeFile(path.join(TEMPDIR, '3'), 'three');
            await fsPromises.writeFile(path.join(TEMPDIR, '4'), 'four');
            await fsPromises.writeFile(path.join(TEMPDIR, '5'), 'five');

            const filenameArray = [];
            for (var i=1; i < 6; i++) {
                filenameArray.push(path.join(TEMPDIR, i.toString()));
            }

            const DESTFILE = path.join(TEMPDIR, 'final');
            await fsUtils.append_files(DESTFILE, filenameArray);

            // confirm "final" file exists    
            const fsStat = await fsPromises.stat(DESTFILE);
            expect(fsStat.isFile()).toBeTruthy();

            // confirm content of the "final" file
            const expectedContent = new Buffer('onetwothreefourfive', 'utf8');
            var fileContents = await fsPromises.readFile(DESTFILE);
            expect(fileContents).toEqual(expectedContent);

            done();
        }
        catch (err) {
            fail(err);
        }
        finally {
        }
    });
});
于 2020-01-02T20:29:04.487 回答
1

Bergi回答帮助我使通话同步。我在下面添加了一个示例,我们在调用前一个函数之后调用每个函数:

function func1 (param1) {
    console.log("function1 : " + param1);
}
function func2 () {
    console.log("function2");
}
function func3 (param2, param3) {
    console.log("function3 : " + param2 + ", " + param3);
}

function func4 (param4) {
    console.log("function4 : " + param4);
}
param4 = "Kate";

//adding 3 functions to array

a=[
    ()=>func1("Hi"),
    ()=>func2(),
    ()=>func3("Lindsay",param4)
  ];

//adding 4th function

a.push(()=>func4("dad"));

//below does func1().then(func2).then(func3).then(func4)

a.reduce((p, fn) => p.then(fn), Promise.resolve());
于 2019-05-10T19:44:45.753 回答
1

我一直在使用 for of 来解决顺序承诺。我不确定它在这里是否有帮助,但这就是我一直在做的事情。

async function run() {
    for (let val of arr) {
        const res = await someQuery(val)
        console.log(val)
    }
}

run().then().catch()
于 2017-09-07T16:22:19.500 回答
0

是的,您可以按如下方式链接一组 promise 返回函数(这会将每个函数的结果传递给下一个函数)。您当然可以编辑它以将相同的参数(或不带参数)传递给每个函数。

function tester1(a) {
  return new Promise(function(done) {
    setTimeout(function() {
      done(a + 1);
    }, 1000);
  })
}

function tester2(a) {
  return new Promise(function(done) {
    setTimeout(function() {
      done(a * 5);
    }, 1000);
  })
}

function promise_chain(args, list, results) {

  return new Promise(function(done, errs) {
    var fn = list.shift();
    if (results === undefined) results = [];
    if (typeof fn === 'function') {
      fn(args).then(function(result) {
        results.push(result);
        console.log(result);
        promise_chain(result, list, results).then(done);
      }, errs);
    } else {
      done(results);
    }

  });
}

promise_chain(0, [tester1, tester2, tester1, tester2, tester2]).then(console.log.bind(console), console.error.bind(console));

于 2018-01-29T10:27:26.743 回答
-2

看到这个样本

Promise.all并行工作

const { range, random, forEach, delay} = require("lodash");  
const run = id => {
    console.log(`Start Task ${id}`);
    let prom = new Promise((resolve, reject) => {
        delay(() => {
            console.log(`Finish Task ${id}`);
            resolve(id);
        }, random(2000, 15000));
    });
    return prom;
}


const exec = () => {
    let proms = []; 
    forEach(range(1,10), (id,index) => {
        proms.push(run(id));
    });
    let allPromis = Promise.all(proms); 
    allPromis.then(
        res => { 
            forEach(res, v => console.log(v));
        }
    );
}

exec();
于 2021-02-08T07:03:35.133 回答