无极连锁竞赛条件

我目前正在处理一个相当简单的逻辑,用于处理排队的ZPL打印作业,这些作业存储在一个数组中,然后进行迭代,每个作业向打印机发送n份副本。

我将阵列简化为一个承诺链,在每个作业的子链中将副本发送到打印机。对打印机的调用是同步的(是的,我知道...),所以我将它们中的每一个都包装到Promise中,该Promise仅在打印机收到副本时才解决,从而确保顺序处理。

在传输失败的情况下,当前的承诺将因手工错误而被拒绝,而该错误被捕获在主链中。

到目前为止,理论上,a子链之间似乎存在一种竞争条件。

我已经尽力了,但是我根本没看到...

这里有一些简化的代码+小提琴,请注意子链如何不随后运行:

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

jsfiddle with console.logs here

任何建议都将受到高度赞赏。停留在如此琐碎的事情上真是太可惜了。

ctq1989 回答:无极连锁竞赛条件

您的pSub链都是在reduce调用期间创建并同步运行的。为了变得连续,它们需要进入then回调内部:

['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())

或者在两个循环之间仅构建一条链:

['job1','job2'].reduce((promise,outerIndex) => {
    return Array.from({length: 2}).reduce((promise,_,innerIndex) => {
        return promise.then(() => new Promise((resolve,2000)));
    },promise);
},Promise.resolve())

@jfriend当然是正确的,对于顺序任务,您只需要编写async / await代码:

for (const item of ['job1','job2']) {
    for (let i = 0; i < 2; i++) {
        await new Promise((resolve,2000));
    }
}

使用该解决方案,您还可以轻松地将try块放置在正确的级别。

,

因此,到现在为止,您已经了解了使用.reduce()对诺言进行序列化时做错了什么。在评论中,我对您提出了一些建议:

  1. 使用现代async/await(必要时进行转译)
  2. 使用已经提供异步迭代的预构建库
  3. 编写/使用一些经过测试的实用程序功能,而不用每次手动编写.reduce()循环。

如果#1或#2不切实际,我建议您制作自己的经过测试的实用程序函数,因为.reduce()的序列化方法很容易出错,对于尚未看过代码的人来说并不总是琐碎的知道它在做什么,而一次编写和测试的适当命名的实用程序函数更易于使用和理解(一旦编写了函数),显然也使重用变得实用。

对于预构建的库,Bluebird和Async都具有这些功能(个人而言,我更喜欢Bluebird),并且自己在运行旧版JS的嵌入式项目(Raspberry Pi)中使用了Bluebird。


关于经过测试的实用程序功能,可以快速使用以下几个功能。

iterateAsync()就像异步.forEach()

mapAsync()就像异步.map()

reduceAsync()就像异步.reduce()

全部将数组作为第一个参数,并将返回promise的函数作为第二个参数。这些是与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()等于数组的长度。如果您的数组很大(长成千上万个元素),那么可能需要大量内存才能预先构建所有这些承诺并将它们链接在一起。

对于您所指的“有限环境”中的非常大的阵列,您可能希望像这样手动进行更多迭代,因为它不会预先构建任何大型结构,而只会一次使用promises:

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

我想有很多方法可以实现这一目标,但就我个人而言,我总是做的是创建一个返回Promise(可能会说PromiseFactory)的函数数组。

const promiseFactoryA = () => {
  return new Promise(resolve => {
    console.log('PromiseA started...');
    setTimeout(() => {
      console.log('PromiseA resolved after 300ms');
      resolve();
    })
  },300);
}

const promiseFactories = [promiseFactoryA,promiseFactoryA];

然后,我们可以将数组传递给此函数,该函数将按顺序运行它们:

const runPromiseSequentially = promiseFactories => {
  let result = Promise.resolve();

  promiseFactories.forEach(
    (promiseFactory) => {
      result = result.then(() => promiseFactory());
    },);

  return result;
}

runPromiseSequentially(promiseFactories);

基本上,它的作用是在我们希望开始操作时要求PromiseFactory创建Promise。

示例REPL

但是,如果您可以使用asyncawait,则不必要。

本文链接:https://www.f2er.com/3098436.html

大家都在问