如何按顺序执行承诺数组?


81

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

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

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

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

但是,如何依次运行它们?

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

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

但是问题在于承诺的数量各不相同,并且承诺的数组是动态构建的。


从其他答案和我的不赞成票看来,似乎更多的人需要阅读rsvp README,其中解释说:“当您从第一个处理程序返回承诺时,真正的精妙之处来自于”。如果您不这样做,那么您真的会错过诺言的表达能力。
Michael Johnston

类似的问题,但不是特定于框架的问题:stackoverflow.com/q/24586110/245966
jakub.g

Answers:


136

如果您已经将它们放在数组中,那么它们已经在执行。如果您有一个承诺,那么它已经在执行。这与promise无关(Task即,在.Start()方法方面,它们不像C#一样)。.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
});

3
这是构造不需要参数的同质承诺树的绝佳方法。这完全等同于使用next_promise指针自己构建树,如果promise的集合在参数方面不是同质的,则需要执行此操作。只是reduce函数正在执行当前指针-为您留一点。如果您的某些事情可以同时发生,那么您还将想要构建自己的树。在承诺树中,分支是序列,叶子是并发的。
Michael Johnston

谢谢您的回答。您没错,创建承诺已经意味着它在执行,因此我的问题没有正确形成。我最终没有答应就以不同的方式解决了我的问题。
jaaksarv 2013年

1
@SSHThis首先,w。其次,先前的响应传递给.then,在本示例中,它只是被忽略了……
Esailija 2015年

3
如果这些承诺中的任何一个失败,那么错误将永远不会被拒绝,并且承诺也将永远无法解决……
Maxwelll '16

5
如果您已经将它们放在数组中,那么它们已经在执行。-此词组应为粗体+大字体。理解至关重要。
ducin

22

使用ECMAScript 2017异步功能,它将像这样完成:

async function executeSequentially() {
    const tasks = [fn1, fn2, fn3]

    for (const fn of tasks) {
        await fn()
    }
}

您现在可以使用BabelJS使用异步功能


到现在(2020年),这应该是默认方法。对于初次使用的用户,在此处需要注意以下两点可能很重要:1.一旦存在诺言,诺言就已经兑现。因此,这是非常重要的2.fn1, fn2, fn3这里是一些函数,例如() => yourFunctionReturningAPromise(),与just相对的函数yourFunctionReturningAPromise()。这也是为什么await fn()只需要必要的原因await fn。请参阅官方文档中的更多内容。很抱歉发表评论,但编辑队列已满:)
ezmegy

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


4

第二次尝试给出一个答案,在这里我尝试更加解释一下:

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

当您从第一个处理程序返回一个promise时,真棒的部分就来了……这使您可以展平嵌套的回调,并且promise的主要功能是防止带有大量异步代码的程序中的“向右漂移”。

通过从then应在其之前完成的Promise中返回较晚的Promise,这正是按顺序进行Promise的方式。

将这样的promise视为树是很有帮助的,其中的分支代表顺序的过程,而叶子代表并发的过程。

建立这样的允诺树的过程类似于建立其他种类树的非常常见的任务:维护指针或引用树中您当前正在添加分支的位置,并迭代地添加事物。

正如@Esailija在他的答案中指出的那样,如果您有一组不带参数的承诺返回函数,则可以用来reduce为您整齐地构建树。如果您曾经亲自实现过reduce,您将理解,reduce在@Esailija的答案背后进行的工作是维护对当前promise(cur)的引用,并使每个promise在其中返回下一个promise then

如果您没有一个很好的均质数组(关于它们接受/返回的参数),保证函数返回函数,或者如果您需要比简单线性序列更复杂的结构,则可以通过维护自己来构建保证树对您要在其中添加新承诺的承诺树中位置的引用:

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')来确保在下一个承诺被解雇之前就已渲染了一个承诺中完成的事情-我的说法太复杂了,也显示了一个例子。


3
这好多了,但是我觉得您仍在偏离话题。这对于许诺中的许多答案都是很常见的,人们似乎没有花时间阅读问题,相反,他们只是对自己理解的许诺中的某些方面发表评论。原始问题不涉及并行执行,甚至不涉及一点点,它确实表明仅then需要通过链接进行链接,您已经获得了很多额外的信息,这些信息隐藏了所提出问题的答案。
David McMullin

@DavidMcMullin“ ....确实清楚地表明只需要通过那时进行链接...”,但实际上,他指出了诺言的序列是动态建立的。因此,他确实需要了解如何构造树,即使在这种情况下,它只是树“线性序列”的简单子集。您仍然必须通过维护对链中最后一个承诺的引用并为其添加新的承诺来构建它。
2014年

当OP表示“诺言数量变化并且诺言数组是动态构建的”时,我很确定他/他的意思是数组的大小不是预先确定的,因此他/他不能使用简单的Promise.resolve().then(...).then(...)...,而不是数组的成长,同时承诺进行执行。当然,现在都没事了。
JLRishe

4

另一种方法是在原型上定义全局序列函数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

免责声明:请谨慎编辑原型!


2

解决这个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)的情况下,人们只想拥有原始的Promise,然后循环就可以将随后的每个Promise注入链中.then()。因此,这不是if(!chain) chain = promises[i];吗?也许我在这里还不了解。
半夜

啊-您a,b,c的确是返回Promises(而不是Promises)的函数。因此以上是合理的。但是,以这种方式包装Promises有什么效用?
半夜

2

我有类似的问题,我制作了一个递归函数,该函数依次逐个运行函数。

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

0
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返回的内容存储在另一个私有变量中,并将其传递给回调


-1

我所追求的基本上是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);
    }
});
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.