使用Promise.all进行并行操作?

24
我认为Promise.all会并行执行传递给它的所有函数,并且不关心返回的promise完成的顺序。 但是当我编写下面的测试代码时:
    function Promise1(){
        return new Promise(function(resolve, reject){
            for(let i = 0; i < 10; i++){
                console.log("Done Err!");
            }
            resolve(true)
        })
    }
    
    function Promise2(){
        return new Promise(function(resolve, reject){
            for(let i = 0; i < 10; i++){
                console.log("Done True!");
            }
            resolve(true)
        })
    }
    
    Promise.all([ 
        Promise1(),
        Promise2()
    ])
    .then(function(){
        console.log("All Done!")
    })

我得到的结果是这个

Done Err!
Done Err!
Done Err!
Done Err!
Done Err!
Done Err!
Done Err!
Done Err!
Done Err!
Done Err!
Done True!
Done True!
Done True!
Done True!
Done True!
Done True!
Done True!
Done True!
Done True!
Done True!
Done!

但是如果它们并行运行,我不会期望它们同时执行并给出这样的结果吗?

Done Err!
Done True!
Done Err!
Done True!
Done Err!
Done True!
Done Err!
Done True!
Etc. Etc.?

或者我在做的方式上漏掉了什么吗?


7
承诺体同步评估。 - SimpleJ
所以,如果其中一个Promise要从数据库中获取某些内容,而另一个Promise要从Web API获取数据,它将异步执行,但是我要求它打印到屏幕上的任何东西都将按照我在数组中指定的顺序完成吗? - Steve
3
这与Promise.all无关。当你创建一个承诺时,传递的函数会立即评估,所以在调用Promise2Promise.all之前,Promise1的主体已经运行了。Javascript不是多线程的,所以你的for循环永远不会像那样重叠。 - SimpleJ
1
这里有一个关于 Promise.all 如何工作的例子。承诺按照它们创建的顺序开始,但并行运行。 - SimpleJ
谢谢,我想我理解了,但我不是100%确定。这是因为setTimeout有一个回调函数,所以它可以在此期间执行其他操作吗? - Steve
1
可能是Is Node.js native Promise.all processing in parallel or sequentially?的重复问题。请注意,一个Promise本身并不能使同步循环“并行”或“异步”,它只是帮助表示已经是异步的事物(如数据库查找、Web API调用或超时)。 - Bergi
5个回答

19

问题出在你的Promise是阻塞且同步的!尝试使用超时而不是同步循环:

    function randomResolve(name) {
      return new Promise(resolve => setTimeout(() => {
        console.log(name);
        resolve();
      }, 100 * Math.random()));
    }
    
    Promise.all([ 
        randomResolve(1),
        randomResolve(2),
        randomResolve(3),
        randomResolve(4),
    ])
    .then(function(){
        console.log("All Done!")
    })


1
抱歉我的无知,我理解这是针对单核CPU的。如果我有一个双核CPU,我期望至少可以并行运行两个任务。比如:像示例中同时迭代2个数组。 - Shihab
1
JavaScript 是单线程的。你的 CPU 数量并不重要,即使是同步操作也只能在一个 CPU 上运行。 - Johannes Merz

6
我建议这样使用它:
const [
    res1,
    res2
] = await Promise.all([
    asyncCall1(),
    asyncCall1(),
]);

2
为什么呢?有什么好处吗? - O'Dane Brissett

3
为了继续 Johannes Merz 的工作,我提出这段代码以澄清事情是在并行发生的。
JS 是单线程的,但 Node.js 有很多工具可以显式和隐式地启动额外的线程。Promise 暴露了更多我们经常需要的功能,而无需显式地启动新的线程或进程。Promise.all() 就是这样一个例子,但你需要熟悉 Promise 才能在使用它时避免创建严重的头痛,例如 Promise 作用域内存泄漏。

    function randomResolve(name,t) {
      return new Promise(resolve => setTimeout(() => {
        console.log({ name, t });
        resolve({ name, t });
      }, t));
    }
    
    (() => {
        // Get epoch time before starting so we can confirm the execution time reflects our slowest timeout
        let start = new Date().valueOf(); 
    
        Promise.all([ 
            randomResolve(1, 1000 * Math.random()),
            randomResolve(2, 1000 * Math.random()),
            randomResolve(3, 1000 * Math.random()),
            randomResolve(4, 1000 * Math.random()),
        ])
        .then(function( res ){
            console.info( res );
            console.log("All Done!", parseInt(new Date().valueOf() - start) );
        })
    })();

该模式接受一个输入数组,并使用array.map()返回一组启动的promise数组,这些promise将像上面所述并行处理。请注意,此处没有使用async/await。

    function randomResolve(name,t) {
      return new Promise(resolve => setTimeout(() => {
        console.log({ name, t });
        resolve({ name, t });
      }, t));
    }
    
    (() => {
        // Get epoch time before starting so we can confirm the execution time reflects our slowest timeout
        let start = new Date().valueOf(),
            vals = [ 
                [1, 1000 * Math.random()],
                [2, 1000 * Math.random()], 
                [3, 1000 * Math.random()],
                [4, 1000 * Math.random()]
            ];
    
        Promise.all( vals.map( v => { return randomResolve(v[0], v[1] ); } ) )
        .then(function( res ){
            console.info( res );
            console.log("All Done!", parseInt(new Date().valueOf() - start) );
        })
    })();

这个版本已经实现了async/await。

    function randomResolve(name,t) {
      return new Promise(resolve => setTimeout(() => {
        console.log({ name, t });
        resolve({ name, t });
      }, t));
    }

    (async () => {
        // Get epoch time before starting so we can confirm the execution time reflects our slowest timeout
        let start = new Date().valueOf(),
            vals = [ 
                [1, 1000 * Math.random()],
                [2, 1000 * Math.random()], 
                [3, 1000 * Math.random()],
                [4, 1000 * Math.random()]
            ];
    
        let res = await Promise.all( vals.map( async v => { return await randomResolve( v[0], v[1] ); } ) );
        // await the Promise.aall() call instead of using .then() afterwards with another closure then
        //     forEach v in vals, start and await a Promise from randomResolve() then return the result to map
    
        console.info( res );
        console.log("All Done!", parseInt(new Date().valueOf() - start) );
    
    })();


1
也许解决方案是使用worker-farm,我不知道如何解释它,因为我是NodeJS的新手,但这里有一篇有趣的文章:

https://blog.logrocket.com/node-js-multithreading-what-are-worker-threads-and-why-do-they-matter-48ab102f8b10/

在这种情况下,您可以使用worker-farm。
(目前我对此主题没有很好的掌握,所以请不要犹豫纠正我。)

首先,安装"worker-farm"

npm install --save worker-farm

然后:
// index.js
const workerFarm = require("worker-farm");
const service = workerFarm(require.resolve("./service"));
function Promise1() {
  return new Promise(function(resolve, reject) {
    service("promise1", (err, output) => {
      resolve(true);
    });
  });
}

function Promise2() {
  return new Promise(function(resolve, reject) {
    service("promise2", (err, output) => {
      resolve(true);
    });
  });
}

Promise.all([Promise1(), Promise2()]).then(function() {
  console.log("All Done!");
});

service.js 中创建一个函数,该函数需要耗费大量的执行时间,在执行完成后执行回调函数。
// service.js
const blocker = (input, callback) => {
  // Number loop turns
  // Adjust this number depending on your CPU
  const nbTurn = 1000000000;
  // How many log to display during the loop
  const nbReminder = 4;
  let i;
  for (i = 0; i <= nbTurn; i++) {
    const remainder = (i % Math.ceil(nbTurn / nbReminder)) / 100;
    if (remainder === 0) {
      console.log(input, i);
    }
  }
  console.log(input + "end", i);
  callback(null, nbTurn);
};
module.exports = blocker;

编辑:我找到了另一个解决方案: napajs (codesandbox上的工作示例)


1
一个非异步的主体是按顺序执行的。当您在主体中遇到异步调用(例如,访问URL)时,数组中的其他Promise将开始执行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接