Node.js 本机 Promise.all 是并行处理还是顺序处理?
Is Node.js native Promise.all processing in parallel or sequentially?
我想澄清这一点,因为 documentation 不太清楚;
Q1: Promise.all(iterable)
是按顺序还是并行处理所有承诺?或者,更具体地说,它是否等同于 运行 像
这样的链式承诺
p1.then(p2).then(p3).then(p4).then(p5)....
或者是其他类型的算法,其中所有 p1
、p2
、p3
、p4
、p5
等都被调用同时(并行)并在所有解决(或一个拒绝)后立即返回结果?
Q2: 如果 Promise.all
运行 是并行的,有没有方便的方法 运行 一个可迭代的序列?
注意:我不想使用 Q 或 Bluebird,而是所有原生 ES6 规范。
Is Promise.all(iterable)
executing all promises?
不,承诺不能 "be executed"。他们在 created 时开始他们的任务 - 他们只代表结果 - you 甚至在将它们传递给 Promise.all
.
Promise.all
只做 await 多个承诺。它不关心它们以什么顺序解决,也不关心计算是否 运行 并行。
is there a convenient way to run an iterable sequencially?
如果您已经有了自己的承诺,那么除了 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())
你可以通过for循环来完成。
异步函数return承诺:
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's (非常简洁,但难以理解;)
此代码将 运行 数组中的每个项目并将下一个 '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());
Bergi's 使用 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);
});
没有等到传输完成就开始另一个传输,甚至在第一个文件传输开始之前就出现了“所有文件传输”文本。
不确定我做错了什么,但想分享对我有用的东西。
编辑:自从我写了这个 post 我现在明白了为什么第一个版本不起作用。 then()
期望 函数 返回一个承诺。所以,你应该传入不带括号的函数名!现在,我的函数需要一个参数,所以我需要包装在一个不带参数的匿名函数中!
您还可以使用递归函数使用异步函数顺序处理可迭代对象。例如,给定一个数组 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"))
并行
await Promise.all(items.map(async (item) => {
await fetchItem(item)
}))
优点:速度更快。即使稍后失败,所有迭代也将开始。但是,它将 "fail fast". Use Promise.allSettled
并行完成所有迭代,即使有些迭代失败。
顺序
for (const item of items) {
await fetchItem(item)
}
优点:循环中的变量可以被每次迭代共享。表现得像普通的命令式同步代码。
我一直在使用 for of 来解决顺序承诺。我不确定它在这里是否有帮助,但这就是我一直在做的。
async function run() {
for (let val of arr) {
const res = await someQuery(val)
console.log(val)
}
}
run().then().catch()
是的,您可以按如下方式链接一组承诺返回函数
(这会将每个函数的结果传递给下一个函数)。您当然可以编辑它以将相同的参数(或没有参数)传递给每个函数。
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));
Bergi's 帮助我使调用同步。我在下面添加了一个示例,我们在调用前一个函数之后调用每个函数:
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());
使用 async await 可以轻松地按顺序执行一系列承诺:
let a = [promise1, promise2, promise3];
async function func() {
for(let i=0; i<a.length; i++){
await a[i]();
}
}
func();
注意:在上面的实现中,如果一个 promise 被拒绝,剩下的就不是 executed.If 你想要你所有的 promise 都被执行,然后将你的 await a[i]();
包裹在 try catch
我在尝试解决 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 {
}
});
});
NodeJS 不会 运行 并行承诺,它会 运行 同时承诺它们,因为它是单线程事件循环架构。通过创建一个新的子进程来利用多核 CPU.
,有可能 运行 事情并行进行
事实上,Promise.all
所做的是,将 promises 函数堆叠在适当的队列中(参见事件循环架构)运行并发地调用它们(调用 P1、P2、...)然后等待每个结果,然后用所有 promises 结果解析 Promise.all。
Promise.all 将在第一次失败时失败,除非您必须自己处理拒绝。
并行和并发之间有一个主要区别,第一个将 运行 在一个单独的进程中同时进行不同的计算,它们将按照自己的节奏进行,而另一个将执行不同的计算一个接一个,不等待前一个计算完成,同时进行,不相互依赖。
最后,回答您的问题,Promise.all
既不会并行执行也不会顺序执行,而是并发执行。
平行
看这个例子
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();
通过 运行 代码它将控制 "CALLED" 所有六个承诺,当它们被解决时,它将在超时后同时控制每 6 个响应
查看此示例
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();
我想澄清这一点,因为 documentation 不太清楚;
Q1: Promise.all(iterable)
是按顺序还是并行处理所有承诺?或者,更具体地说,它是否等同于 运行 像
p1.then(p2).then(p3).then(p4).then(p5)....
或者是其他类型的算法,其中所有 p1
、p2
、p3
、p4
、p5
等都被调用同时(并行)并在所有解决(或一个拒绝)后立即返回结果?
Q2: 如果 Promise.all
运行 是并行的,有没有方便的方法 运行 一个可迭代的序列?
注意:我不想使用 Q 或 Bluebird,而是所有原生 ES6 规范。
Is
Promise.all(iterable)
executing all promises?
不,承诺不能 "be executed"。他们在 created 时开始他们的任务 - 他们只代表结果 - you 甚至在将它们传递给 Promise.all
.
Promise.all
只做 await 多个承诺。它不关心它们以什么顺序解决,也不关心计算是否 运行 并行。
is there a convenient way to run an iterable sequencially?
如果您已经有了自己的承诺,那么除了 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())
你可以通过for循环来完成。
异步函数return承诺:
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's
此代码将 运行 数组中的每个项目并将下一个 '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());
Bergi's 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);
});
没有等到传输完成就开始另一个传输,甚至在第一个文件传输开始之前就出现了“所有文件传输”文本。
不确定我做错了什么,但想分享对我有用的东西。
编辑:自从我写了这个 post 我现在明白了为什么第一个版本不起作用。 then()
期望 函数 返回一个承诺。所以,你应该传入不带括号的函数名!现在,我的函数需要一个参数,所以我需要包装在一个不带参数的匿名函数中!
您还可以使用递归函数使用异步函数顺序处理可迭代对象。例如,给定一个数组 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"))
并行
await Promise.all(items.map(async (item) => {
await fetchItem(item)
}))
优点:速度更快。即使稍后失败,所有迭代也将开始。但是,它将 "fail fast". Use Promise.allSettled
并行完成所有迭代,即使有些迭代失败。
顺序
for (const item of items) {
await fetchItem(item)
}
优点:循环中的变量可以被每次迭代共享。表现得像普通的命令式同步代码。
我一直在使用 for of 来解决顺序承诺。我不确定它在这里是否有帮助,但这就是我一直在做的。
async function run() {
for (let val of arr) {
const res = await someQuery(val)
console.log(val)
}
}
run().then().catch()
是的,您可以按如下方式链接一组承诺返回函数 (这会将每个函数的结果传递给下一个函数)。您当然可以编辑它以将相同的参数(或没有参数)传递给每个函数。
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));
Bergi's
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());
使用 async await 可以轻松地按顺序执行一系列承诺:
let a = [promise1, promise2, promise3];
async function func() {
for(let i=0; i<a.length; i++){
await a[i]();
}
}
func();
注意:在上面的实现中,如果一个 promise 被拒绝,剩下的就不是 executed.If 你想要你所有的 promise 都被执行,然后将你的 await a[i]();
包裹在 try catch
我在尝试解决 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 {
}
});
});
NodeJS 不会 运行 并行承诺,它会 运行 同时承诺它们,因为它是单线程事件循环架构。通过创建一个新的子进程来利用多核 CPU.
,有可能 运行 事情并行进行事实上,Promise.all
所做的是,将 promises 函数堆叠在适当的队列中(参见事件循环架构)运行并发地调用它们(调用 P1、P2、...)然后等待每个结果,然后用所有 promises 结果解析 Promise.all。
Promise.all 将在第一次失败时失败,除非您必须自己处理拒绝。
并行和并发之间有一个主要区别,第一个将 运行 在一个单独的进程中同时进行不同的计算,它们将按照自己的节奏进行,而另一个将执行不同的计算一个接一个,不等待前一个计算完成,同时进行,不相互依赖。
最后,回答您的问题,Promise.all
既不会并行执行也不会顺序执行,而是并发执行。
平行
看这个例子
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();
通过 运行 代码它将控制 "CALLED" 所有六个承诺,当它们被解决时,它将在超时后同时控制每 6 个响应
查看此示例
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();