emailjs / emailjs-imap-client

Low-level JS IMAP client for all your IMAP needs.
MIT License
553 stars 122 forks source link

UnhandledPromiseRejectionWarning: Error: IDLE not allowed now #216

Closed gitowiec closed 4 years ago

gitowiec commented 4 years ago

Hi First of all, thank you for great library. It helps us very much running our E2E tests. We are using emailjs-imap-client with Ethereal Email, it is such a convenient service. Recently I got this issue:

(node:25155) UnhandledPromiseRejectionWarning: Error: IDLE not allowed now
    at Object.callback (/home/user/workspace/project/packages/imap-client/node_modules/emailjs-imap-client/src/imap.js:269:25)
    at Imap.callback [as _handleResponse] (/home/user/workspace/project/packages/imap-client/node_modules/emailjs-imap-client/src/imap.js:597:28)
    at Imap._handleResponse [as _parseIncomingCommands] (/home/user/workspace/project/packages/imap-client/node_modules/emailjs-imap-client/src/imap.js:561:12)
    at Imap._parseIncomingCommands [as _onData] (/home/user/workspace/project/packages/imap-client/node_modules/emailjs-imap-client/src/imap.js:405:10)
    at TCPSocket._onData [as ondata] (/home/user/workspace/project/packages/imap-client/node_modules/emailjs-imap-client/src/imap.js:140:16)
    at TCPSocket.ondata [as _emit] (/home/user/workspace/project/packages/imap-client/node_modules/emailjs-tcp-socket/src/node-socket.js:60:29)
    at TLSSocket._emit (/home/user/workspace/project/packages/imap-client/node_modules/emailjs-tcp-socket/src/node-socket.js:31:45)
    at TLSSocket.emit (events.js:198:13)
    at TLSSocket.EventEmitter.emit (domain.js:448:20)
    at addChunk (_stream_readable.js:288:12)
    at readableAddChunk (_stream_readable.js:269:11)
    at TLSSocket.Readable.push (_stream_readable.js:224:10)
    at TLSWrap.onStreamRead [as onread] (internal/stream_base_commons.js:94:17)
(node:25155) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
[0-0] (node:25155) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

When running this code

let client: ImapClient;

const getClient = (disableLogging?: boolean) => {
  if (client) return client;
  client = new ImapClient(config.imap.host, config.imap.port, {
    logLevel: 'info',
    auth: config.auth,
    useSecureTransport: true,
  });
  client.onerror = function (error) {
    !disableLogging && console.log(error);
  };
};

const connectClient = async (disableLogging?: boolean): Promise<void> => client.connect().then(() => {
  !disableLogging && console.log('connection opened');
});

const closeConnection = (disableLogging?: boolean) => async (messageText?: string): Promise<string> =>
  client.close().then((value: unknown) => {
    !disableLogging && console.log('connection closed');
    return messageText;
  });

export const wipeoutInbox = async (disableLogging?: boolean): Promise<boolean> => {
  try {
    getClient(disableLogging);
    await connectClient(disableLogging);
    let uids = await client.search('INBOX', {}, { byUid: true });
    !disableLogging && console.log({ uids });
    if (uids.length === 0) {
      await closeConnection(disableLogging)();
      return true;
    }
    do {
      let results = await Promise.all(uids.map(uid =>
        client.deleteMessages('INBOX', uid, { byUid: true }),
      ));
      !disableLogging && console.log({ results });
      uids = await client.search('INBOX', {}, { byUid: true });
      !disableLogging && console.log({ uidsAfter: uids });
    } while (0 < uids.length);
    await closeConnection(disableLogging)();
    return uids.length === 0;
  } catch (e) {
    await closeConnection(disableLogging)();
  }
  return false;
};

I use this wipeoutInbox to clean up the Inbox after tests suite. I don't know where to start, it is strange that exception is unhandled (as Node says in the last line of error) because as you can see it is all in try-catch block. We use this function when running E2E tests with WebDriverIO, Mocha and Chai. If there is any more info I should share, just let me know. How to fix this error or catch it properly? Best regards

gitowiec commented 4 years ago

I have rewritten wipeoutInbox partially because I was not sure if Promise.all makes client.deleteMessages calls sequentially and if it does not, the way it was makes problems for the imap server. But the error still persists:

export const wipeoutInbox = async (disableLogging?: boolean): Promise<boolean> => {
  try {
    getClient(disableLogging);
    await connectClient(disableLogging);
    let uids: string[] = await client.search('INBOX', {}, { byUid: true });
    !disableLogging && console.log({ uids });
    if (uids.length === 0) {
      // await closeConnection(disableLogging)();
      await client.close();
      return true;
    }
    do {
      await uids.reduce(async (previousPromise, nextUid) => {
        await previousPromise;
        return client.deleteMessages('INBOX', nextUid, { byUid: true });
      }, Promise.resolve());
      uids = await client.search('INBOX', {}, { byUid: true });
      !disableLogging && console.log({ uidsAfter: uids });
    } while (0 < uids.length);
    // await closeConnection(disableLogging)();
    await client.close();
    return uids.length === 0;
  } catch (e) {
    // await closeConnection(disableLogging)();
    try {
      await client.close().catch((e) => console.log(e));
    } catch (error) {

    }
  }
  return false;
};
gitowiec commented 4 years ago

I managed to suppress the IDLE Error by adding client.client.onidle = null; right after client = new ImapClient(...) but I can't overcome long waiting time. I tried adding

  client.timeoutIdle = 1000;
  client.timeoutNoop = 1000;

But it does not help. And yes, there is repeated client because Client class has its own property of the same name (thanks its public!)

gitowiec commented 4 years ago

I am closing this issue, it happen to be wrong configuration of WebdriverIO. I accidentaly declared two hooks (after and afterSuite) with call to wipeoutInbox(). That means my code was trying to delete all messages in Inbox twice.