Javascript 承诺链竞赛条件
我目前正在研究一个相当简单的逻辑来处理排队的ZPL打印作业,这些作业存储在一个数组中,然后被迭代,每个作业向打印机发送n份拷贝 我将数组缩减为一个承诺链,每个任务将副本发送到打印机,并在子链中混合。对打印机的调用是同步的(是的,我知道…),因此我将它们中的每一个都包装成一个承诺,只有当打印机收到副本时才会解析,从而确保顺序处理 在传输失败的情况下,当前承诺将被拒绝,并在主链中捕获手工制作的错误 到目前为止,理论似乎在子链之间存在一种竞争条件 我尽了最大努力,但我就是看不到 这里有一些简化的代码+提琴,请注意子链是如何不依次运行的:Javascript 承诺链竞赛条件,javascript,es6-promise,race-condition,Javascript,Es6 Promise,Race Condition,我目前正在研究一个相当简单的逻辑来处理排队的ZPL打印作业,这些作业存储在一个数组中,然后被迭代,每个作业向打印机发送n份拷贝 我将数组缩减为一个承诺链,每个任务将副本发送到打印机,并在子链中混合。对打印机的调用是同步的(是的,我知道…),因此我将它们中的每一个都包装成一个承诺,只有当打印机收到副本时才会解析,从而确保顺序处理 在传输失败的情况下,当前承诺将被拒绝,并在主链中捕获手工制作的错误 到目前为止,理论似乎在子链之间存在一种竞争条件 我尽了最大努力,但我就是看不到 这里有一些简化的代码+
['job1', 'job2'].reduce((pMain, item, curIndex) => {
var pSub = Promise.resolve();
for (let i = 0; i < 2; i++) pSub = pSub.then(() => new Promise((resolve, reject) => setTimeout(reject, 2000)));
return pMain.then(() => pSub);
}, Promise.resolve())
.then(() => /* print all done */)
.catch( handleError );
['job1','job2'].reduce((pMain,item,curIndex)=>{
var pSub=Promise.resolve();
对于(让i=0;i<2;i++)pSub=pSub.then(()=>newpromise((resolve,reject)=>setTimeout(reject,2000));
返回pMain.then(()=>pSub);
},Promise.resolve())
。然后(()=>/*打印全部完成*/)
.接住(把手错误);
非常感谢您的建议。被如此琐碎的事情缠住是一件很糟糕的事。您的
pSub
链都是在reduce
调用期间创建并同步运行的。要成为连续的,它们需要进入内部,然后进入回调:
['job1', 'job2'].reduce((pMain, item, curIndex) => {
return pMain.then(() => {
var pSub = Promise.resolve();
for (let i = 0; i < 2; i++)
pSub = pSub.then(() => new Promise((resolve, reject) => setTimeout(reject, 2000)));
return pSub;
});
}, Promise.resolve())
当然@jfriend是对的,对于顺序任务,您应该只编写async
/wait
代码:
for (const item of ['job1', 'job2']) {
for (let i = 0; i < 2; i++) {
await new Promise((resolve, reject) => setTimeout(reject, 2000));
}
}
for(常量项['job1','job2']){
for(设i=0;i<2;i++){
等待新的承诺((解决,拒绝)=>setTimeout(拒绝,2000));
}
}
您也可以使用该解决方案轻松地将try
块放在正确的级别上。我想有很多方法可以实现这一点,但就个人而言,我通常做的是创建一个函数数组,返回承诺(你可能会说是承诺工厂)
然后,我们可以将数组传递给该函数,该函数将按顺序运行它们:
const runPromiseSequentially = promiseFactories => {
let result = Promise.resolve();
promiseFactories.forEach(
(promiseFactory) => {
result = result.then(() => promiseFactory());
},
);
return result;
}
runPromiseSequentially(promiseFactories);
基本上,它所做的是在我们希望开始操作时,要求承诺人创建承诺
范例
不过,如果您可以使用async
和wait
,这将是不必要的。因此,到目前为止,您已经了解了使用.reduce()
序列化承诺的错误之处。在评论中,我提出了一些建议,建议您:
使用现代async/await
(必要时使用传输)
使用已经提供异步迭代的预构建库
编写/使用一些经过测试的实用程序函数,您可以使用这些函数来代替每次手动编写.reduce()
循环李>
如果#1或#2不实用,我建议您自己制作经过测试的实用程序函数,因为.reduce()
序列化方法很容易出错,对于那些还没有看过代码的人来说,知道它在做什么并不总是微不足道的,而一个经过编写和测试的适当命名的实用程序函数更易于使用和理解(一旦编写了函数),显然它也使重用变得切实可行
对于预构建库,Bluebird和Async都具有这些功能(我个人更喜欢Bluebird),并且在运行旧版本JS的嵌入式项目(Raspberry Pi)上使用了Bluebird
对于已测试的实用程序函数,这里有几个可以快速使用的函数
iterateAsync()
就像一个异步的.forEach()
mapsync()
就像一个异步的.map()
reduceAsync()
就像一个异步的.reduce()
它们都将数组作为第一个参数,将返回承诺的函数作为第二个参数。这些是ES5兼容的,但一定要假设Promise
可用。以下是三个功能:
// iterate an array sequentially, calling a function (that returns a promise)
// on each element of the array
// The final resolved value is whatever the last call to fn(item) resolves to
// like an asynchronous .forEach()
function iterateAsync(array, fn) {
return array.reduce(function(p, item) {
return p.then(function() {
return fn(item);
});
}, Promise.resolve());
}
// iterate an array sequentially, calling a function (that returns a promise)
// on each element of the array
// The final resolved value is an array of what all the fn(item) calls resolved to
// like an asynchronous .map()
function mapAsync(array, fn) {
var results = [];
return array.reduce(function(p, item) {
return p.then(function() {
return fn(item).then(function(val) {
results.push(val);
return val;
});
});
}, Promise.resolve()).then(function() {
return results;
});
}
// iterate an array sequentially, calling a function fn(item, val)
// (that returns a promise) on each element of the array. Like array.reduce(),
// the next fn(accumulator, item) is passed the previous resolved value and the promise
// that fn() returns should resolve to the value you want passed to the next
// link in the chain
// The final resolved value is whatever the last call to fn(item, val) resolves to
// like an asynchronous .reduce()
function reduceAsync(array, fn, initialVal) {
return array.reduce(function(p, item) {
return p.then(function(accumulator) {
return fn(accumulator, item);
});
}, Promise.resolve(initialVal));
}
请注意,使用现代Javascript功能(特别是async/await
),所有这些功能通常都比较简单,因此这些功能主要用于那些现代功能不可用或传输不实用的情况
为了完整起见,我要补充一点,以这种方式使用.reduce()
可能不适合迭代非常大的数组。这是因为它所做的是同步地预构建一个承诺链p.then().then().then().then()
,其数量.then()
等于数组的长度。如果您的阵列非常大(数万或数十万个元素长),则需要大量内存来预构建所有这些承诺并将它们链接在一起
对于您提到的“有限环境”中的非常大的阵列,您可能希望像这样更手动地迭代,而不是预先构建任何大型结构,而是一次只使用一个:
function iterateAsync(list, fn) {
return new Promise(function(resolve, reject) {
var index = 0;
function next(val) {
if (index < list.length) {
try {
fn(list[index++]).then(next, reject);
} catch(e) {
reject(e);
}
} else {
resolve(val);
}
}
next();
});
}
函数iterateAsync(列表,fn){
返回新承诺(功能(解决、拒绝){
var指数=0;
下一个功能(val){
如果(索引<列表长度){
试一试{
fn(列出[index++])。然后(下一步,拒绝);
}捕获(e){
拒绝(e);
}
}否则{
解决(val);
}
}
next();
});
}
预期的输出是什么?我肯定不是一个聪明的人,但我发现这段代码很难阅读。承诺是否需要按顺序运行?如果有错误,是否要停止处理?请使用wait
在for
循环中序列化承诺操作。因此,书写、阅读和理解都要容易得多。使用.reduce()
(正确完成后)是我们在使用之前异步迭代数组的方式<
// iterate an array sequentially, calling a function (that returns a promise)
// on each element of the array
// The final resolved value is whatever the last call to fn(item) resolves to
// like an asynchronous .forEach()
function iterateAsync(array, fn) {
return array.reduce(function(p, item) {
return p.then(function() {
return fn(item);
});
}, Promise.resolve());
}
// iterate an array sequentially, calling a function (that returns a promise)
// on each element of the array
// The final resolved value is an array of what all the fn(item) calls resolved to
// like an asynchronous .map()
function mapAsync(array, fn) {
var results = [];
return array.reduce(function(p, item) {
return p.then(function() {
return fn(item).then(function(val) {
results.push(val);
return val;
});
});
}, Promise.resolve()).then(function() {
return results;
});
}
// iterate an array sequentially, calling a function fn(item, val)
// (that returns a promise) on each element of the array. Like array.reduce(),
// the next fn(accumulator, item) is passed the previous resolved value and the promise
// that fn() returns should resolve to the value you want passed to the next
// link in the chain
// The final resolved value is whatever the last call to fn(item, val) resolves to
// like an asynchronous .reduce()
function reduceAsync(array, fn, initialVal) {
return array.reduce(function(p, item) {
return p.then(function(accumulator) {
return fn(accumulator, item);
});
}, Promise.resolve(initialVal));
}
function iterateAsync(list, fn) {
return new Promise(function(resolve, reject) {
var index = 0;
function next(val) {
if (index < list.length) {
try {
fn(list[index++]).then(next, reject);
} catch(e) {
reject(e);
}
} else {
resolve(val);
}
}
next();
});
}