1

I'm creating a recursive system that resend socket.io packet till the server answer by fulfilling the socket.io acknowledgement. I create a promise that will reject in X seconds or resolve if the server answer in time, if it timeouts I recreate one with a longer timeout.

The problem is that the acknowledgement can't resolve the promise when there have been at least one timeout before and I don't understand why.

Here is a snippet of my code :

  async emit(event, data) {
    if (!this.socket) {
      this.eventToSend[event] = data;
    }

    let timeoutRef;
    let alreadyResolved = false;

    return new Promise(async r => {

      const sentPromise = timeout => {
        return new Promise((resolve, reject) => {
          console.log("------ New Call ------");
          timeoutRef = setTimeout(() => {
            if (alreadyResolved === true) {
              console.log("Promise with timeout : " + timeoutRef + " is already resolved !!!");
            } else {
              console.log("promise " + timeoutRef + " timeouted !");
              reject();
            }
          }, timeout);
          console.log("Create promise with Timeout number : " + timeoutRef);

          this.socket.emit(event, { data }, function(response) {
            alreadyResolved = true;
            console.log("try to delete " + timeoutRef + " timeout");
            resolve(response);
          });
        });
      };

      try {
        const result = await this.recursiveSend(sentPromise);
        console.log("received the result " + result + ", aborting the process");
        r(result);
      } catch (e) {
        console.error(e);
        this.socket.disconnect(true);
      }
    });
  }

  async recursiveSend(promise, retryIndex = 0) {
    try {
      const result = await promise(this.timeoutRate[retryIndex]);
      console.log("recevied result ! " + result);
      return result;
    } catch (e) {
      // Here the setTimeout executed before I received the server acknowledgement
      const newRetryIndex = retryIndex + 1;
      if (newRetryIndex >= this.timeoutRate.length) {
        throw new Error("Timeout exceeded, unable to join the socket");
      } else {
        return this.recursiveSend(promise, newRetryIndex);
      }
    }
  }

This is the actual console log output :

...  
------ New Call ------  
Create promise with Timeout number : 32  
promise 32 timeouted !  
------ New Call ------  
Create promise with Timeout number : 34  
promise 34 timeouted !  
------ New Call ------  
Create promise with Timeout number : 36  
try to delete 36 timeout // Here the promise is supposed to be resolved  
Promise with timeout : 36 is already resolved !!! // But here we tried to reject it  

Logs are not reliable so i tried using breakpoint, I still go in the resolve() first (but I can't enter it) then in the reject(). It's like the socket.io acknowledgement is made in another thread but it works perfectly when there is no timeout and the server respond right away

Azurox
  • 11
  • 2
  • 2
    [Never pass an `async function` as the executor to `new Promise`](https://stackoverflow.com/q/43036229/1048572)! – Bergi Jul 08 '19 at 10:25
  • When exactly is the callback to `this.socket.emit` called? Are you getting the acknowledgement in the "old" emit callback, instead of the one in the retried? – Bergi Jul 08 '19 at 10:30
  • I removed the async function and simply returned the promise, it seems that it doesn't change anything... The server respond 6 seconds after receiving the first request. I don't undertand your last question, the acknoledgement is only received in the last call – Azurox Jul 08 '19 at 10:40
  • How do you know it's the last call? Your `timeoutRef` and `alreadyResolved` variables are global. Try putting their declarations inside the `sentPromise` function. – Bergi Jul 08 '19 at 11:00
  • So you say you're not getting "*recevied result !*" and "*received the result … aborting the process*" logs, but would expect them? – Bergi Jul 08 '19 at 11:02
  • Exactly, and I don't know why because the resolve is clearly called – Azurox Jul 08 '19 at 11:08
  • As I said, make sure it's the right `resolve` by keeping `timeoutRef` local. I'm pretty certain the `emit` callback is getting called in one of the older `sentPromise` calls that was already timeouted. – Bergi Jul 08 '19 at 11:12
  • Thanks, you are right ! The acknoledgement is called on the first emit so it tries to resolve an already rejected Promise, my bad ! – Azurox Jul 08 '19 at 11:22

1 Answers1

0

The Promise that is passed to recursiveSend is rejected on timeout, after that the same Promise is passed to recursiveSend again in the catch, where you're trying to resolve it.

Resolving an already rejected Promise is not possible.

Lazykiddy
  • 1,525
  • 1
  • 11
  • 18
  • 1
    I'm not sure it's the same promise because it's a function that create a new one that is passed in the recursive call. I `await` a `new Promise` everytime. Here the problem is that the resolve in the acknoledgment callback is the resolve of the very first promise created. So when my server responds, the callback is executed in an already rejected promise. I simply resolved it by stocking the last `resolve` function created externally and called it instead of the first one. – Azurox Jul 08 '19 at 11:54