是Node.js的原生承诺。所有的处理是并行的还是顺序的?

我想澄清这一点,因为文档不是很清楚;

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

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

还是其他类型的算法,其中所有p1p2p3p4p5等同时被调用(并行),结果在所有解决(或一个拒绝)后立即返回?

Q2:如果Promise.all并行运行,是否有一种方便的方法来顺序运行可迭代对象?

请注意:我不想使用Q,或蓝鸟,但所有本地ES6规格。

156643 次浏览

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

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

Promise.all只执行等待多重承诺。它不关心它们解析的顺序,也不关心计算是否在并行运行。

是否有一种方便的方法来连续运行一个可迭代对象?

如果你已经有了承诺,你只能做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循环来实现。

Async函数返回承诺:

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回答(非常简洁,但很难理解;)

这段代码将运行数组中的每一项,并将下一个'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回答使用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()期望函数返回一个promise。因此,您应该传入不带括号的函数名!现在,我的函数需要一个参数,所以我需要封装在一个匿名函数中,不带参数!

也可以使用递归函数用异步函数顺序处理可迭代对象。例如,给定一个数组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)
}))

优点:速度更快。所有的迭代都将启动,即使其中一个在以后失败。然而,它将“失败fast"。使用Promise.allSettled来并行完成所有迭代,即使某些迭代失败。从技术上讲,这些是并发调用,而不是并行调用。

按顺序

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

优点:循环中的变量可以在每次迭代中共享。行为类似于普通的命令式同步代码。

我一直在用for来求解连续的承诺。我不确定这是否有帮助,但这就是我一直在做的。

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

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

使用异步等待,一个promise数组可以很容易地按顺序执行:

let a = [promise1, promise2, promise3];


async function func() {
for(let i=0; i<a.length; i++){
await a[i]();
}
}


func();

注意:在上述实现中,如果一个承诺被拒绝,其余的承诺将不会被执行。如果你希望所有的承诺都被执行,那么将await a[i]();包装在try catch

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

节点的'fs'模块确实提供了appendFileSync,但我不想在此操作期间阻塞服务器。我想使用fs.promises模块并找到一种方法将这些东西链接在一起。本页上的例子并不适合我,因为我实际上需要两个操作:fsPromises.read()读入文件块,fsPromises.appendFile()连接到目标文件。也许如果我是更好的JavaScript,我可以使以前的答案为我工作。;-)

我偶然发现了,我能够拼凑出一个工作解决方案:

/**
* 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来并行运行。

并行Vs并发

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

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

最后,为了回答你的问题,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();