Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does JS promise print all the resolve first then rejects second

Why does promise print all the success first then the rejects after, even though i wrote the code for it to appear randomly

var errors = 0;
var successes = 0;
var p1;
for (var i = 0; i < 10; i++) {
  p1 = new Promise(function(resolve, reject) {
    var num = Math.random();
    if (num < .5) {
      resolve(num);
    } else {
      reject(num)
    }
  });

  p1.then(function success(res) {
    successes++;
    console.log("*Success:* " + res)
  }).catch(function error(error) {
    errors++
    console.log("*Error:* " + error)
  });
}

OUTPUT

VM331:16 *Success:* 0.28122982053146894
VM331:16 *Success:* 0.30950619874924445
VM331:16 *Success:* 0.4631742111936423
VM331:16 *Success:* 0.059198322061176256
VM331:16 *Success:* 0.17961879374514966
VM331:16 *Success:* 0.24027158041021068
VM331:19 *Error:* 0.9116586303879894
VM331:19 *Error:* 0.7676575145407345
VM331:19 *Error:* 0.5289135948801782
VM331:19 *Error:* 0.5581542856881132
like image 893
Tenzin Choklang Avatar asked Aug 23 '18 06:08

Tenzin Choklang


People also ask

Does Promise all resolve in order?

all() method is the order of the maintained promises. The first promise in the array will get resolved to the first element of the output array, the second promise will be a second element in the output array and so on.

How do you deal with resolve and reject in promises?

then() method should be called on the promise object to handle a result (resolve) or an error (reject). It accepts two functions as parameters. Usually, the . then() method should be called from the consumer function where you would like to know the outcome of a promise's execution.

What is the problem with promises in Javascript?

Apart from how the API is structured - promises have another major flaw: they treat unintentional native runtime exceptions and intentional rejected promises - which are two drastically different intentions - in the same "path".

Can a JS Promise be rejected more than once?

It is not safe to resolve/reject promise multiple times. It is basically a bug, that is hard to catch, becasue it can be not always reproducible.


1 Answers

It has to do with how asynchronous code works

.then().catch() - has to wait for the queue twice (hmm, I need to explain this)

.then() only once

Promises are by nature asynchronous ... in your code, when a promise resolves, the .then code is put on the microtask? queue ... and processed in turn

when it rejects, as .then has no onRejected callback, so, the next handler in the promise chain .catch in your case is added to the microtask? queue - but by then, all the .then codes have been executed

try using .then(onSuccess, onError) and you'll get what you expect

var errors = 0;
var successes = 0;
var p1;
for (var i = 0; i < 10; i++) {
    p1 = new Promise(function(resolve, reject) {
        var num = Math.random();
        if (num < .5) {
            resolve(num);
        } else {
            reject(num);
        }
    });
    p1.then(function success(res) {
        successes++;
        console.log("*Success:* " + res);
    }, function error(error) {
        errors++;
        console.log("*Error:* " + error);
    });
}

Another way (at least in native Promises) to get what you are after is

var errors = 0;
var successes = 0;
var p1;
for (let i = 0; i < 10; i++) {
    p1 = new Promise(function(resolve, reject) {
      setTimeout(() => {
            var num = Math.random();
            if (num < .5) {
                resolve(`${i} ${num}`);
            } else {
                reject(`${i} ${num}`)
            }
        });
    });
    p1.then(function success(res) {
        successes++;
        console.log("*Success:* " + res)
    }).catch(function error(error) {
        errors++
        console.log("*  Error:* " + error)
    });
}

This is because the setTimeout delays the resolve/reject

An in-depth explanation

First things first ... you need to understand that .then is actually

.then(onFullfilled, onRejected)

and returns a Promise

next, .catch is simply "syntax sugar" for

.then(null, onRejected)

in fact, in most Promise libraries (before they went native) it is defined as

Promise.prototype.catch = function (onRejected) {
    return this.then(null, onRejected);
};

Right ... so let's look at an un-wound, simple version of your code - and only use THREE promises for brevity

function executorFunction(resolve, reject) {
    const num = Math.random();
    if (num < .5) {
      resolve(num);
    } else {
      reject(num)
    }
}
let successes = 0, errors = 0;
function success(res) {
    successes++;
    console.log("*Success:* " + res)
}
function error(error) {
    errors++
    console.log("*Error:* " + error)
}

const p1 = new Promise(executorFunction);
p1.then(success).catch(error);

const p2 = new Promise(executorFunction);
p2.then(success).catch(error);

const p3 = new Promise(executorFunction);
p3.then(success).catch(error);

You can run that and see that it produces the same order of success and error

Now, let's change it up a bit, so we always get success/fail/success

function executorFunction(num, fail) {
    return (resolve, reject) => {
        if (fail) {
          reject(num);
        } else {
          resolve(num)
        }
    };
}
function success(res) {
    console.log("*Success:* " + res)
}
function error(error) {
    console.log("*Error:* " + error)
}

const p1 = new Promise(executorFunction(1, false));
p1.then(success).catch(error);

const p2 = new Promise(executorFunction(2, true));
p2.then(success).catch(error);

const p3 = new Promise(executorFunction(3, false));
p3.then(success).catch(error);

This ALWAYS outputs

*Success:* 1
*Success:* 3
*Error:* 2

So we see the order you are seeing in your question - so far so good

Now, let's rewrite the .then/.catch in there expanded form

function executorFunction(num, fail) {
    return (resolve, reject) => {
        if (fail) {
          reject(num);
        } else {
          resolve(num)
        }
    };
}
function success(res) {
    console.log("*Success:* " + res)
}
function error(error) {
    console.log("*Error:* " + error)
}

const p1 = new Promise(executorFunction(1, true));
p1.then(success, null).then(null, error);

const p2 = new Promise(executorFunction(2, false));
p2.then(success, null).then(null, error);

let's only use two promises, the FIRST rejecting ... we know that this will output success 2 then error 1 - i.e. in the reverse order we are expecting

So let's analyse what is happening

Because you're resolving/rejecting synchronously in the Promise constructor executorFunction

const p1 = new Promise(executorFunction(1, false));

is immediatelty a resolved Promise - fulfilled as 2 for p2, and rejected with reason 1 for p1, but it's never in a Pending state. So, when a promise is not pending (it's resolved, but that means either fulfilled or rejected, but the terminology has been mixed up, so I'll keep saying "not pending"), any "handler" is added to the microtask queue - so at the end of all that code, the microtask queue looks like

**microtask queue**
(resolved:2).then(success, null).then(null, error); // added second
(rejected:1).then(success, null).then(null, error); // added first

Now the JS engine, since there's nothing running anymore, processes the microtask queue (the head of the queue is at the bottom by the way)

  • it sees a rejected promise(1), but the .then has no on rejected function, so the promise value carries down the chain
  • .then returns this rejected promise with the original rejection reason
  • this promise, because it has a handler (.catch in the original code) is added to the microtask queue

.

**microtask queue**
(rejected:1)>(rejected:1).then(null, error);         // added third
(resolved:2).then(success, error).then(null, error); // added second

Now the next microtask is processed

  • It sees a resolved promise(2) so calls success
  • outputs success 2
  • .then returns a promise, because your success function has no return, this is return undefined and the promise is resolved as undefined
  • this promise, because it has a handler (.catch in the original code) is added to the microtask queue

.

**microtask queue**
(resolved:2)>(resolved:undefined).then(null, error); // added fourth
(rejected:1)>(rejected:1).then(null, error);         // added third
  • It sees a rejected promise(1) and there is an onrejected handler calls error
  • outputs error 1
  • .then returns a promise, there's no handler, so nothing is added to the microtask queue

.

**microtask queue**
(resolved:2)>(resolved:undefined).then(null, error); // added fourth

Now the next microtask is processed

  • It sees a resolved promise(2, now undefined) - but there is no onSuccess handler
  • .then returns a promise, there's no handler, so nothing is added to the microtask queue

.

**microtask queue**
**empty**

why using .then(onFullfilled, onRejected) results in the expected order

OK, so now, if we write the code

function executorFunction(num, fail) {
    return (resolve, reject) => {
        if (fail) {
          reject(num);
        } else {
          resolve(num)
        }
    };
}
function success(res) {
    console.log("*Success:* " + res)
}
function error(error) {
    console.log("*Error:* " + error)
}
const p1 = new Promise(executorFunction(1, true));
p1.then(success, error);

const p2 = new Promise(executorFunction(2, false));
p2.then(success, error);

The microtask queue starts, like

**microtask queue**
(resolved:2).then(success, error); // added second
(rejected:1).then(success, error); // added first

Now the next microtask is processed

  • It sees a rejected promise(1) so calls error
  • outputs error 1
  • .then returns a promise, there's no handler, so nothing is added to the microtask queue

.

**microtask queue**
(resolved:2).then(success, error); // added second
  • It sees a resolved promise(2) so calls success
  • outputs success 2
  • .then returns a promise, there's no handler, so nothing is added to the microtask queue

.

**microtask queue**
**empty**

why adding setTimeout results in the expected order

Now let's change the executorFunction to add the setTimeout

function executorFunction(num, fail) {
    return (resolve, reject) => {
        setTimeout(function() {
            if (fail) {
              reject(num);
            } else {
              resolve(num)
            }
        });
    };
}
function success(res) {
    console.log("*Success:* " + res)
}
function error(error) {
    console.log("*Error:* " + error)
}

const p1 = new Promise(executorFunction(1, true));
p1.then(success, null).then(null, error);

const p2 = new Promise(executorFunction(2, false));
p2.then(success, null).then(null, error);

Again, for brevity, let's use only TWO promises, and the first one fails, because we know that the output in the original code would be success 2 then fail 1 Now we have two queues to consider ... microtask and "timer" - the timer queue has lower priority than the microtask queue ... i.e. when there's nothin running (immediately) then JS will process the microtask queue until it is empty, before even trying the timer queue

So - here we go then

At the end of that code we have

** empty microtask queue **                             timer queue
                                                        setTimeout(resolve(2))
                                                        setTimeout(reject(1))

Processing the timer queue, we get a microtask (rejected:1).then(success, null).then(null, error)

** microtask queue **                                   timer queue
(rejected:1).then(success, null).then(null, error)      setTimeout(resolve(2))

Oh, there's something in the microtask queue, lets process that and ignore the timer queue

  • it sees a rejected promise(1), but the .then has no on rejected function, so the promise value carries down the chain
  • .then returns this rejected promise with the original rejection reason
  • this promise, because it has a handler (.catch in the original code) is added to the microtask queue

Oh, there's something in the microtask queue, lets process that and ignore the timer queue

** microtask queue **                                   timer queue
(rejected:1).then(success, null).then(null, error)      setTimeout(resolve(2))
  • It sees a rejected promise(1) and there is an onrejected handler calls error
  • outputs error 1
  • .then returns a promise, there's no handler, so nothing is added to the microtask queue

Now the queues look like

** microtask queue **                                   timer queue
                                                        setTimeout(resolve(2))

So, I don't need to go on, right, because error 1 has been output before the second promise chain has even started :p1

like image 100
Jaromanda X Avatar answered Oct 18 '22 08:10

Jaromanda X