Node.js本机Promise.all是并行还是顺序处理?


173

我想澄清这一点,因为文档对此不太清楚。

问题1:Promise.all(iterable)按顺序还是并行处理所有承诺?或者,更具体地说,它相当于运行像

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

或者是一些其他类型的算法的所有p1p2p3p4p5,等是被称为在同一时间(并行)和结果尽快返回所有的决心(或一个不合格品)?

问题2:如果Promise.all并行运行,是否有方便的方法可以依次运行可迭代程序?

注意:我不想使用Q或Bluebird,而是要使用所有本机ES6规范。


您是在问有关节点(V8)的实现,还是有关规范的问题?
阿米特(Amit)

1
我很确定Promise.all可以并行执行它们。
royhowie

@Amit我标记了node.jsio.js因为这是我使用它的地方。所以,是的,如果您愿意的话,可以使用V8。
亚尼克·罗雄

9
不能“执行”承诺。它们在创建时就开始执行任务-它们仅代表结果-并且在并行执行所有操作之前甚至将其传递给Promise.all
Bergi 2015年

在创建时执行承诺。(可以通过运行一些代码来确认)。在new Promise(a).then(b); c();一个首先被执行,则c,则B。不是Promise.All履行了这些承诺,而是在它们解决时进行处理。
Mateon1 2015年

Answers:


257

正在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())

1
在此示例中,可迭代的函数数组是否返回您要调用的promise?
James Reategui '16

2
@SSHThis:与then序列完全相同-返回值是最后fn结果的承诺,您可以将其他回调链接到该序列。
Bergi '16

1
@wojjas完全等同于fn1().then(p2).then(fn3).catch(…吗?无需使用函数表达式。
Bergi '16

1
@wojjas当然,retValFromF1会传入p2,这就是正确的p2。当然,如果您想做更多的事情(传递附加变量,调用多个函数等),则需要使用函数表达式,尽管p2在数组中进行更改会更容易
Bergi 2016年

1
@ robe007是的,我的意思iterable是那个[fn1, fn2, fn3, …]数组
Bergi

62

在平行下

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

优点:更快。即使一次迭代失败,也将执行所有迭代。

按顺序

for (let i = 0; i < items.length; i++) {
    await fetchItem(items[i])
}

优点:循环中的变量可以由每次迭代共享。行为类似于普通的命令式同步代码。


7
或:for (const item of items) await fetchItem(item);
罗伯特·彭纳

1
@david_adler在并行示例中,您说过即使一次失败也将执行所有迭代。如果我没记错的话,这仍然会很快失败。要更改此行为,您可以执行以下操作: await Promise.all(items.map(async item => { return await fetchItem(item).catch(e => e) }))
Taimoor

@Taimoor是的,它确实“快速失败”并在Promise之后继续执行代码。所有但所有迭代仍在执行codepen.io/mfbx9da4/pen/BbaaXr
david_adler

async函数是API调用而您不想DDOS服务器时,这种方法更好。您可以更好地控制单个结果和执行中引发的错误。更好的是,您可以决定继续发生哪些错误以及打破循环的原因。
普通话,

请注意,由于javascript是单线程的,因此javascript实际上并未使用线程“并行”执行异步请求。developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
david_adler

11

Bergis的答案使用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()期望函数返回承诺。因此,您应该传递不带括号的函数名称!现在,我的函数需要一个参数,因此我需要将一个不带参数的匿名函数包装起来!


4

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

这段代码将运行数组中的每个项目,并将下一个“ then chain”添加到末尾;

function eachorder(prev,order) {
        return prev.then(function() {
          return get_order(order)
            .then(check_order)
            .then(update_order);
        });
    }
orderArray.reduce(eachorder,Promise.resolve());

希望这是有道理的。


3

您还可以使用递归函数使用异步函数顺序处理可迭代对象。例如,给定一个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"))


使用array.prototype.reduce在性能方面比递归函数好得多
MateuszSowiński

@MateuszSowiński,每个通话之间有1500毫秒的超时时间。考虑到这是按顺序执行异步调用的,因此即使对于非常快速的异步周转,也很难看到它的相关性。
Mark Meyer

假设您必须互相执行40个真正快速的异步函数-使用递归函数会很快阻塞您的内存
MateuszSowiński18年

@MateuszSowiński,这里的堆栈没有结束...我们在每次调用后都返回。将其与reduce必须then()一步构建整个链然后执行的位置进行比较。
Mark Meyer

在第40个顺序函数调用中,该函数的第一个调用仍在内存中,等待顺序函数链返回
MateuszSowiński18年

2

使用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


2

平行

看这个例子

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

通过运行代码,它将为所有六个promise控制台“ CALLED”,并且当它们解决后,它将在超时后同时对每6个响应进行控制台


2

NodeJS不并行运行promise,因为它是一个单线程事件循环体系结构,所以它可以并行运行它们。通过创建一个新的子进程来利用多核CPU,可以并行运行事物。

并行与并行

其实呢 Promise.all是将promise函数堆叠在适当的队列中(请参阅事件循环体系结构),并同时运行它们(调用P1,P2等),然后等待每个结果,然后将Promise.all与所有promise一起解决。结果。Promise.all会在第一个承诺失败时失败,除非您自己管理拒绝。

并行和并发之间的主要区别是,第一个将在完全相同的时间在单独的进程中运行不同的计算,并且它们将在该节奏中进行,而另一个将不等待上一个就一次又一次地执行不同的计算。无需彼此依赖即可同时完成和进行计算。

最后,要回答您的问题,Promise.all将不会并行或顺序执行,而不会并行执行。


这是不对的。NodeJS可以并行运行事物。NodeJS具有工作线程的概念。默认情况下,辅助线程的数量为4。例如,如果您使用加密库对两个值进行哈希处理,则可以并行执行它们。两个工作线程将处理任务。当然,您的CPU必须是多核才能支持并行性。
Shihab

是的,是的,这就是我在第一段末尾所说的,但是我谈到了子进程,当然他们可以雇用工人。
阿德里安·德·佩雷蒂

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

这是对原始问题的答案吗?
朱利奥·卡钦

0

您可以通过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);
}

然后按顺序创建所有客户端。

快乐的编码:)


8
目前,async/ await仅可用于编译器或使用Node以外的其他引擎。另外,您实际上不应该async与混合使用yield。尽管它们在转译器中的行为相同co,但它们实际上是完全不同的,通常不应互相推崇。另外,您应该提及这些限制,因为您的答案会使新手程序员感到困惑。
Yanick Rochon

0

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

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

run().then().catch()

0

这可能会回答您的部分问题。

是的,您可以按如下方式链接一个保证返回函数的数组...(这会将每个函数的结果传递给下一个)。您当然可以对其进行编辑,以将相同的参数(或无参数)传递给每个函数。

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


0

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

Node的“ fs”模块确实提供了appendFileSync,但我不想在此操作期间阻止服务器。我想使用fs.promises模块并找到一种将这些东西链接在一起的方法。此页面上的示例对我而言并不十分有效,因为我实际上需要执行两项操作:fsPromises.read()读取文件块,fsPromises.appendFile()链接至目标文件。也许如果我对javascript更好,那么我可以使之前的答案对我有用。;-)

我偶然发现了这个... https://css-tricks.com/why-using-reduce-to-sequentially-resolve-promises-works/ ... ...我得以破解一个可行的解决方案。

TLDR:

/**
 * 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 {
        }
    });
});

希望对您有所帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.