【问题标题】:Parallel operations with Promise.all?与 Promise.all 并行操作?
【发布时间】:2017-08-03 23:40:53
【问题描述】:

我被引导相信 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.?

还是我在做这件事的过程中遗漏了什么?

【问题讨论】:

  • 承诺主体是同步评估的。
  • 因此,如果其中一个承诺是从数据库中获取某些内容,而另一个承诺是从 Web API 获取数据,它将异步执行此操作,但我告诉它打印到的任何内容屏幕会按照我在数组中指定的顺序完成吗?
  • Promise.all无关。当你创建一个 Promise 时,你传入的函数会立即被评估,所以 Promise1 的主体在你调用 Promise2Promise.all 之前运行。 Javascript 不是多线程的,所以你的 for 循环永远不会像那样重叠。
  • Here'sPromise.all 工作原理的示例。 Promise 以它们创建的顺序开始,但它们是并行运行的。
  • Is Node.js native Promise.all processing in parallel or sequentially? 的可能重复项。请注意,promise 本身不会使同步循环“并行”或“异步”,它仅有助于表示已经异步的事物(如数据库查找、Web api 调用或超时)。

标签: node.js promise bluebird


【解决方案1】:

这是因为你的 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!")
    })

【讨论】:

  • 对不起,我的无知,我理解这对于单核 CPU。如果我有一个双核 CPU,我希望至少有两个任务可以并行运行。像:如示例中那样同时迭代 2 个数组。
  • javascript 是单线程的。您的 cpu 数量无关紧要,即使这样,同步操作也只能在一个 cpu 中运行。
【解决方案2】:

我建议这样使用它:

const [
    res1,
    res2
] = await Promise.all([
    asyncCall1(),
    asyncCall1(),
]);

【讨论】:

  • 为什么会这样?有什么优势?
【解决方案3】:

为了在 Johannes Merz 开始的基础上再接再厉,我提出这段代码是为了澄清事情是并行发生的。

JS 是单线程的,但是 Node.js 有很多工具可以显式和隐式地启动额外的线程。 Promise 公开了我们经常需要的更多功能,而无需显式启动新线程或进程。 Promise.all() 就是这样一个例子,但是您需要熟悉 Promises 才能使用它,而不会给自己带来严重的麻烦,例如 Promise Scope 内存泄漏。

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

这个版本确实实现了异步/等待。

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

【讨论】:

    【解决方案4】:

    串行执行非异步主体 当您在正文中到达异步调用时(例如,点击 URL),数组中的其他 Promise 将开始执行。

    【讨论】:

      【解决方案5】:

      也许解决方案是使用 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 (working example on codesandbox)

      【讨论】:

        猜你喜欢
        • 2019-05-17
        • 1970-01-01
        • 2017-02-05
        • 2016-11-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多