【问题标题】:Running asynchronous functions in series with promises与 Promise 串联运行异步函数
【发布时间】:2016-07-12 11:03:18
【问题描述】:

我正在尝试使用 Promise 连续运行多个异步任务。每个任务都应该在前一个任务完成后立即运行。这是我尝试过的简化示例:

var order = [];
var tasks = [
    new Promise(resolve => {
        order.push(1);
        setTimeout(() => {
            order.push(2)
            resolve();
        }, 100);
    }),
    new Promise(resolve => {
        order.push(3);
        setTimeout(() => {
            order.push(4)
            resolve();
        }, 100);
    }),
    new Promise(resolve => {
        order.push(5);
        resolve();
    })
];

tasks.reduce((cur, next) => cur.then(next), Promise.resolve()).then(() => {
    console.log(order); // [ 1, 3, 5 ]
});
setTimeout(() => console.log(order), 200); // [ 1, 3, 5, 2, 4 ]

我希望order 在回调函数中等于[ 1, 2, 3, 4, 5 ]。但是我得到了那些奇怪的结果([ 1, 3, 5 ]then 回调和[ 1, 3, 5, 2, 4 ] 在延迟函数中)。我错过了什么?

【问题讨论】:

  • 我在 Angular 中使用 $q.serial 从这里做类似的事情:codeducky.org/q-serial 您可以摆脱特定于角度代码的东西并对其进行调整以满足您的需求。您还可以查看async 库中的waterfall 方法:github.com/caolan/async
  • reduce 函数在继续处理数组中的下一个条目之前不会等待前一个 promise 解决,因此您会在循环中立即触发所有三个推送。 setTimeout 会延迟 resolve(),但不会延迟 promise 内的操作
  • 你写new Promise(function() {...的那一刻,承诺回调正在运行(你正在使用箭头函数,但相同)。如果你想串行而不是并行运行它们,你不能像那样在数组中创建承诺,你需要想出一些不同的东西

标签: javascript promise ecmascript-6 es6-promise


【解决方案1】:

当你写类似

的东西时
new Promise(resolve => {
    order.push(1);
    setTimeout(() => {
        order.push(2)
        resolve();
    }, 100);
});

它立即执行,这意味着它现在运行,并在 0.1 秒内解决。
将它写在数组中并不重要,函数仍在运行现在,并且承诺作为数组中的值返回。

换句话说,所有三个 Promise 调用都是并行运行的,它们都立即运行,仅相隔几毫秒,并在内部计时器中的给定时间从 现在开始解决!

如果你想一个接一个地运行一个promise,它们必须以某种方式被包装,这样它们就不会运行现在,但是每当它们被调用时,例如像

var tasks = [
    _ => new Promise(resolve => {
            order.push(1);
            setTimeout(() => {
                order.push(2)
                resolve();
            }, 100);
    }),
    _ => new Promise(resolve => {
            order.push(3);
            setTimeout(() => {
                order.push(4)
                resolve();
            }, 100);
    }),
    _ => new Promise(resolve => {
            order.push(5);
            resolve();
    }),
];

(下划线是匿名箭头函数的有效 ES2015 简写)

其中每个数组值都是一个可以调用的匿名函数,当被调用时,promise 构造函数运行并返回promise。

要递归调用串行函数,递归函数调用是最简单的,当当前函数完成时调用下一个函数等。

(function iterate(i) {
    tasks[i]().then(() => {          // when done
        if (tasks[++i]) iterate(i);  // call the next one
    });
})(0);

FIDDLE


编辑:

你也可以 Array.reduce 就像你已经在做的那样,现在你有返回承诺的函数

tasks.reduce((cur, next) => cur.then(next), Promise.resolve()).then(() => {
    // all done, chain is complete !
});

【讨论】:

  • 非常感谢!你准确地解释了我做错了什么。你能看到使用这种递归解决方案比使用 reduce 有什么好处吗?
  • 似乎没有什么可以减少的?你有一系列承诺,你真的不想把它减少到一个,你只想按顺序调用它们,并且值在单独的 order 数组中,所以你似乎不想减少也下降了?
  • 当我将数组中的任务更改为返回承诺的函数时,reduce 的解决方案有效。使用 reduce 版本,我可以使用 then 添加在所有任务完成后执行的回调
  • 是的,看起来你是对的,reduce 有效,这是一个巧妙的技巧。你有tasks.reduce((cur, next) => cur.then(next), Promise.resolve()),其中一个已解决的promise作为初始值传入,然后当cur函数完成时它会继续调用next函数,并且当顺序调用全部完成时,减少的最后一个promise被解决.递归函数调用通常用于此目的,您可以使用 Promise.all 检查所有承诺是否已解决,但它与您已经拥有的 reduce 相同,所以坚持下去。
【解决方案2】:

这是一种没有承诺且有点奇怪的做事方式,但它似乎有效:

"use strict";
var order = [];
var i = 0;
function next(){
    if(tasks[++i]) tasks[i]()
}
var tasks = [
    function() {
        order.push(1);
        setTimeout(() => {
            order.push(2)
            next()
        }, 100);
    },
    function() {
        order.push(3);
        setTimeout(() => {
            order.push(4)
            next();
        }, 100);
   },
   function() {
        order.push(5);
        next()
        console.log(order)
    }
];

tasks[0]()

【讨论】:

    【解决方案3】:

    您错过了这样一个事实,即当您使用setTimeout 时,回调(推送24 和日志order)将在事件的下一次迭代中执行循环,即下一个“滴答”。而所有其他函数(Promise 构造函数和reduce 回调)正在立即执行,即在当前的“tick”中。

    var order = [];
    var tasks = [
        new Promise(resolve => {
            order.push(1); // 1. callback executes immediately pushing 1 into order
            setTimeout(() => { // 2. setTimeout executes, pushing the callback into the event queue after 100ms
                order.push(2) // 8. callback executes, pushing 2 into order
                resolve();
            }, 100);
        }),
        new Promise(resolve => {
            order.push(3); // 3. callback executes immediately pushing 3 into order
            setTimeout(() => { // 4. setTimeout executes, pushing the callback into the event queue after 100ms
                order.push(4) // 9. callback executes, pushing 4 into order
                resolve();
            }, 100);
        }),
        new Promise(resolve => {
            order.push(5); // 5. callback executes immediately pushing 5 into order 
            resolve();
        })
    ];
    
    console.log(order); // [ 1, 3, 5 ]    
    
    // 6. reduce executes immediately, executes Promise.resolve which logs order and then loops through order and executes the callback everytime
    tasks.reduce((cur, next) => cur.then(next), Promise.resolve()).then(() => {
        console.log(order); // [ 1, 3, 5 ]
    });
    
    setTimeout(() => {
        console.log(order); // 10. callback executes and logs order
    }, 200); // 7. setTimeout executes, pushing the callback into the event queue after 200ms
    

    只有步骤 1 到 7 发生后,所有被推入事件队列(由setTimeout)的回调将被执行,即推入调用堆栈,清除事件队列并最终在所述回调执行后(步骤 8、9 和 10)清除调用堆栈。

    请注意,只有在调用堆栈为空时,函数才会从事件队列中弹出。

    【讨论】:

      【解决方案4】:

      每个异步函数都有一个同步部分,即导致承诺(同步)返回的设置。

      【讨论】:

        猜你喜欢
        • 2018-04-13
        • 2021-02-15
        • 2020-06-08
        • 1970-01-01
        • 2019-10-11
        • 1970-01-01
        • 2021-09-23
        • 2022-11-16
        • 1970-01-01
        相关资源
        最近更新 更多