promise.all在forEach循环中 - 一次触发的所有内容

时间:2016-05-07 14:03:44

标签: javascript node.js asynchronous promise es6-promise

在Node应用程序中,我需要以同步方式迭代某些项目,但循环内部的一些操作是异步的。我的代码现在看起来像这样:

someAPIpromise().then((items) => {
   items.forEach((item) => {
      Promise.all[myPromiseA(item), myPromiseB(item)]).then(() => {
         doSomethingSynchronouslyThatTakesAWhile();
      });
    }
}

items是1的数组时,这会产生奇迹。但是,一旦有多个项目,promise.all()将立即为阵列中的每个项目启动,而无需等待操作在循环结束。

所有这些......我怎样才能确保数组中每个项目的整个操作是同步运行的(即使某些操作是异步并返回一个promise)?

非常感谢!

<磷>氮

5 个答案:

答案 0 :(得分:4)

您正在构建多个承诺,但它们都是异步的。你构建了Promise1,Promise2,Promise3,......但是一旦他们在野外,他们都会同时开火。如果你想要同步行为,你必须将它们链接在一起,所以Promise1的.then()执行Promise2,依此类推。在过去,我已经使用了Array.reduce。

someAPIpromise().then((items) => {
    items.reduce((accumulator, current) =>
        accumulator.then(() =>
             Promise.all[myPromiseA(item), myPromiseB(item)]).then(() => 
                 doSomethingSynchronouslyThatTakesAWhile();
             )
        )
    , Promise.resolve());

如果您愿意,可以将其写为辅助函数,这可能会使事情更加清晰。

function execSequentially (arr, func) {
    return arr.reduce(
        (accumulator, current) => accumulator.then(() => func(current)), 
        Promise.resolve());
}

该功能以

执行
execSequentially(items, item => console.log(item));

当然用你想要的东西替换console.log。

帮助函数方法对变更的侵入性也较小。帮助程序应用于您的原始代码:

someAPIpromise().then((items) => {
   execSequentially(items, (item) =>
      Promise.all[myPromiseA(item), myPromiseB(item)]).then(() => {
         doSomethingSynchronouslyThatTakesAWhile();
      });
   );
});

答案 1 :(得分:1)

您应该可以删除.forEach();使用Array.prototype.reduce()Promise值数组返回Promise.all()。如果带有items的元素是一个函数,则调用函数,否则换行Promise.resolve(),这应该返回与items数组中的结果相同的结果

请参阅Promise.all()

  

Promise.all传递来自所有承诺的值数组   它被传递的可迭代对象。值数组维持着   原始可迭代对象的顺序,而不是承诺的顺序   解决了。​​如果迭代数组中传递的东西不是   保证,它由Promise.resolve转换为一个。

var arr = [1, // not asynchronous
  function j() {
    return new Promise(function(resolve) {
      setTimeout(function() {
        resolve(2)
      }, Math.floor(Math.random() * 10000))
    })
  }, // asynchronous
  3, // not asynchronous
  function j() {
    return new Promise(function(resolve) {
      setTimeout(function() {
        resolve(4)
      }, Math.floor(Math.random() * 3500))
    })
  }, // asynchronous
  5, // not asynchronous
  Promise.resolve(6), // asynchronous
  7
];

Promise.all(arr.reduce(function(p, next) {
    var curr = Promise.resolve(typeof next === "function" ? next() : next);
    return p.concat.apply(p, [curr.then(function(data) {
      console.log(data);
      return data
    })]);
  }, []))
  .then(function(data) {
    console.log("complete", data)
  })

另一种方法是使用Array.prototype.shift()Promise.resolve().then(),递归

function re(items, res) {
  if (items.length) {
    var curr = items.shift();
    return Promise.resolve(
      typeof curr === "function" 
      ? curr() 
      : curr
    ).then(function(data) {
      // values from `arr` elements should be logged in sequential order
      console.log(data);
      res.push(data)
    }).then(re.bind(null, items, res))
  } else {
    return ["complete", res]
  }
}

var _items = arr.slice(0);

re(_items, [])
.then(function(complete) {
  console.log(complete)
})

var arr = [1, // not asynchronous
  function j() {
    return new Promise(function(resolve) {
      setTimeout(function() {
        resolve(2)
      }, Math.floor(Math.random() * 10000))
    })
  }, // asynchronous
  3, // not asynchronous
  function j() {
    return new Promise(function(resolve) {
      setTimeout(function() {
        resolve(4)
      }, Math.floor(Math.random() * 3500))
    })
  }, // asynchronous
  5, // not asynchronous
  Promise.resolve(6), // asynchronous
  7
];

function re(items, res) {
  if (items.length) {
    var curr = items.shift();
    return Promise.resolve(
      typeof curr === "function" 
      ? curr() 
      : curr
    ).then(function(data) {
      // values from `arr` elements should be logged in sequential order
      console.log(data);
      res.push(data)
    }).then(re.bind(null, items, res))
  } else {
    return ["complete", res]
  }
}
var _items = arr.slice(0);
re(_items, [])
  .then(function(complete) {
    console.log(complete)
  })

答案 2 :(得分:1)

好吧......我们能够让它发挥作用的方式:array.reduce()在Promises的帮助下。最终结果:

myAsyncAPIcall.then(items => {
    items.reduce((current, nextItem) => {
        return current.then(() => {
          return new Promise(res => {
             Promise.all([myPromiseA(nextItem), myPromiseB(nextItem]).then(() => {
               someSynchronousCallThatTakesAWhile(nextItem);
               res();
             }).catch(err => {
                   console.log(err);
             });
          });
        });
    }, Promise.resolve())
})

它的工作方式是,通过将数组的每个项目包装在自己的Promise(resolve, reject)中,我们可以确保每次迭代都是同步运行的,因为一次迭代的完成将触发解决下一个Promise的需要, 等等等等。在每个promise解析中,调用可以根据需要异步启动,并且知道它们只会作用于父承诺,直到完成为止。

我希望这对人们有所帮助!

答案 3 :(得分:0)

保持forEach ......

var stopAllProcessingOnServerLowValue= false;

function someAPIpromise(){
    var arr = [
        {id:123, urlVal:null},
        {id:456, urlVal:null},
        {id:789, urlVal:null},
        {id:101112, urlVal:null}
    ];

    return new Promise(function(resolve){
        setTimeout(function(){
            resolve(arr)
        }, 3000);
    })
}

function extractSomeValueRemotely(url){
    return new Promise(function(resolve, reject){
        console.log("simulate an async connection @ %s to request a value", url);
        setTimeout(function(){
            var someRandom = Math.round(Math.random()*7) + 1;
            console.log("%s responded with %s", url, someRandom);
            if(someRandom > 4){
                resolve(someRandom);
            }
            else{
                var issue = "Urls result is too low ("+someRandom+" <= 4).";
                console.warn(issue+".It will be set to -1");
                if(stopAllProcessingOnServerLowValue){
                    reject(issue+".Operation rejected because one or mole server results are too low ["+someRandom+"].");
                }
                else{
                    resolve(-1);
                }
            }
        }, 1500*Math.round(Math.random()*7) + 1);
    });
}

function addAnotherExtraParamToItem(_item){
    return new Promise(function(resolve, reject){
        setTimeout(function(){
            console.log("setting extra2 on %s", _item.id);
            _item['extra'] = "additional_processing_"+_item.id;
            resolve(_item);
        }, 1500*Math.round(Math.random()*5) + 1);
    });
}

function addOrderIndexToItem(_item, _order){
    return new Promise(function(resolve, reject){
        setTimeout(function(){
            console.log(">> setting order %s on %s",_order,  _item.id);
            _item['order'] = _order;
            resolve(_item);
        }, 1500*Math.round(Math.random()*3) + 1);
    });
}

someAPIpromise().then(function(items){

    var perItemPromises = [];
    items.forEach(function(item, idx){

        perItemPromises.push(

            new Promise(function(pulseItemResolve, pulseItemReject){
                var itemStepsPromises =  [];
                itemStepsPromises.push(addAnotherExtraParamToItem(item));

                itemStepsPromises.push(extractSomeValueRemotely("http://someservice:777/serve-me")
                    .catch(
                        function(reason){
                            //the entire item will be rejected id
                            pulseItemReject(reason);
                        })
                );

                itemStepsPromises.push(addOrderIndexToItem(item, idx));

                //promise that ensure order of execution on all previous async methods
                Promise.all(itemStepsPromises).then(function(values){
                    //0 - first is result from addAnotherExtraParamToItem
                    var theItem = values[0]; //it returns the item itself
                    //urlVal has not been set yet

                    // 1 - second promise return the url result
                    var serverResult = values[1];

                    //2 - third promise add the order index but we do not care to inspect it because theItem reference in value[0] has been already updated.
                    // console.info(values[2]);

                    //sets the url result in the item
                    theItem.urlVal = serverResult;
                    console.log("urlVal set to:", theItem.urlVal);

                    //resolve the prepared item
                    pulseItemResolve(theItem);

                });
            })
                .catch(function(reason){
                    //escalate error
                    throw new Error(reason);
                })
        )

    });

    Promise.all(perItemPromises).then(function(resultsInAllItems){
        console.info("Final results:");
        console.info(resultsInAllItems);
    }).catch(function(finalReject){
        console.error("Critical error:",finalReject);
    })


});

答案 4 :(得分:0)

经过大量研究,对我来说最明确的答案就在这里......

我已经阅读了一系列解决方案,以获得有用的纯JavaScript(无插件) - Promise Iterator - 可以在我的项目中轻松使用(一行),最后我还是Salketer找到了 this solution

function one_by_one(objects_array, iterator, callback) {
    var start_promise = objects_array.reduce(function (prom, object) {
        return prom.then(function () {
            return iterator(object);
        });
    }, Promise.resolve()); // initial
    if(callback){
        start_promise.then(callback);
    }else{
        return start_promise;
    }
}

有关使用的详细信息和示例,请访问the link

它还允许直接处理回调。

这是我在经过多年努力进行Promise迭代并从许多问题,博客和官方网站测试MULTIPLE解决方案后找到的最合乎逻辑且可重复使用的方法。

如果您还在努力争取明确的答案,请尝试一下。