0

I have such function:

async howLongToMyBirthdayDate(date) {
    return await new Promise((resolve, reject) => {
      let bday;
      if (!(date instanceof Date)) {
        if (Number.isInteger(date) && date > 0) {
          bday = new Date(date);
        } else {
          setTimeout(() => reject(new Error('Wrong argument!')), 1000);
          return;
        }
      } else {
        bday = date;
      }

      const today = new Date();
      today.setHours(0, 0, 0, 0);

      bday.setFullYear(today.getFullYear());
      bday.setHours(0, 0, 0, 0);

      if (bday - today === 0) {
        setTimeout(() => resolve(this.congratulateWithBirthday()), 1000);
        
      }

      setTimeout(
        () =>
          resolve(this.notifyWaitingTime(Math.ceil((bday - today) / 86400000))),
        1000
      );
    });
  }

The promise resolves twice. I see the result of function congratulateWithBirthday and notifyWaitingTime. Is it normal? I thought that promise can be resolved or rejected only once - by the first invokation of resolve or reject callback. Maybe setTimeout changes the behaviour of it? Can anyone explain me, please?

Tsukuruu
  • 23
  • 4
  • *"I see the result of.."*. See it how ? Why are you calling resolve() twice when dates are the same? – charlietfl Dec 18 '21 at 16:19
  • 4
    "Can one promise be resolved twice?" — No, but it is hard to tell what is happening because you've not provided a [mcve] – Quentin Dec 18 '21 at 16:19
  • 3
    *"The promise resolves twice."* You probably mean it's *fulfilled* twice. (More on terminology [in my post here](https://thenewtoys.dev/blog/2021/02/08/lets-talk-about-how-to-talk-about-promises/).) But a promise can neither be resolved nor fulfilled more than once. If you see something that makes you think it's happened twice, you're seeing the result of something else, not promise fulfillment. – T.J. Crowder Dec 18 '21 at 16:20
  • 1
    Side note: There's no reason for any of the date logic to be intermixed with promises here. Just do the math, and if you want to delay the reporting of the result by a second, `await` a [`setTimeout` wrapper](https://stackoverflow.com/questions/22519784/how-do-i-convert-an-existing-callback-api-to-promises). – T.J. Crowder Dec 18 '21 at 16:26

2 Answers2

2

Your promise doesn't need to resolve twice to get the behavior you're describing. You're invoking both of those functions directly, so of course they get invoked. Whether the second call to resolve does anything isn't related.

This line

resolve(this.notifyWaitingTime(Math.ceil((bday - today) / 86400000))),

does not say "resolve and then run this function", it says "invoke this function, and then call resolve with the return value of it".

A more appropriate way to write this code might be something like this:

async howLongToMyBirthdayDate(date) {
    const result = await new Promise((resolve, reject) => {
      let bday;
      if (!(date instanceof Date)) {
        if (Number.isInteger(date) && date > 0) {
          bday = new Date(date);
        } else {
          setTimeout(() => reject(new Error('Wrong argument!')), 1000);
          return;
        }
      } else {
        bday = date;
      }

      const today = new Date();
      today.setHours(0, 0, 0, 0);

      bday.setFullYear(today.getFullYear());
      bday.setHours(0, 0, 0, 0);

      // resolve with how long until bday
      if (bday - today === 0) {
        setTimeout(() => resolve(0), 1000);
        
      }

      // we just resolve with how long until bday
      setTimeout(
        () =>
          resolve(Math.ceil((bday - today) / 86400000)),
        1000
      );
    });

  if (result === 0) { this.congratulateWithBirthday(); }
  else if (result > 0) { this.notifyWaitingTime(result); }
  }

Not 100% sure this addresses your question since I can't see what the functions you're calling do, but my guess from how you were (ab)using resolve is that you want to essentially call one of two functions after calculations depending on if bday is now or not.

CollinD
  • 7,304
  • 2
  • 22
  • 45
-2

Short answer, yes, given your code it will resolve twice. The reason for that is because congratulateWithBirthday and notifyWaitingTime are not conditioned on each other so they will both be called on the same run.

If you want to avoid that you should have something like

if (bday - today === 0) {
  setTimeout(() => resolve(this.congratulateWithBirthday()), 1000);
} else {
  setTimeout(() => resolve(this.notifyWaitingTime(Math.ceil((bday - today) / 86400000))), 1000);
}

Also, as a side note, try to decide between using async/await or Promise. There's no reason for using both and it makes the code look weird.

  • Except that calling `resolve()` the second time on the same promise does NOTHING. A promise is only ever actually fulfilled once. – jfriend00 Dec 18 '21 at 18:05
  • yes, but he's not waiting for the fulfilled value, he is calling some other functions, which in this example if `bday - today === 0` will both get called – Silviu Glavan Dec 18 '21 at 18:17