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
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.
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.
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".
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.
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)
.then
has no on rejected function, so the promise value carries down the chain.then
returns this rejected promise with the original rejection reason.
**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
success
success 2
.then
returns a promise, because your success function has no return, this is return undefined
and the promise is resolved as undefined
.
**microtask queue**
(resolved:2)>(resolved:undefined).then(null, error); // added fourth
(rejected:1)>(rejected:1).then(null, error); // added third
error
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
.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
error
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
success
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
.then
has no on rejected function, so the promise value carries down the chain.then
returns this rejected promise with the original rejection reasonOh, 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))
error
error 1
.then
returns a promise, there's no handler, so nothing is added to the microtask queueNow 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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With