🥳 В релизе Chrome 129 получил обновление API для реализации кооперативной многозадачности - Scheduler API.



Был добавлен метод scheduler.yield(), который позволяет приостановить процесс выполнения задачи и вернуть управление в планировщик.



Зачем это нужно? 🤔



Ну давайте разбирать с начала. Что вообще такое кооперативная многозадачность? Это набор техник позволяющий нам организовать "одновременное" выполнение сразу нескольких задач в рамках одного общего потока выполнения. Разумеется одновременность тут условная, ибо задачи по сути частями выполняются согласно правилам очереди.



Такой подход особенно актуален в рамках JS и браузера, где нам нужно максимально избегать длительных блокировок главного потока одной задачей. Почему? Ну потому что тогда наш интерфейс будет "тормозить". Пока вы считаете какой-то "большой" JS код все остальное простаивает: не вызываются обработчики действий пользователя и не выполняется никакой другой код.



Да, у нас есть API WebWorkers, но с ним также связано множество ограничений, да и речь сейчас не про него. Как же можно сделать иначе?



Ну, мы можем сказать, что сама регистрированная задача состоит из дискретных этапов вычисления. Причем между этими этапами она может "засыпать" отдавая ресурсы на другие задачи. Реализовать такое проще всего используя генераторы: генератор реализует АПИ итератора, а значит вот и наши дискретные этапы; внутри генератора можно приостанавливаться используя yield.



function* longTask(timer) {

for (let i = 0; i < 1e9; i++) {

if (timer.shouldSleep) {

yield;

}



// ...

}

}




И нам нужна структура планировщика, которая могла бы принимать множество таких задач и оркестрировать их выполнения. У задач могут быть свои приоритеты и планировщик должен это учитывать.

Причем подчеркиваю, при реализации кооперативной многозадачности остановку задачи делает сама задача, а не планировщик. Существуют и альтернативная реализация многозадачности - преемтивная (вытесняющая). При таком подходе остановкой задач также занимается планировщик. Это надежнее, но сложнее реализовать.



const scheduler = new Sheduler();



scheduler.addTask({

priority: 'blocker',

*task(timer) {

for (let i = 0; i < 1e9; i++) {

if (timer.shouldSleep) {

yield;

}



// ...

}

}

}).then(console.log, console.error);




Так вот. Такой вот подход оказался чрезвычайно востребован в JS и используется под капотом многих библиотек, чтобы гарантировать отсутствие блокировок при тяжелых операциях. А значит "нативная" реализация такого АПИ должна была рано или поздно появится. Так и произошло. Встречайте, Scheduler API.



// PS: ставь лайк, кто не знал, что в JS можно деллать Immediate Instantiated Class Expression

const timer = new class {

#ms;

#start;



constructor(ms) {

this.#ms = ms;

}



get shouldSleep() {

this.#start ??= Date.now();

return Date.now() - this.#start >= this.#ms;

}



reset() {

this.#start = undefined;

}

}(50);



scheduler.postTask(async function myTask() {

for (let i = 0; i < 1e9; i++) {

if (timer.shouldSleep) {

await scheduler.yield();

timer.reset();

}



// ...

}

}, {priority: 'user-blocking'}).then(console.log, console.error);




Разумеется, данный пример больше демонстративный, но он показывает, что нативное АПИ в общем очень похоже на то, что мы делали раньше. Здорово, что такие вещи добавляются в стандартную библиотеку. Сейчас Scheduler API работает только в Chrome, но его легко заполифилить.



Всем базы! 💪