Хорошо, я написал это, и оно работает так, как я хочу, я просто не понимаю, как это работает/почему оно работает именно так.
Может кто-нибудь объяснить, чего мне здесь не хватает.
Вот код:
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
Спасибо
Я бы рекомендовал регистрировать таймауты для сигнала отмены вместо создания подкласса AbortController и перезаписи его abort() метода. В общем, вам следует передавать сигнал только той функции, которую вы хотите прервать (meLoop), а не всему контроллеру — meLoop не должен иметь возможность прерывать себя.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Когда meLoop() звонит
await controller.sleep(5 * 1000);
функция приостанавливается до тех пор, пока не истечет тайм-аут в sleep() и не вызовется resolve().
Когда вы очищаете тайм-аут в ProcessController.abort(), он никогда не вызывает resolve(), поэтому оператор await никогда не завершается, и цикл зависает навсегда.
Ах да, так это утечка памяти? Собирает ли он мусор? или просто повеситься навсегда? Спасибо
Оно будет висеть вечно. Вы можете сохранить обещания в свойстве класса и abort() вызвать Promise.reject(), чтобы отклонить его. Это вызовет исключение в meLoop() и все исчезнет.
Спасибо за помощь. отмечу как ответивший
@DavidWhite Со сбором мусора все должно быть в порядке, но на самом деле отказ от обещания - лучшая практика, чтобы у кода была возможность очистить (например, запустив finally блоки) или даже catch ошибку тайм-аута и продолжить, а не зависать.
Вызов 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 в сигнале, когда тайм-аут разрешит обещание.
Да, ты прав.
await controller.sleep(5 * 1000)ждет, пока разрешится обещание сна. Поскольку вы отменили таймер, он никогда не разрешается, поэтомуawaitждет вечно, и цикл зависает.