如何按顺序执行一系列承诺?
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# Task
s)。 .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
这里是函数,例如() => 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 返回的内容存储在另一个私有变量中并将其传递给回调
【讨论】:
以上是关于如何按顺序执行一系列承诺?的主要内容,如果未能解决你的问题,请参考以下文章