如何按顺序执行一系列承诺?

Posted

技术标签:

【中文标题】如何按顺序执行一系列承诺?【英文标题】: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 的表达能力。 类似的问题,但不是特定于框架的:***.com/q/24586110/245966 【参考方案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 返回的内容存储在另一个私有变量中并将其传递给回调

【讨论】:

以上是关于如何按顺序执行一系列承诺?的主要内容,如果未能解决你的问题,请参考以下文章

如何强制 observables 按顺序执行?

如何与 forEach 同时执行承诺?

复杂承诺返回链中的承诺 catch() 顺序

使用 Gatling 将场景模块化以按顺序运行

在javascript承诺中执行的顺序是什么

如何按顺序执行操作并更新 UI