简体   繁体   中英

Running asynchronous functions in series with promises

I am trying to run multiple asynchronous tasks in series using promises. Each task should run right after the previous one finishes. This is simplified example what I have tried:

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 ]

I would expect order to be equal [ 1, 2, 3, 4, 5 ] in the callback function. However I got those strange results ( [ 1, 3, 5 ] in then callback and [ 1, 3, 5, 2, 4 ] in delayed function). What am I missing?

When you write something like

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

it's executed right away, meaning it runs now , and resolves in 0.1 seconds.
It doesn't matter that you write it inside an array, the functions are still ran right now , and the promises are returned as the values in the array.

In other words, all three promise calls run in parallell, they all run right away, with just milliseconds apart, and resolve at the given timen in the internal timer, from now !

If you want to run one promise after the other, they have to somehow be wrapped so they don't run now , but whenever they are called, for instance something like

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

(the underscore is valid ES2015 shorthand for an anonymous arrow function)

where each array value is an anonymous function that can be called, and when called the promise constructor runs and returns the promise.

To recursively call the functions in serial, a recursive function call is the easiest, where the next function is called when the current is finished etc.

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

FIDDLE


Edit:

You could also Array.reduce the way you're already doing it, now that you have functions that returns promises

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

You're missing the fact that when you're using setTimeout , the callbacks (that push 2 , 4 and log order ) will be executed on the next iteration of the event loop , ie the next 'tick'. While all other functions (the Promise constructors and the reduce callback) are being executed immediately , ie in the current '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

Only After steps 1 through 7 happen, all callbacks that where pushed into the event queue (by setTimeout ) will be executed, ie pushed on the call stack, clearing the event queue and eventually clearing the call stack, after said callbacks execute (steps 8, 9 and 10).

Note that functions are only popped off the event queue when the call stack is empty.

This is a promise-less and slightly strange way of doing things, but it seems to work:

"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]()

每个异步函数都有一个同步部分,该设置通向promise的(同步)返回。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM