0

Please help me explain why log result is different in 2 ways:

Way 1: Log sequentially every 1 second

Way 2: After 1 second all elements are logged.

// Way 1
let sequence = Promise.resolve();
[1,2,3,4].forEach((val)=> {
    sequence = sequence.then(()=> {
        return new Promise(resolve => setTimeout(resolve, 1000, val))
    }).then(console.log);
})


//Way 2
let sequence2 = Promise.resolve();
[5,6,7,8].map(val => {
    return new Promise(resolve => setTimeout(resolve, 1000, val));
}).forEach(promise => {
    sequence2 = sequence2.then(() => promise).then(console.log);
});

Edited: describe incorrectly log results in way 2

windluffy
  • 13
  • 3
  • Your Map function is synchronous, therefore executing everything on the spot. What I mean here by executing, is just adding your promises to the future call stack, and moving on because promises are asynchronous. – Paul Ghiran Sep 06 '17 at 14:49

2 Answers2

1

You have sequenced your timers in "way 1" (they each fire a second apart) and run your timers in parallel in "way 2" so the timers all fire at about the same time. Here's the more detailed explanation:

In "way 1", you create a chain of promises with sequence = sequence.then() and the setTimeout() is called from within the sequence. So, timer 2 won't start until after timer 1 fires and so on. You will get each individual timer running truly sequential with each firing about 1 second apart.

In "way 2", you start all the timers at once in your .map(), so all the timers run in parallel, not in sequence. You are then trying to force them into a sequence with your sequence2 = sequence2.then() loop, but the async operations were already started in parallel so all this sequential loop doesn't really accomplish anything more than a Promise.all() would in terms of timing.

If you run each of these two snippets, they will log exactly when the timers fire and you can see the difference between the two.

Here, in a version of "way 1" that logs the time sequence of each timer, you can see the timers firing about 1 second apart:

// way 1
let startTime = Date.now();

function log() {
    let args = Array.from(arguments);
    // calc time since startTime
    let delta = (Date.now() - startTime) / 1000;
    args.unshift(delta + ": ");
    console.log.apply(console, args);
}

function delay(t, val) {
    return new Promise(resolve => {
        setTimeout(() => {
            log("timer fire");
            resolve(val);           
        }, t);
    });
}

// Way 1
let sequence = Promise.resolve();
[1,2,3,4].forEach((val)=> {
    sequence = sequence.then(()=> {
        return delay(1000, val);
    }).then(console.log);
});
sequence.then(() => {
    log("all done");
})

Here, in a version of "way 2" that logs the time sequence of each timer, you can see the timers firing at all about the same time:

// way 2
let startTime = Date.now();

function log() {
    let args = Array.from(arguments);
    // calc time since startTime
    let delta = (Date.now() - startTime) / 1000;
    args.unshift(delta + ": ");
    console.log.apply(console, args);
}

function delay(t, val) {
    return new Promise(resolve => {
        setTimeout(() => {
            log("timer fire");
            resolve(val);           
        }, t);
    });
}

//Way 2
let sequence2 = Promise.resolve();
[5,6,7,8].map(val => {
    return new delay(1000, val);
}).forEach(promise => {
    sequence2 = sequence2.then(() => promise).then(console.log);
});

sequence2.then(() => {
    log("all done");
});

When running each snippet, not in particular the time it takes to get to the "all done" message. The first one serializes four one second timers so it takes about 4 seconds to run. The second one runs all the timers in parallel so it takes about 1 second to run.


Additional explanation:

When you do this:

let arrayOfPromoises = [5,6,7,8].map(val => {
    return new Promise(resolve => setTimeout(resolve, 1000, val));
});

That code executes return new Promise(resolve => setTimeout(resolve, 1000, val)); four times in a row, one right after the other with no delays. Since each time that code is execute, it creates a new Promise object, you end up with an array of four promises.

Now, inside of that code you have this:

new Promise(resolve => setTimeout(resolve, 1000, val));

The Promise executor function (that's what the callback is called that you pass to the Promise constructor) is called IMMEDIATELY. There is no waiting. So, not only have you created four promises, but you've also started four times that are all running at once. These timers are already started. No amount of structuring with your sequence2 = sequence2.then() loop will change that. The timers are already running.

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • "*sequential loop doesn't really accomplish anything more than a `Promise.all()` would*" - their behaviour differs between what the fulfillment value is and how rejections are handled. I'd argue that the sequential loop is *more* wrong :-) – Bergi Sep 06 '17 at 19:45
  • @Bergi - I just meant in terms of timing. Obviously everything else is different. Edited to clarify. – jfriend00 Sep 06 '17 at 19:54
  • I really do not understand why .map() runs the whole promises in parallel (see the @Shyam Babu's answer) – windluffy Sep 06 '17 at 20:48
  • @windluffy - `.map()` is a synchronous function. The promises you create are non-blocking so they just create the promise and immediately return and they resolve some time later. So, you immediately start all the timers and end up with an array of promises that represent async operations that have all been started. I don't follow what part of that you don't understand. You do realize that `setTimeout()` is non-blocking, right? It schedules the timer for some future time and then immediately returns. – jfriend00 Sep 06 '17 at 20:51
  • I know setTimout() is non-blocking. In way 2, I really try to "force them into a sequence with your sequence2 = sequence2.then() loop", but I still don't understand why "the async operations were already started in parallel" – windluffy Sep 06 '17 at 21:02
  • @windluffy - I added some more explanation to the end of my answer. – jfriend00 Sep 06 '17 at 21:20
0

To understand this you need to understand that a promise just wait's for something to resolve, so until a resolve is got the following (.then) code chain wont execute

For the first, way lets use different variables instead of the same sequence variable, then the code becomes.

var firstSequence = Promise.resolve()
                        .then(()=> {return new Promise(resolve => setTimeout(resolve, 1000, 1))})
                        .then(console.log);
var secondSequence = firstSequence
                        .then(()=> {return new Promise(resolve => setTimeout(resolve, 1000, 2))})
                        .then(console.log);
var thirdSequence = secondSequence
                        .then(()=> {return new Promise(resolve => setTimeout(resolve, 1000, 3))})
                        .then(console.log);
var foruthSequence = thirdSequence
                        .then(()=> {return new Promise(resolve => setTimeout(resolve, 1000, 4))})
                        .then(console.log);                       

from this you can see that the timeout calls do not happen before a promise is resolved. So they appear to be sequential and they keep getting pushed to wait for one second.

But the second form is equivalent to this.

function makePromise(val) {
    return new Promise(resolve => setTimeout(resolve, 1000, val)); 
}
var storageArray  = [makePromise(1), makePromise(2), makePromise(3), 
makePromise(4)];

we have already pushed all the setimout calls together in one go, so after one second all promises are already resolved. so (.then) wont need to wait wait.

 var firstSequence = Promise.resolve()
                    .then(()=> {return storageArray[0]})
                    .then(console.log);
 var secondSequence = firstSequence
                    .then(()=> {return storageArray[1]})
                    .then(console.log);
 var thirdSequence = secondSequence
                    .then(()=> {return storageArray[2]})
                    .then(console.log);
 var foruthSequence = thirdSequence
                    .then(()=> {return storageArray[3]})
                    .then(console.log); 

So even though they appear to be similar to the first syntax the promises are all, already resolved so no need to wait.

Shyam Babu
  • 1,069
  • 7
  • 14
  • Although you had transformed the snippet code, I don't understand why "pushed all the setimout calls together in one go". I see settimeout is called in separate promise – windluffy Sep 06 '17 at 20:54