Функция не выполняется до завершения при очистке тайм-аута NodeJS

Хорошо, я написал это, и оно работает так, как я хочу, я просто не понимаю, как это работает/почему оно работает именно так.

Может кто-нибудь объяснить, чего мне здесь не хватает.

Вот код:

const crypto = require('crypto');

class ProcessController extends AbortController {
  timeouts = new Map();

  sleep(ms) {
    const id = crypto.randomBytes(8).toString('hex');
    return new Promise((resolve) => {
      this.timeouts.set(id, setTimeout(() => {
        this.timeouts.delete(id);
        resolve();
      }, ms))
    })
  }

  abort() {
    super.abort();
    for (const timeout of this.timeouts.values()) {
      clearTimeout(timeout);
    }
    // not really necessary as not reusable but be tidy
    this.timeouts.clear();
  }
}

async function meLoop(controller) {
  const { signal } = controller;

  while (!signal.aborted) {
    console.info("START OF LOOP. Uptime:", Math.floor(process.uptime()));
    await controller.sleep(5 * 1000);
    console.info('END OF LOOP');
  }
  console.info("BEYOND LOOP. Uptime:", process.uptime());
}

async function main() {
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  const controller = new ProcessController();
  meLoop(controller);
  await sleep(23 * 1000);
  controller.abort();

  console.info("END. Uptime:", process.uptime());
}

main();

И вот результат:

> node testBreakSleep.js
START OF LOOP. Uptime: 0
END OF LOOP
START OF LOOP. Uptime: 5
END OF LOOP
START OF LOOP. Uptime: 10
END OF LOOP
START OF LOOP. Uptime: 15
END OF LOOP
START OF LOOP. Uptime: 20
END. Uptime: 23.044878139

По сути, это прерываемый цикл. Насколько я понимаю, во время цикла while все, что там находится, должно завершиться до того, как while будет оценено снова, поскольку Javascript является однопоточным.

Однако чего я не понимаю, так это того, что когда abort() вызывается и setTimeout очищается, он просто завершает цикл, а не завершает его? Я не понимаю, что происходит с обещанием? Это не разрешается, и конец цикла никогда не достигается (это хорошо для меня)

Может кто-нибудь объяснить?

Изменить - подумал, что я должен упомянуть, что если я закомментирую clearTimeout в функции abort(), Node будет зависать до тех пор, пока обещание не будет выполнено.

> node testBreakSleep.js
START OF LOOP. Uptime: 0
END OF LOOP
START OF LOOP. Uptime: 5
END OF LOOP
START OF LOOP. Uptime: 10
END OF LOOP
START OF LOOP. Uptime: 15
END OF LOOP
START OF LOOP. Uptime: 20
END. Uptime: 23.037329278
END OF LOOP
BEYOND LOOP. Uptime: 25.041987186

Спасибо

await controller.sleep(5 * 1000) ждет, пока разрешится обещание сна. Поскольку вы отменили таймер, он никогда не разрешается, поэтому await ждет вечно, и цикл зависает.
Barmar 23.02.2024 18:10

Я бы рекомендовал регистрировать таймауты для сигнала отмены вместо создания подкласса AbortController и перезаписи его abort() метода. В общем, вам следует передавать сигнал только той функции, которую вы хотите прервать (meLoop), а не всему контроллеру — meLoop не должен иметь возможность прерывать себя.

Bergi 23.02.2024 20:31
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
2
79
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Когда meLoop() звонит

await controller.sleep(5 * 1000);

функция приостанавливается до тех пор, пока не истечет тайм-аут в sleep() и не вызовется resolve().

Когда вы очищаете тайм-аут в ProcessController.abort(), он никогда не вызывает resolve(), поэтому оператор await никогда не завершается, и цикл зависает навсегда.

Ах да, так это утечка памяти? Собирает ли он мусор? или просто повеситься навсегда? Спасибо

David White 23.02.2024 18:22

Оно будет висеть вечно. Вы можете сохранить обещания в свойстве класса и abort() вызвать Promise.reject(), чтобы отклонить его. Это вызовет исключение в meLoop() и все исчезнет.

Barmar 23.02.2024 18:34

Спасибо за помощь. отмечу как ответивший

David White 23.02.2024 19:02

@DavidWhite Со сбором мусора все должно быть в порядке, но на самом деле отказ от обещания - лучшая практика, чтобы у кода была возможность очистить (например, запустив finally блоки) или даже catch ошибку тайм-аута и продолжить, а не зависать.

Bergi 23.02.2024 20:26
Ответ принят как подходящий

Вызов await блокируется до тех пор, пока обещание не будет разрешено или отклонено, а это означает, что вызов controller.abort() rigth после запуска цикла не будет эффективным, пока обещание не будет выполнено и цикл не начнется заново. Добавление прослушивателя событий (если вы работаете с nodejs, вы можете использовать генераторы событий) к вашей отменяемой асинхронной функции может помочь, возможно, этот пример поможет вам достичь того, чего вы хотите:

function myCancellableFunction (signal) {
    return new Promise((res,rej) => {
        if (signal.aborted) return rej();
        const cancellableHandler = () => {
           // you may also want to perform a clean up here
           // like removing the timeout from your timeouts list
           signal.removeEventListener("abort",cancellableHandler);
           rej();
           
         }
        signal.addEventListener("abort",cancellableHandler);
        setTimeout(() => {
            signal.removeEventListener("abort",cancellableHandler);
            res();
        },2000);
    })
}

async function myLoopSignalFunction (signal) {
     while (true) {
         console.info("While loop beginning");
         // You can stop the while loop catching the error and breaking
         try {
            console.time("Cancellable elapsed time");
            await myCancellableFunction(signal);
            console.info("Cancellable function has been executed");
            console.timeEnd("Cancellable elapsed time");
         } catch(e)  {
            console.info("Function has been canceled");
            break;
         }
         
     }
}

async function main () {
    let controller = new AbortController();
    myLoopSignalFunction(controller.signal);
    setTimeout(() => {
      controller.abort();
      setTimeout(() => {
        controller = new AbortController()
        controller.abort()
        myLoopSignalFunction(controller.signal); // The controller is already aborted, nothing to do
      },1000);
    },10000);
   
}
main()

Не забудьте также отменить регистрацию cancelHandler в сигнале, когда тайм-аут разрешит обещание.

Bergi 23.02.2024 20:33

Да, ты прав.

ppepa 26.02.2024 09:49

Другие вопросы по теме