skip to Main Content

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


  1. If you have a rejected promise and you chain single-argument then clauses to it, the promises returned by those then clauses will be rejected in the same way as the original. This is because of the rules in the definition of then:

    If it [onFulfilled] is not a function, it is internally replaced with an identity function ((x) => x) which simply passes the fulfillment value forward.

    If it [onRejected] is not a function, it is internally replaced with a thrower function ((x) => { throw x; }) which throws the rejection reason it received.

    Here, your cleanRoom is rejected; you chain two then calls to it without onRejected handlers, so by the time you get to the catch clause your Promise is still rejected with its initial state. The article you read does not describe this case adequately.

    const roomCleaningPromise = Promise.reject(new Error("original failure"));
    
    roomCleaningPromise.then(function goFootballAfterCleaning(resolveValue) {
      console.log("this doesn't run, roomCleaningPromise failed");
    }).then(function goConcertAfterGoal(resolveValue) {
      console.log("this doesn't run, roomCleaningPromise failed");
    }).catch(function cleanDogAfterLosing(errorValue) {
      console.log(`Catch #1, value: ${errorValue}`);
    })
      
    roomCleaningPromise.catch(function goLaundryAfterNoCleaning(errorValue) {
      console.log(`Catch #2, value: ${errorValue}`);
      return new Promise(function doLaundry(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(`Laundry + concert: ${resolveValue}`);
    }).catch(function cleanDogAfterNoLaundary(errorValue){
      console.log(`Laundry + dog: ${errorValue}`);
    })

    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:

    function main() {
      try {
        a();
      } catch(e) {
        console.log(e);
      }
    }
    
    function a() {
      b();
      console.log("this doesn't happen because b throws");
    }
    
    function b() {
      throw new Error("this is logged by main despite not being handled in a");
    }
    
    Login or Signup to reply.
  2. 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.

    const log = console.log;
    
    const getRandomResult = (): boolean => {
      return Math.random() >= 0.5;
    };
    
    const getRandomPromiseBoolean = (): Promise<boolean> => {
      return new Promise((resolve, reject) => {
        const result = getRandomResult();
        if (result) {
          resolve(true);
          return;
        }
        resolve(false);
      });
    };
    
    const getPromiseOfTrue = (): Promise<boolean> => {
      return new Promise((resolve, reject) => {
        resolve(true);
      });
    };
    
    const cleanRoom = (): Promise<boolean> => {
      return getRandomPromiseBoolean();
    };
    
    const playFootball = (): Promise<boolean> => {
      return getRandomPromiseBoolean();
    };
    
    const doLaundry = (): Promise<boolean> => {
      return getRandomPromiseBoolean();
    };
    
    const goToConcert = (): Promise<boolean> => {
      // In the task, the concert always succeeds.
      return getPromiseOfTrue();
    };
    
    const cleanDog = (): Promise<boolean> => {
      // In the task, the dog always gets cleaned.
      return getPromiseOfTrue();
    };
    

    Then the logic of the program becomes incredibly simple, matching your description fairly closely:

    const isRoomCleaned = await cleanRoom();
    if (isRoomCleaned) {
      log(`Room cleaned, going to play football`);
      const isGoalMade = await playFootball();
      if (isGoalMade) {
        log(`Goal made, going to concert`);
        await goToConcert();
        log(`Concert visited`);
      } else {
        log(`Goal not made, cleaning dog`);
        await cleanDog();
        log(`Dog cleaned`);
      }
    } else {
      log(`Room not cleaned, doing laundry`);
      const isLaundryDone = await doLaundry();
    
      if (isLaundryDone) {
        // if he is able to clean the laundry then he goes for concert.
        log(`Laundry done, going to concert`);
        await goToConcert();
        log(`Concert visited`);
      } else {
        // If he is not able to finish the laundry either, then he has to clean his dog.
        log(`Laundry not done, cleaning dog`);
        await cleanDog();
        log(`Dog cleaned`);
      }
    }
    
    // Added for linting
    export {};
    

    Paste these snippets into a file called runme.ts and run this with:

    npx esrun runme.ts
    
    Login or Signup to reply.
  3. 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:

    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 >= 0.5) {
            resolve(number);
          } else {
            reject(new Error(number));
          }
        });
      })
      .then(
        function goConcertAfterGoal(resolveValue) {
          console.log(`Made a goal! Going to Music concert -: ${resolveValue}`);
        },
        function cleanDogAfterNoGoal(errorValue) {
          console.log(`Couldn't make a goal, cleaning the dog -: ${errorValue}`);
        }
      );
    
    roomCleaningPromise
      .catch(function goLaundryAfterNoCleaning(errorValue) {
        console.log(`Room was not cleaned so going to do Laundry -: ${errorValue}`);
        return new Promise(function doLaundry(resolve, reject) {
          let number_2 = Math.random();
          if (number_2 >= 0.5) {
            resolve(number_2);
          } else {
            reject(new Error(number_2));
          }
        });
      })
      .then(
        function goConcertAfterLaundry(resolveValue) {
          console.log(`Completed laundry, now going to Music Concert -: ${resolveValue}`);
        },
        function cleanDogAfterNoLaundry(errorValue) {
          console.log(`Didn't complete laundry, cleaning the dog -: ${errorValue}`);
        }
      );
    

    This way, each promise has its own set of .then() and .catch() blocks, making the control flow more predictable.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search