如何批量添加rxjs observables返回的数组/列表?

时间:2017-10-15 16:53:08

标签: rxjs

有一个observable返回数组/事物列表:Observable

我有一个用例,对于这个observable的下游消费者而言,这个列表中添加了更多项目是非常昂贵的事情。所以我想减慢对此列表所做的添加量,但不要放松。

类似于一个运算符,它接受这个observable并返回另一个具有相同签名的observable,但每当一个新列表被推送到它并且它有比上次更多的项时,那么一次只添加一个或几个。

因此,如果最后一次推送是一个包含3个项目的列表,而下一个推送是6个项目,批次大小为1,那么这一个列表推送将分成10个单独的列表大小:4,5,6

因此添加是批量的,这样消费者可以更容易地跟上列表的新增内容。或者消费者在处理数组/列表中的其他项目时,每次都不必停顿这么长时间,因为添加内容会被拆分并分布在可配置的批量大小上。

1 个答案:

答案 0 :(得分:0)

我创建了一个addAdditionalOnIdle运算符,您可以使用let运算符将其应用于任何rxjs observable。它需要一个batchSize参数,因此您可以配置批量大小。它还需要一个dontBatchAfterThreshold,它会在某个列表大小之后停止对列表进行批处理,这对我的目的很有用。

该实现在内部使用新的requestIdleCallback函数来在浏览器中有空闲时间时调度其他项目的批量推送。这个功能在IE或Safari中还没有,但是我发现这个功能非常好,所以今天你可以使用它:https://github.com/aFarkas/requestIdleCallback:)

请参阅下面的addAdditionalOnIdle的实现和示例用法:



function addAdditionalOnIdle(
  batchSize = 1,
  dontBatchAfterThreshold = 22,
) {
  return (source) => {
    let idleCallback;
    let currentPushedItems = [];
    let lastItemsReceived = [];
    return Rx.Observable.create((observer) => {
      let sourceSubscription = source
      .subscribe({
        complete: () => {
          observer.complete();
        },
        error: (error) => {
          observer.error(error);
        },
        next: (items) => {
          try {
            lastItemsReceived = items;

            if (idleCallback) {
              return;
            }

            if (items.length > currentPushedItems.length) {
              const idleCbFn = () => {
                if (currentPushedItems.length > lastItemsReceived.length) {
                  throw new Error('currentPushedItems should never be larger than lastItemsReceived.');
                }

                const from = currentPushedItems.length;
                const to = from + batchSize;
                const last = lastItemsReceived.length;
                if (from < dontBatchAfterThreshold) {
                  for (let i = from ; i < to && i < last ; i++ ) {
                    currentPushedItems[i] = lastItemsReceived[i];
                  }
                } else {
                  currentPushedItems = lastItemsReceived;
                }

                if (currentPushedItems.length < lastItemsReceived.length) {
                  idleCallback = window.requestIdleCallback(() => {
                    idleCbFn();
                  });
                } else {
                  idleCallback = undefined;
                }

                observer.next(currentPushedItems);
              };
              idleCallback = window.requestIdleCallback(() => {
                idleCbFn();
              });
            } else {
              currentPushedItems = items;
              observer.next(currentPushedItems);
            }
          } catch (error) {
            observer.error(error);
          }
        },
      });

      return () => {
        sourceSubscription.unsubscribe();
        sourceSubscription = undefined;
        lastItemsReceived = undefined;
        currentPushedItems = undefined;
        if (idleCallback) {
          window.cancelIdleCallback(idleCallback);
          idleCallback = undefined;
        }
        observer = undefined;
      };
    });
  };
}

function sleep(milliseconds) {
  var start = new Date().getTime();
  for (var i = 0; i < 1e7; i++) {
    if ((new Date().getTime() - start) > milliseconds){
      break;
    }
  }
}

let testSource = Rx.Observable.of(
  [1,2,3],
  [1,2,3,4,5,6],
)
.concat(Rx.Observable.never());

testSource
.let(addAdditionalOnIdle(2))
.subscribe((list) => {
  // Simulate a slow synchronous consumer with a busy loop sleep implementation
	sleep(1000);
	document.body.innerHTML += "<p>" + list + "</p>";
});
&#13;
<script src="https://unpkg.com/rxjs@5.4.3/bundles/Rx.min.js"></script>
&#13;
&#13;
&#13;