I have a task as below which I have to solve using Promise Chaining in JavaScript and I am getting some unexpected outputs -:
Task:
If Mike is able to clean the room, then he goes for football.
In football match, if he makes a goal, then as reward he gets a chance to visit the music concert. If he does not make a goal, then he cleans his dog.
If he is not able to clean the room, then he does the laundry.
If he is not able to finish the laundry either, then he has to clean his dog.
But if he is able to clean the laundry then he goes for concert.
Solution:
const cleanRoom = (resolve, reject) => {
let num = Math.random();
if (num >= .5) {
resolve(num); //room cleaned
} else {
reject(new Error(num)); //room not cleaned
}
}
const roomCleaningPromise = new Promise(cleanRoom);
roomCleaningPromise.then(function goFootballAfterCleaning(resolveValue) {
console.log(`Room was cleaned so going to play Football -: ${resolveValue}`);
return new Promise(function makeGoal(resolve, reject) {
let number = Math.random();
if (number >= .5) { //go to concert
resolve(number);
} else {
reject(number); //dog cleaning
}
})
}).then(function goConcertAfterGoal(resolveValue) {
console.log(`Room was cleaned so went to play Football and made a goal!! Now Going to Music concert -: ${resolveValue}`);
}).catch(function cleanDogAfterlosing(errorValue) {
console.log(`Room was cleaned so went to play Football but couldn't make a goal so Cleaning the dog -: ${errorValue}`);
})
roomCleaningPromise.catch(function goLaundaryAfterNoCleaning(errorValue) {
console.log(`Room was not cleaned so going to do Laundry -: ${errorValue}`);
return new Promise(function doLaundary(resolve, reject) {
let number_2 = Math.random();
if (number_2 >= .5) { //go to concert
resolve(number_2);
} else {
reject(number_2); //dog cleaning
}
})
}).then(function goConcertAfterLaundary(resolveValue) {
console.log(`Room was not cleaned so went to do Laundry, which I completed so now going to Music Concert -: ${resolveValue}`);
}).catch(function cleanDogAfterNoLaundary(errorValue){
console.log(`Room was not cleaned so went to do Laundry, which I didn't complete so cleaning the dog -: ${errorValue}`);
})
Output:
Room was not cleaned so going to do Laundry -: Error: 0.10282616920143206
Room was cleaned so went to play Football but couldn't make a goal so Cleaning the dog -: Error: 0.10282616920143206
Room was not cleaned so went to do Laundry, which I didn't complete so cleaning the dog -: 0.3140749736813231
Doubt:
Now my question is if the room was not cleaned then why did it print "Room was cleaned…."? If the initial promise was rejected then the control should have gone to the second .catch() of the code and then if the second promise was also rejected the control should then have just gone to the last .catch(), that’s it. Why did this not happen?
Expected Output:
One of the possible expected output would have been something like-:
Room was not cleaned so going to do Laundry -: Error: 0.10282616920143206
Room was not cleaned so went to do Laundry, which I didn't complete so cleaning the dog -: 0.3140749736813231
Reference:
The above task and code is based on the "JavaScript Promises" Medium article by Praveen Gaur.
3
Answers
If you have a rejected promise and you chain single-argument
then
clauses to it, the promises returned by thosethen
clauses will be rejected in the same way as the original. This is because of the rules in the definition ofthen
:Here, your
cleanRoom
is rejected; you chain twothen
calls to it withoutonRejected
handlers, so by the time you get to thecatch
clause your Promise is still rejected with its initial state. The article you read does not describe this case adequately.The ability to propagate errors should be seen as a feature of Promises, and one that approximates the use of
try
/catch
in an asynchronous way:This is a good academic question, but
.then()
and.catch()
should be avoided in favour of async await and try/catch in modern (post ES2017) programs as they allow asynchronous JS to be expressed in direct style consistent with synchronous code.Additionally, errors should be used for errors – a user not doing a task is better handled as a result of
false
than an Error, which is more typically used for unexpected behaviour.If your goal is implement the program in the simplest way for you and others to understand, this answer is likely the best. However if your goal is to specifically use pre-ES2017 promise handling techniques, you may prefer the other answer.
An ES2017 solution (also using TypeScript and top level
await
)First let’s define define our promise-returning functions.
Then the logic of the program becomes incredibly simple, matching your description fairly closely:
Paste these snippets into a file called
runme.ts
and run this with:It looks like there might be a misunderstanding of how Promise chaining works. In your code, you are mixing the use of .then() and .catch() in a way that may lead to unexpected results.
Let’s break down your code to understand the flow:
If the room is cleaned, it goes to the first .then() block and tries to play football.
Inside the football block, if he makes a goal, it goes to the second .then() block (which is correct).
If he doesn’t make a goal, it goes to the first .catch() block.
Now, if the room is not cleaned:
It goes to the first .catch() block as expected.
Inside the first .catch() block, it goes to the second .then() block (which might be unexpected).
The reason for this behavior is that when a promise is rejected, it looks for the nearest .catch() block in the chain. If it doesn’t find one, it looks for the next .then() block. In your case, since the first .catch() block returns a promise, it goes to the next .then() block.
To achieve the expected output, you should avoid mixing .then() and .catch() blocks in this way. Instead, you can use separate .then() and .catch() blocks for each promise. Here’s a modified version of your code:
This way, each promise has its own set of .then() and .catch() blocks, making the control flow more predictable.