Так как же на самом деле работает RxJS QueueScheduler?

И как это может помочь мне предотвратить ошибку повторного входа (которая вызывает переполнение стека) + реализовать подход в ширину в операторах объединения потоков, таких как комбинироватьПоследние.


конфеты в очереди

Prerequisites: you should have good knowledge about RxJS, Observables and Schedulers.

В 95 процентах задач вам не нужно использовать Планировщики. Но в некоторых сложных случаях знание того, как работают планировщики, может иметь решающее значение. Я все еще думаю, что планировщики — это чисто документированная часть RxJS. И в этой статье я постараюсь восполнить некоторые информационные пробелы этой темы.

Если мы посмотрим на определение планировщиков в планировщик.мд репозитория rx.js github, то мы узнаем, что:

Планировщик контролирует, когда начинается подписка и когда доставляются уведомления.

Что это значит — когда уведомления доставляются?

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

Все возможности перечислены в этой таблице:


Планировщики

я не указал Планировщик Виртуального Времени а также планировщик тестов потому что этот пост не о них.

Примеры:

//значение будет передано сразу — синхронно
of(1).subscribe(console.log)

**//значение будет передано сразу — синхронно **
of(1, queueScheduler).subscribe(console.log)

**// значение будет передано в mIcrotask сразу после текущей mAcrotask **
of(1, asapScheduler).subscribe(console.log)

**// значение будет сгенерировано в другой макрозадаче **
of(1, asyncScheduler).subscribe(console.log)

**// значение будет сгенерировано в другой макрозадаче непосредственно перед перерисовкой браузера **
of(1, animationFrameScheduler).subscribe(console.log)

Как видите, у нас есть нулевой а также планировщик очереди оба отправляют данные в одну и ту же mAcrotask. Так в чем же между ними разница?

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

Если мы используем планировщик очереди — тогда rx.js просто добавляет все значения в очередь планировщика, а затем сбрасывает ее. Это происходит в той же макрозадаче в событии подписки.
Для клиента, использующего rx.js, разницы в контексте нет.
Вот код из диапазон.ts:


rx.js диапазон.ts

И использования оператора подписаться на массив:


‘of’ по умолчанию (без планировщика) вызывает ‘fromArray’, который использует подписаться на массив.

Есть ли тогда разница?

Чтобы увидеть, когда нулевой а также очередь планировщик может вести себя по-разному, давайте рассмотрим этот пример из переполнение стека:

const a$ = of(1, 2);  
const b$ = of(10);
const c$ = combineLatest(a$, b$, (a,b)=>a+b);
c$.subscribe(console.log);

Результат, который вы можете ожидать:
11
12
Фактический результат:
12

Вот кодовая ручка так что вы тоже можете запустить его.

комбинироватьПоследние объединяет несколько Observables для создания Observable, значения которого рассчитываются на основе последних значений каждого из его входных Observables. Он начинает испускаться, если аргументы-наблюдатели (в нашем случае a$ и b$) имеют хотя бы одно значение. Если у него есть функция resultSelector (здесь это (a,b)=>a+b ) — то он выдаст свой вывод (a+b)

Что мы можем ожидать, что комбинироватьПоследние подписывается на а**,грамметс1,тчасенстыбссрябесто**б** , получает 1, затем подписывается на **b , получает 10 и выдает 1+10=11. И тогда a$ выдаст 2, поэтому combLatest observable выдаст 2+10=12.

Но так как планировщику не подается ‘из’ функция, поэтому она работает не так, как мы можем ожидать: комбинироватьПоследние подписывается на **a**, а так как ‘ **of’** работает с циклом ‘ **for** ‘, a выдает 1 и 2 одновременно. затем комбинироватьПоследние подписывается на б$ и получает 10. Теперь он берет последние значения и передает их в selectorFunction, поэтому 2+10=12, поэтому выдается 12.

Это называется подходом «сначала в глубину», но можем ли мы каким-то образом сделать его «сначала вширь»? Итак, сначала все наблюдаемые аргументы могут выдать свое первое значение и так далее и тому подобное?

Да, с помощью queueScheduler!

const a$ = of(1, 2, queueScheduler);
const b$ = of(10, queueScheduler);
const c$ = combineLatest(a$, b$, (a,b)=>a+b, queueScheduler);
c$.subscribe(console.log);

Выход:
11
12

Если вам интересно, как это работает — в приложении к статье я положу анимированный gif с записью сеанса отладки combLatest+queueScheduler.


Еще один пример:

let Rx = window[‘rxjs’];  
const {Subject, queueScheduler} = Rx;  
const {take, observeOn} = Rx.operators;  
console.clear();

const signal = new Subject();  
let count = 0;

const somecalculations = (count) => console.log(‘do some calculations with ‘, count);

console.log(‘Start’);  
signal.pipe(take(1600))
 .subscribe(() => {  
    somecalculations** (count);  
    signal.next(count++);  
  });


signal.next(count++);  
console.log(‘Stop’);

Вы можете попробовать этот код в кодовая ручка также.

Здесь мы используем именованный сигнал субъекта для рекурсивного выполнения некоторых вычислений.

После вычислений инициируется следующий рекурсивный вызов с помощью signal.next.

С Тема.далееметод работает синхронно — здесь мы имеем рекурсивные вызовы обработчика подписчика.

А если кол-во звонков небольшое — работает хорошо. Но если мы будем делать много вызовов рекурсивно — получим проблему переполнения стека.

Можем ли мы это как-то исправить?

Да, с планировщик очереди ! Мы применим его, чтобы сигнализировать Субъекту с помощью наблюдать на оператор.

console.log(‘Пуск’);
signal.pipe (взять (1600), наблюдать за (queueScheduler))
.подписать(() => {
некоторые расчеты (считать);
сигнал.следующий(количество++);
});

планировщик очереди заменит рекурсивные вызовы итеративными. Все расчеты будут выполняться во время одной и той же mAcrotask.

Если вам интересно, как планировщик очереди делает это под капотом: вот мой отвечать на stackoverflow.com с более подробной информацией об отладке. А также я приведу анимированную гифку с записью сеанса отладки чуть ниже статьи.

Итак, пора подводить итоги:

Когда нам нужен queueScheduler?: на случай, если вам нужно заменить рекурсивные вызовы подписки итеративными запусками. И если вам нужны некоторые операторы объединения (например, CombineLatest) для работы в ширину.

Надеюсь, вам понравилась статья. Пожалуйста, оставляйте комментарии с вашими личными вариантами использования планировщиков rx.js!

Packpub.com и я подготовили целую Курс обзора rx.js со многими другими подробностями о том, как вы можете решать повседневные задачи разработчиков с помощью этой удивительной библиотеки. Взглянем!

ПРИЛОЖЕНИЕ 1. CombineLatest с сеансом отладки queueScheduler:

ПРИЛОЖЕНИЕ 2. Subject.next с сеансом отладки queueScheduler:

Похожие записи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *