繁体   English   中英

Node.js 原生 Promise.all 是并行处理还是顺序处理?

[英]Is Node.js native Promise.all processing in parallel or sequentially?

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

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

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

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

Q2:如果Promise.all是并行运行的,有没有方便的方式顺序运行一个可迭代对象?

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

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

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

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

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

如果你已经有你的承诺,除了Promise.all([p1, p2, p3, …]) (它没有序列的概念),你不能做太多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())

在平行下

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

优点:更快。 即使稍后失败,所有迭代也将开始。

按顺序

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

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

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

并行与并发

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

并行和并发有一个很大的区别,第一个会在不同的进程中同时运行不同的计算,它们会按照那里的节奏进行,而另一个会一个接一个地执行不同的计算,而无需等待前一个计算同时完成和进行而不相互依赖。

最后,回答你的问题, Promise.all不会并行或顺序执行,而是并发执行。

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

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

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

只是为了详细说明@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());

希望这是有道理的。

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

平行线

看这个例子

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

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

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

然后依次创建所有客户端。

快乐编码:)

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

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

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

run().then().catch()

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

是的,您可以按如下方式链接一组 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));

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

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

我偶然发现了这个...... https://css-tricks.com/why-using-reduce-to-sequentially-resolve-promises-works/ ......我能够一起破解一个有效的解决方案。

域名注册地址:

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

我希望它可以帮助某人。

看到这个样本

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM