【问题标题】:How can I execute array of promises in sequential order?如何按顺序执行一系列承诺?
【发布时间】:2013-12-04 16:47:37
【问题描述】:

我有一系列需要按顺序运行的承诺。

var promises = [promise1, promise2, ..., promiseN];

调用 RSVP.all 将并行执行它们:

RSVP.all(promises).then(...); 

但是,我怎样才能按顺序运行它们呢?

我可以像这样手动堆叠它们

RSVP.resolve()
    .then(promise1)
    .then(promise2)
    ...
    .then(promiseN)
    .then(...);

但问题是承诺的数量会发生变化,并且承诺的数组是动态构建的。

【问题讨论】:

  • 从我的其他答案和反对票看来,似乎更多的人需要阅读rsvp README,其中解释了“当您从第一个处理程序返回承诺时,真正令人敬畏的部分就出现了”。如果你不这样做,你就真的错过了 Promise 的表达能力。
  • 类似的问题,但不是特定于框架的:stackoverflow.com/q/24586110/245966

标签: javascript ember.js promise rsvp.js


【解决方案1】:

如果您已经将它们放在一个数组中,那么它们已经在执行。如果你有一个承诺,那么它已经在执行。这不是承诺的问题(即在 .Start() 方法方面,它们不像 C# Tasks)。 .all 不执行任何操作 它只是返回一个承诺。

如果你有一个 promise 返回函数的数组:

var tasks = [fn1, fn2, fn3...];

tasks.reduce(function(cur, next) {
    return cur.then(next);
}, RSVP.resolve()).then(function() {
    //all executed
});

或值:

var idsToDelete = [1,2,3];

idsToDelete.reduce(function(cur, next) {
    return cur.then(function() {
        return http.post("/delete.php?id=" + next);
    });
}, RSVP.resolve()).then(function() {
    //all executed
});

【讨论】:

  • 这是构建不需要参数的同质承诺树的绝佳方式。它完全等同于使用 next_promise 指针自己构建树,如果承诺集在参数等方面不是同质的,则需要这样做。只是 reduce 函数正在执行指向当前的指针-叶子给你。如果您的某些事情可以同时发生,您还需要构建自己的树。在 promise 树中,分支是序列,叶子是并发的。
  • 感谢您的回答。你是对的,创建一个承诺已经意味着它正在执行,所以我的问题没有正确形成。我最终在没有承诺的情况下以不同的方式解决了我的问题。
  • @SSHThis 首先,wat。其次,之前的响应被传递给.then,在这个例子中它只是被忽略了......
  • 如果这些承诺中的任何一个失败,错误将永远不会被拒绝并且承诺永远不会解决......
  • 如果您已经将它们放在一个数组中,那么它们已经在执行。 - 这句话应该是粗体+更大的字体。理解这一点至关重要。
【解决方案2】:

第二次尝试回答,我试图解释得更清楚:

首先,一些必要的背景,来自RSVP README

当你从第一个处理程序返回一个 Promise 时,真正令人敬畏的部分出现了......这允许你扁平化嵌套回调,并且是 Promise 的主要功能,它可以防止在具有大量异步代码的程序中“向右漂移” .

这正是你使承诺顺序的方式,通过从应该在它之前完成的承诺的then 返回后面的承诺。

将这样一组 Promise 视为一棵树会很有帮助,其中分支代表顺序进程,叶子代表并发进程。

构建这样一个 promise 树的过程类似于构建其他类型树的非常常见的任务:维护一个指针或引用,指向您当前在树中添加分支的位置,并迭代地添加内容。

正如@Esailija 在他的回答中指出的那样,如果您有一系列不带参数的承诺返回函数,您可以使用reduce 为您巧妙地构建树。如果您曾经为自己实现过 reduce,您就会明白,reduce 在@Esailija 的回答中在幕后所做的是保持对当前承诺 (cur) 的引用,并让每个承诺在其 @987654325 中返回下一个承诺@。

如果你没有一个很好的齐次数组(关于他们接受/返回的参数)承诺返回函数,或者如果你需要一个比简单线性序列更复杂的结构,你可以构建承诺树通过维护对要添加新 Promise 的 Promise 树中位置的引用:

var root_promise = current_promise = Ember.Deferred.create(); 
// you can also just use your first real promise as the root; the advantage of  
// using an empty one is in the case where the process of BUILDING your tree of 
// promises is also asynchronous and you need to make sure it is built first 
// before starting it

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

// etc.

root_promise.resolve();

您可以通过使用 RSVP.all 将多个“叶子”添加到承诺“分支”来构建并发和顺序流程的组合。我因过于复杂而被否决的答案就是一个例子。

您还可以使用 Ember.run.scheduleOnce('afterRender') 来确保在下一个 Promise 被触发之前,在一个 Promise 中完成的某些事情被渲染——我的 downvoted-for-being-being-too-complicated 答案也显示了一个示例那个。

【讨论】:

  • 这好多了,但是我觉得你仍然偏离主题。这对于许多关于 Promise 的答案很常见,人们似乎没有花时间阅读问题,而是简单地评论他们个人理解的 Promise 的某些方面。最初的问题不涉及并行执行,甚至一点也不涉及,它确实清楚地表明只需通过then 进行链接是需要的,您提供了很多额外的信息,这些信息隐藏了所问问题的答案.
  • @DavidMcMullin “....它确实清楚地表明需要简单地通过 then 链接...”但实际上他表示承诺的序列是动态建立的。所以他确实需要了解如何构造一棵树,即使在这种情况下它是树“线性序列”的简单子集。您仍然必须通过维护对链中最后一个 Promise 的引用并向其添加新 Promise 来构建它。
  • 当 OP 说“承诺的数量变化并且承诺的数组是动态构建的”时,我很确定他/她的意思是数组的大小不是预先确定的,而 s/因此,他不能使用简单的Promise.resolve().then(...).then(...)...,并不是说数组正在增长承诺正在执行。当然,现在一切都没有实际意义。
【解决方案3】:

我追求的东西本质上是 mapSeries,我碰巧正在映射一组值,我想要结果。

所以,这就是我所能得到的,以帮助其他人在未来寻找类似的东西..

(注意上下文是一个 Ember 应用程序)。

App = Ember.Application.create();

App.Router.map(function () {
    // put your routes here
});

App.IndexRoute = Ember.Route.extend({
    model: function () {
            var block1 = Em.Object.create({save: function() {
                return Em.RSVP.resolve("hello");
            }});
    var block2 = Em.Object.create({save: function() {
            return Em.RSVP.resolve("this");
        }});
    var block3 = Em.Object.create({save: function() {
        return Em.RSVP.resolve("is in sequence");
    }});

    var values = [block1, block2, block3];

    // want to sequentially iterate over each, use reduce, build an array of results similarly to map...

    var x = values.reduce(function(memo, current) {
        var last;
        if(memo.length < 1) {
            last = current.save();
        } else {
            last = memo[memo.length - 1];
        }
        return memo.concat(last.then(function(results) {
            return current.save();
        }));
    }, []);

    return Ember.RSVP.all(x);
    }
});

【讨论】:

    【解决方案4】:

    使用 ECMAScript 2017 异步函数可以这样完成:

    async function executeSequentially() {
        const tasks = [fn1, fn2, fn3]
    
        for (const fn of tasks) {
            await fn();
        }
    }
    

    您现在可以使用BabelJS 来使用异步函数

    【讨论】:

    • 这应该是现在(2020 年)的默认方法。对于第一次使用的用户来说,在这里注意两件事可能很重要: 1. 一旦一个承诺存在,它就已经开始了。所以非常重要的是 2.fn1, fn2, fn3 这里是函数,例如() =&gt; yourFunctionReturningAPromise() 而不是 yourFunctionReturningAPromise()。这也是为什么需要await fn() 而不是await fn 的原因。查看更多in the official docs。很抱歉作为评论发布,但编辑队列已满 :)
    【解决方案5】:

    我遇到了类似的问题,我做了一个递归函数,依次运行函数。

    var tasks = [fn1, fn2, fn3];
    
    var executeSequentially = function(tasks) {
      if (tasks && tasks.length > 0) {
        var task = tasks.shift();
    
        return task().then(function() {
          return executeSequentially(tasks);
        });
      }
    
      return Promise.resolve();  
    };
    

    如果您需要从这些函数中收集输出:

    var tasks = [fn1, fn2, fn3];
    
    var executeSequentially = function(tasks) {
      if (tasks && tasks.length > 0) {
        var task = tasks.shift();
    
        return task().then(function(output) {
          return executeSequentially(tasks).then(function(outputs) {
            outputs.push(output);
    
            return Promise.resolve(outputs);  
          });
        });
      }
    
      return Promise.resolve([]);
    };
    

    【讨论】:

      【解决方案6】:

      解决这个问题需要for 循环:)

      var promises = [a,b,c];
      var chain;
      
      for(let i in promises){
        if(chain) chain = chain.then(promises[i]);
        if(!chain) chain = promises[i]();
      }
      
      function a(){
        return new Promise((resolve)=>{
          setTimeout(function(){
            console.log('resolve A');
            resolve();
          },1000);
        });
      }
      function b(){
        return new Promise((resolve)=>{
          setTimeout(function(){
            console.log('resolve B');
            resolve();
          },500);
        });
      }
      function c(){
        return new Promise((resolve)=>{
          setTimeout(function(){
            console.log('resolve C');
            resolve();
          },100);
        });
      }
      

      【讨论】:

      • 为什么if(!chain) chain = promises[i](); 后面有一个()?我认为在链为空(迭代 0)的情况下,人们只想拥有原始承诺,然后循环可以将每个后续承诺注入链的.then()。因此,这不是if(!chain) chain = promises[i]; 吗?可能我这里有什么不明白的地方。
      • 啊 - 你的 a,b,c 确实是返回 Promise 的函数,而不是 Promises。所以上面说的很有道理。但是以这种方式包装 Promise 有什么用处呢?
      【解决方案7】:

      2017 年的 ES7 方式。

        <script>
        var funcs = [
          _ => new Promise(resolve => setTimeout(_ => resolve("1"), 1000)),
          _ => new Promise(resolve => setTimeout(_ => resolve("2"), 1000)),
          _ => new Promise(resolve => setTimeout(_ => resolve("3"), 1000)),
          _ => new Promise(resolve => setTimeout(_ => resolve("4"), 1000)),
          _ => new Promise(resolve => setTimeout(_ => resolve("5"), 1000)),
          _ => new Promise(resolve => setTimeout(_ => resolve("6"), 1000)),
          _ => new Promise(resolve => setTimeout(_ => resolve("7"), 1000))
        ];
        async function runPromisesInSequence(promises) {
          for (let promise of promises) {
            console.log(await promise());
          }
        }
        </script>
        <button onClick="runPromisesInSequence(funcs)">Do the thing</button>
      

      这将按顺序(一个接一个)执行给定的函数,而不是并行执行。参数promises是一个函数数组,返回Promise

      上面代码的 Plunker 示例:http://plnkr.co/edit/UP0rhD?p=preview

      【讨论】:

        【解决方案8】:

        另一种方法是在Promise 原型上定义一个全局序列函数。

        Promise.prototype.sequence = async (promiseFns) => {
          for (let promiseFn of promiseFns) {
            await promiseFn();
          }
        }
        

        然后你可以在任何地方使用它,就像Promise.all()

        示例

        const timeout = async ms => new Promise(resolve =>
          setTimeout(() => {
            console.log("done", ms);
            resolve();
          }, ms)
        );
        
        // Executed one after the other
        await Promise.sequence([() => timeout(1000), () => timeout(500)]);
        // done: 1000
        // done: 500
        
        // Executed in parallel
        await Promise.all([timeout(1000), timeout(500)]);
        // done: 500
        // done: 1000
        

        免责声明:编辑原型时要小心!

        【讨论】:

          【解决方案9】:
          export type PromiseFn = () => Promise<any>;
          
          export class PromiseSequence {
            private fns: PromiseFn[] = [];
          
            push(fn: PromiseFn) {
              this.fns.push(fn)
            }
          
            async run() {
              for (const fn of this.fns) {
                await fn();
              }
            }
          }
          

          然后

          const seq = new PromiseSequence();
          seq.push(() => Promise.resolve(1));
          seq.push(() => Promise.resolve(2));
          seq.run();
          

          也可以将 promise 返回的内容存储在另一个私有变量中并将其传递给回调

          【讨论】:

            猜你喜欢
            • 2018-09-03
            • 2017-07-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-05-30
            • 1970-01-01
            • 2016-02-01
            相关资源
            最近更新 更多