【问题标题】:Is Node.js native Promise.all processing in parallel or sequentially?Node.js 原生 Promise.all 是并行处理还是顺序处理?
【发布时间】:2015-08-29 16:17:18
【问题描述】:

我想澄清这一点,因为documentation对此不太清楚;

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

这样的链式 Promise
p1.then(p2).then(p3).then(p4).then(p5)....

或者是其他类型的算法,其中所有p1p2p3p4p5 等都被同时调用(并行)并返回结果一旦全部解决(或一个拒绝)?

Q2:如果Promise.all并行运行,有没有一种方便的方式来顺序运行一个iterable?

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

【问题讨论】:

  • 你问的是节点(V8)的实现,还是规范?
  • 我很确定 Promise.all 会并行执行它们。
  • @Amit 我标记了node.jsio.js,因为这是我使用它的地方。所以,是的,如果你愿意的话,V8 实现。
  • 承诺不能“被执行”。他们在创建时开始他们的任务——他们只代表结果——并且甚至在将它们传递给Promise.all之前并行执行所有内容。
  • Promise 在创建时执行。 (可以通过运行一些代码来确认)。在new Promise(a).then(b); c(); 中,首先执行 a,然后执行 c,然后执行 b。运行这些 Promise 的不是 Promise.all,它只是在它们解决时处理。

标签: javascript node.js promise es6-promise


【解决方案1】:

Promise.all(iterable) 是否执行了所有的承诺?

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

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

有没有一种方便的方式来顺序运行一个可迭代对象?

如果你已经有你的承诺,你不能做太多,但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())

【讨论】:

  • 在这个例子中,是否是一个可迭代的函数数组,这些函数返回您要调用的承诺?
  • @SSHThis:与then 序列完全相同——返回值是对最后一个fn 结果的承诺,您可以将其他回调链接到该序列。
  • @wojjas 这完全等同于fn1().then(p2).then(fn3).catch(…?无需使用函数表达式。
  • @wojjas 当然retValFromF1 被传递到p2,这正是p2 所做的。当然,如果您想做更多事情(传递额外的变量、调用多个函数等),您需要使用函数表达式,尽管在数组中更改 p2 会更容易
  • @robe007 是的,我的意思是iterable[fn1, fn2, fn3, …] 数组
【解决方案2】:

并行

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

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

按顺序

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

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

【讨论】:

  • 或:for (const item of items) await fetchItem(item);
  • @david_adler 在您所说的并行示例优势中,所有迭代都将被执行,即使一个迭代失败。如果我没记错的话,这仍然会很快失败。要更改此行为,可以执行以下操作:await Promise.all(items.map(async item => { return await fetchItem(item).catch(e => e) }))
  • @Taimoor 是的,它确实“快速失败”并在 Promise.all 之后继续执行代码,但所有迭代仍在执行 codepen.io/mfbx9da4/pen/BbaaXr
  • 这种方法更好,当 async 函数是一个 API 调用并且您不想对服务器进行 DDOS 时。您可以更好地控制执行中引发的单个结果和错误。更好的是,您可以决定继续哪些错误以及中断循环。
  • 请注意,由于 javascript 是单线程的,因此 javascript 实际上并没有使用线程“并行”执行异步请求。 developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
【解决方案3】:

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

Parallel Vs Concurent

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

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

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

【讨论】:

  • 这是不对的。 NodeJS 可以并行运行。 NodeJS 有一个工作线程的概念。默认情况下,工作线程的数量是 4。例如,如果您使用加密库对两个值进行哈希处理,那么您可以并行执行它们。两个工作线程将处理该任务。当然,您的 CPU 必须是多核的才能支持并行性。
  • 是的,你说得对,就是我在第一段末尾说的,但是我说的是子进程,他们当然可以运行worker。
  • 迄今为止的最佳答案。我很困惑,像 Node.js 这样的单线程架构如何并行运行多个 Promise。非常感谢先生。附言我知道工作线程是如何以及它们是如何工作的,但是承诺是由 Node.js 事件循环本身而不是通过使用 libuv 来解决的。因此,Node.js 可以做的最好的事情就是同时执行它们(承诺)。
【解决方案4】:

Bergianswer 使用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() 期望 function 返回一个承诺。所以,你应该传入不带括号的函数名!现在,我的函数需要一个参数,所以我需要包含在一个不带参数的匿名函数中!

【讨论】:

    【解决方案5】:

    您还可以使用递归函数通过异步函数顺序处理迭代。例如,给定一个数组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,每次通话之间有 1500 毫秒的超时。考虑到这是按顺序执行异步调用,即使对于非常快速的异步周转,也很难看出这有什么关系。
    • 假设你必须一个接一个地执行 40 个非常快速的异步函数 - 使用递归函数会很快堵塞你的记忆
    • @MateuszSowiński,堆栈不会在这里结束......我们在每次调用后返回。与reduce 相比,您必须一步构建整个then() 链然后执行。
    • 在顺序函数的第40次调用中,函数的第一次调用仍在内存中等待顺序函数链返回
    【解决方案6】:

    只是详细说明@Bergianswer(非常简洁,但很难理解;)

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

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

        并行

        看这个例子

        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 个响应

        【讨论】:

          【解决方案9】:

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

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

          我偶然发现了this,我能够拼凑出一个可行的解决方案:

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

          这里有一个 jasmine 单元测试:

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

          【讨论】:

            【解决方案10】:

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

            【讨论】:

            • 此时,async/await 只能使用转译器,或者使用other engines 而不是 Node。此外,您真的不应该将asyncyield 混合使用。虽然它们与转译器和co 的行为相同,但它们确实完全不同,通常不应相互替代。此外,您应该提及这些限制,因为您的回答会让新手程序员感到困惑。
            【解决方案11】:

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

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

            【讨论】:

              【解决方案12】:

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

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

              【讨论】:

              • 这是对原始问题的回答吗?
              【解决方案13】:

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

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

              【讨论】:

                【解决方案14】:

                查看此示例

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2023-03-31
                  • 1970-01-01
                  • 2021-11-08
                  • 1970-01-01
                  相关资源
                  最近更新 更多