【问题标题】:How to execute promises sequentially, passing the parameters from an array?如何按顺序执行承诺,从数组传递参数?
【发布时间】:2019-03-08 12:06:02
【问题描述】:
var myArray = [1, 2, 3, 4, 5, 6]

function myPromise(num){
  return new Promise(res => {
    window.setTimeout(()=>{
      res(  console.log("done: " + num)  )
    },2000)
  })
}


myPromise(myArray[0])
  .then(x => myPromise(myArray[1]))
  .then(x => myPromise(myArray[2]))
  .then(x => myPromise(myArray[3]))
  .then(x => myPromise(myArray[4]))
  .then(x => myPromise(myArray[5]))

现在,如果我执行上面的语句,它将按顺序运行。在我的实际用例中,数组是动态填充的,我需要为myArray 中的每个成员执行myPromise() 函数。

如何创建一个“可暂停循环”来循环数组中的每个项目,执行 myPromise 并等待承诺得到解决,然后再继续下一次迭代?

【问题讨论】:

  • Promise.All is what you want
  • @JohnPeters 就其本身而言,这还不够,因为它不会按顺序解析。
  • 最干净、最容易阅读的代码就是在 for 循环中等待它们

标签: javascript


【解决方案1】:

如果您可以像问题中的情况一样创建与数组元素一样多的 Promise,那么您可以将 .then 的重复应用非常整齐地折叠起来:

myArray.reduce(
  (p, x) =>
    p.then(_ => myPromise(x)),
  Promise.resolve()
)

但例如,异步函数不需要:

const mapSeries = async (iterable, action) => {
  for (const x of iterable) {
    await action(x)
  }
}

mapSeries(myArray, myPromise)

作为mapSeries内置在优秀的promise库Bluebird中:

Promise.mapSeries(myArray, myPromise)

可运行的sn-p:

const myArray = [1, 2, 3, 4, 5, 6]

const sleep = ms =>
  new Promise(res => {
    setTimeout(res, ms)
  })

const myPromise = num =>
  sleep(500).then(() => {
    console.log('done: ' + num)
  })

myArray.reduce(
  (p, x) =>
    p.then(_ => myPromise(x)),
  Promise.resolve()
)

【讨论】:

  • 如果您需要像Promise.all - myArray.reduce((p, x) => p.then(results => fn(x).then(r => results.concat(r))), Promise.resolve([])).then(results => {}); 一样返回的所有结果
  • 并返回上面 mapSeries 示例的结果:const mapSeries = async (iterable, action) => { let res = [] for (const x of iterable) { res.push(await action(x)) } return res }
【解决方案2】:

不要创建一系列承诺。创建一个返回承诺的函数数组。

const f = x => new Promise(resolve => setTimeout(() => resolve(console.log(x)), 2000))

(async () => {
    for (let job of [1, 2, 3, 4, 5, 6].map(x => () => f(x)))
        await job()
})()

Promise 在创建后立即开始运行。因此,只有在完成当前的 Promise 后,才能通过构造下一个 Promise 来确保顺序执行。

【讨论】:

  • 在 for 循环子句本身内转换数组以进行迭代的巧妙技巧。我喜欢这种代码,但如果我与其他人一起工作,我会将它分成两个语句以提高可读性
【解决方案3】:

顺序:

您可以使用async await 功能顺序运行承诺。这是一个sn-p

async function chainPromiseCalls(asyncFunctions=[],respectiveParams=[]){

    for(let i=0;i<asyncFunctions.length;i++){
        const eachResult = await asyncFunctions[i](...respectiveParams[i]);
        // do what you want to do with each result 
       
    }

    return ;
}

平行:

对于并行,您可以在循环中调用每个异步函数一次,但如果您确实想获得它们的组合结果,您可以使用Promise.all

function parallelPromiseCalls(asyncFunctions=[],respectiveParams=[]){
    return Promise.all(asyncFunctions.map((func,index)=>func(...respectiveParams[index])))
       .then(resultsList=>{
        resultsList.forEach((result,index)=>{
           // do what you want to do with each result in the list
        })
        return ;
    })
}


注意:我正在考虑将各个参数作为列表列表,因为应该将多个参数传递给任何一个函数,否则如果您只需要向每个函数传递一个参数,那么您可以删除扩展运算符。

【讨论】:

    【解决方案4】:

    您也可以通过递归方法来做到这一点 - executeSequentially 调用自身:

    function createPromise(x) {
      return new Promise(res => {
        setTimeout(() => {
          console.log(x)
          res(x);
        }, x * 1000)
      })
    }
    
    function executeSequentially(array) {  
      return createPromise(array.shift())
        .then(x => array.length == 0 ? x : executeSequentially(array));
    }
    
    console.time('executeSequentially');
    executeSequentially([1, 2, 3]).then(x => {
      console.log('last value: ' + x);
      console.timeEnd('executeSequentially');
    });

    【讨论】:

    • 如果数组在执行期间可以改变,递归方法很有用。使用 Array.reduce 会立即生成所有的 executor 函数,而这里的递归方法会在最后一个 promise 被解析后生成每个 executor 函数。
    【解决方案5】:

    我知道我很晚了,我的回答与其他人发布的相似。但我想我可以发布一个更清晰的答案,这可能对任何初学者都有帮助。

    我们可以使用 Promise 工厂,而不是直接使用 Promise。由于 Promise 在使用 Promise 工厂创建后立即开始执行,因此我们延迟了 Promise 的创建。

    在这个例子中,我创建了 5 个,它在一秒钟后解析。我使用 promiseCreator 来创建承诺。现在数组promises 使用promiseCreator 创建5 个promise 实例。但是数组promiseFactoriespromiseCreator 包装在一个函数中,因此不会立即调用promise。使用时调用。

    函数executeSequentially 按顺序执行所有promiseLike

    • promise 数组被传递时,结果是promise 数组本身并行执行(实际上它们在创建时立即执行,而不是在调用此行时执行)。
    • promiseFactory 数组被传递时,结果是新的 Promise 在之前的 Promise 完成执行时创建。

    const promiseCreator = (i, time, text) => {
        return new Promise(resolve => setTimeout(
            () => resolve(console.log(`${i} ${text}`)),
            time)
        );
    }
    
    const promises = [
        promiseCreator(1, 1000, "parallel"),
        promiseCreator(2, 1000, "parallel"),
        promiseCreator(3, 1000, "parallel"),
        promiseCreator(4, 1000, "parallel"),
        promiseCreator(5, 1000, "parallel"),
    ]
    
    const promiseFactories = [
        () => promiseCreator(1, 1000, "sequential"),
        () => promiseCreator(2, 1000, "sequential"),
        () => promiseCreator(3, 1000, "sequential"),
        () => promiseCreator(4, 1000, "sequential"),
        () => promiseCreator(5, 1000, "sequential"),
    ]
    
    function executeSequentially(promiseLikeArray) {
        var result = Promise.resolve();
        promiseLikeArray.forEach(function (promiseLike) {
            result = result.then(promiseLike);
        });
        return result;
    }
    
    executeSequentially(promises)
    executeSequentially(promiseFactories)

    【讨论】:

    • 您能否阐明您的示例,说明如何按顺序调用承诺?比如说,我有一个名为“myPromise”的承诺,我想用不同的参数依次调用它 5 次。
    • 这个例子不是已经做到了。传递给 executeSequentially 的 promises 或 promiseFactory(s) 有不同的参数。你能澄清一下你在哪一部分感到困惑。乐意效劳。我也同意executeSequentially 是评估者对该功能的词选择不佳。它更像tryToExecuteSequentiallty,因为它最终取决于是通过承诺还是通过承诺工厂。
    【解决方案6】:

    你可以使用Array.reduce

    //type: [number]
    var myArray = [1, 2, 3, 4, 5, 6] //doesn't really matter
    
    //type: number -> Promise<number>
    function myPromise(num){
      return new Promise((resolve) => {
        window.setTimeout(()=>{
          resolve(console.log("done: " + num)  )
        },2000)
      })
    }
    
    //Array.reduce has type: [a] ~> ((b, a) -> b), b) -> b
    //So it can have type:
    //[number] ~> ((Promise<number>, number) -> Promise<number>), Promise<number>) -> Promise<number>
    //Therefore we need to give reduce a function that takes a Promise 
    //resolving to a number and a number which makes a new promise.
    //This is the function we want:
    
    function sequencePromises(promise, number) {
      return new Promise((resolve) => {
        resolve(promise.then(_ => myPromise(number)));
      });
    } 
    
    myArray.reduce(sequencePromises, Promise.resolve());
    

    当然,如果你有一个可能出错的承诺,或者如果你需要以前的结果,那么这种简单的方法就行不通了,所以你可能想让sequencePromises 更通用:

    function genericSequencePromises(promiseFunction) {
      return (promise, parameter) => {
        return new Promise((resolve, reject) => 
                             return promiseFunction(resolve, 
                                                    reject, 
                                                    promise, 
                                                    parameter));
      }
    }
    

    然后你可以做任何你想做的事情,只要你返回一个 Promise。

    最后,您可能会从这个小帮手中受益:

    function promiseSeries(array, reducer) {
      return array.reduce(reducer, Promise.resolve());
    }
    

    把它们放在一起:

    let sequencePromises = genericSequencePromises((resolve, reject, promise, num) => {
      resolve(promise.then(_ => console.log(`done: ${num}`)));
    }
    
    promiseSeries(myArray, sequencePromises);
    

    这样,您不仅可以处理问题中的案例,还可以处理更复杂的案例。

    【讨论】:

      【解决方案7】:

      我会使用 babel 并这样做:

          let args = [1, 2, 3];
          
          const myPromise = async x => console.log('arg:',x);
          
          const test = async () => {
            for (let task of args.map(myPromise)) 
              await task;
          }
        
          test().then(console.log('Done'));
      &lt;script src="https://unpkg.com/babel-standalone@6.24.0/babel.min.js"&gt;&lt;/script&gt;

      【讨论】:

      • 我相信您的第二个示例不能保证按顺序运行。
      【解决方案8】:

      您可以遍历元素数组并像这样传递参数:

      const arr = [1, 2, 3, 4, 5, 6];
      const MyPromiseFunction = num => new Promise((resolve, reject) => {
        // Your logic...
        setTimeout(() => num <= 4 
          ? resolve('Success!') 
          : reject('Rejected!'), 1000 * num);
      });
      const logMessage = (num, msg) => console.log(`For number ${num} promise result: ${msg}`);
      
      arr.forEach(
        num => MyPromiseFunction(num)
          .then(message => logMessage(num, message))
          .catch(reason => logMessage(num, reason))
      );

      【讨论】:

        【解决方案9】:

        const myArray = [1, 2, 1000, 4, 5000, 6]
        
        const sleep = ms =>
          new Promise(res => {
            setTimeout(res, ms)
          })
        
        const myPromise = num =>
          sleep(num).then(() => {
            console.log('done: ' + num)
          })
        
        myArray.reduce(
          (p, x) =>
            p.then(_ => myPromise(x)),
          Promise.resolve()
        )

        【讨论】:

          猜你喜欢
          • 2018-09-03
          • 2015-02-15
          • 2018-10-03
          • 1970-01-01
          • 2013-12-04
          • 2016-08-25
          相关资源
          最近更新 更多