简体   繁体   English

使用 rxjs 6 如何创建一个带有缓存的可取消 web 请求管道,并限制同时发生的请求数

[英]Using rxjs 6 how can I create a cancellable web request pipeline with a cache, and which limits the number of requests happening at the same time

I hope someone is able to point me in the right direction, because I am struggling with combining concurrency and the ability to cancel queued requests in rxjs. I am going to try and explain this in sequential events.我希望有人能够指出我正确的方向,因为我正在努力结合并发和取消 rxjs 中排队请求的能力。我将尝试在顺序事件中解释这一点。 Say we have observable A, which receives an array of strings.假设我们有可观察的 A,它接收一个字符串数组。

Events: A observes: ['dog', 'cat', 'elephant', 'tiger'] Downstream checks whether the string.network response is cached, if it exists in cache it gets it from cache, if not it requests it from the web and saves the observable to the cache with a publishReplay / shareReplay.事件:A observes: ['dog', 'cat', 'elephant', 'tiger'] 下游检查 string.network 响应是否被缓存,如果它存在于缓存中则从缓存中获取它,如果不存在则从缓存中请求它web 并使用 publishReplay / shareReplay 将可观察对象保存到缓存中。 There is a limit of 2.network requests happening at once, so it tries to fetch 'dog' and 'cat' from the api (this operation takes over 2000ms).一次发生的 2.network 请求是有限制的,因此它尝试从 api 中获取“狗”和“猫”(此操作需要超过 2000 毫秒)。 After 1000ms A observes another set of values: ['dog', 'rat', 'horse', 'rabbit']. 1000 毫秒后,A 观察到另一组值:['dog', 'rat', 'horse', 'rabbit']。

Next the following should happen.接下来应该发生以下情况。 I don't want 'dog' and 'cat' requests to be cancelled, I want them to finish their request, but 'elephant' and 'tiger' from the first request I want to ignore.我不想取消“狗”和“猫”的请求,我希望他们完成他们的请求,但是我想忽略第一个请求中的“大象”和“老虎”。 Once 'dog' and 'cat' gets their response 'rat' and 'horse' from the second frame should be requested from the.network, and lastly once either of those resolve 'rabbit' is requested.一旦“狗”和“猫”得到它们的响应,第二帧中的“老鼠”和“马”就应该从.network请求,最后一次请求解析“兔子”中的任何一个。

Here is my current code.这是我当前的代码。 I have tried switching between defer and from for the.networkObservable and the behavior is different, and neither is what I want.我已经尝试在 defer 和 from for the.networkObservable 之间切换,但行为不同,这也不是我想要的。


const cache = new Map();

// Fake Promise to fake a api request
function later(delay, value) {
  console.log('requesting', value);
  return new Promise(resolve => setTimeout(resolve, delay, value));
}


const testObservable = of(['dog', 'rat', 'horse', 'rabbit']).pipe(
  delay(1000),
  startWith(['dog', 'cat', 'elephant', 'tiger'])
);

testObservable.pipe(
  map(array => from(array).pipe(
    publish(arrayObservable => {
      const cachedObservable = arrayObservable.pipe(
        filter(id => cache.has(id)),
        flatMap(id => cache.get(id), 1)
      );
      const uncachedObservable = arrayObservable.pipe(
        filter(id => !cache.has(id)),
        flatMap(id => {
          const networkObservable = from(later(2000, id)).pipe(
            tap(e => console.log('response', e)),
            map(e => 'parsed: ' + e),
            tap(e => console.log('parsed', e)),
            publishReplay(1),
            refCount(),
            take(1)
          );
          cache.set(id, networkObservable);
          return networkObservable;
        }, 2)
      );
      return merge(cachedObservable, uncachedObservable);
    })
  )),
  switchAll()
)

This results in an output of:这导致 output 为:

requesting dog
requesting cat
requesting rat
requesting horse
response dog
parsed parsed: dog
response rat
parsed parsed: rat
requesting rabbit
response horse
parsed parsed: horse
response rabbit
parsed parsed: rabbit

Which is close to the wanted behavior, but with one glaring defect.这接近于想要的行为,但有一个明显的缺陷。 Rat and horse is being requested and is not waiting for dog and cat to resolve before being executed.正在请求 Rat 和 Horse,而不是等待 dog 和 cat 在执行前解决。 However, the 'tiger' and 'elephant' has been properly disposed, so that functionality works.但是,“老虎”和“大象”已被妥善处理,因此功能正常。

Will I have to create a separate subject which handles the requests?我是否必须创建一个单独的主题来处理请求?

I have tried to wire up a solution for this interesting problem, at least for what I have understood of it.我试图为这个有趣的问题找到一个解决方案,至少就我所了解的而言。

The starting point is testObservable which is a stream of Arrays<string> .起点是testObservable ,它是Arrays<string>的 stream。 Every string of these arrays represents a potential request to a back end service.这些 arrays 中的每个string都代表对后端服务的潜在请求。 There can not be more than 2 requests on the fly at each given time, so there must be some kind of queue mechanism in place.每个给定时间不能有超过 2 个正在运行的请求,因此必须有某种队列机制。 For this I use the concurrency parameter of mergeMap .为此,我使用mergeMapconcurrency参数。

The key point here is that, any time a new Array is emitted by testObservable , any request related to string s contained in arrays emitted previously which has not yet been sent to the remote service should be stopped.这里的关键点是,每当 testObservable 发出一个新的 Array 时,任何与先前发出的testObservable中包含的string相关但尚未发送到远程服务的请求都应停止。

So I start creating a stream of objects containing the string that is the input for the remote service call as well as a stop indicator like this所以我开始创建一个 stream 的对象,其中包含作为远程服务调用输入的string以及这样的停止指示器

testObservable
  .pipe(
    mergeMap((a) => {
      i++;
      if (arrays[i - 1]) {
        arrays[i - 1].stop = true;
      }
      const thisArray = { stop: false, i };
      arrays[i] = thisArray;
      return from(a.map((_v) => ({ v: _v, ssTop: arrays[i] }))).pipe(
        mergeMap((d) => {
          // d is an object containing the parameter for the remote call and an object of type {stop: boolean, i: number}
          // for every string of every array a d is emitted
        }, 2)
      );
    })
  )

Then, for each d emitted I can implement a logic that makes sure that a call to the remote service is performed only if the stop flag is not set to true like this然后,对于每个发出的d我都可以实现一个逻辑,确保仅当stop标志未像这样设置为true时才执行对远程服务的调用

d.ssTop.stop
            ? NEVER
            : from(later(2000, d.v))

Notice that the stop flag for the i th array is set to true any time the i+1 th array is emitted by testObservable ensuring so that no calls related to the i th array are made after the i+1 th array has been emitted.请注意, testObservable发出第i+1个数组时,第i个数组的stop标志设置为 true,确保在第 i i+1 1 个数组发出后不会进行与第i个数组相关的调用。

This could be how the complete code could look like这可能是完整代码的样子

const cache = new Map();

// Fake Promise to fake a api request
function later(delay, value) {
  console.log("requesting", value);
  return new Promise((resolve) => setTimeout(resolve, delay, value));
}

const testObservable = of(["dog", "rat", "horse", "rabbit"]).pipe(
  delay(1000),
  startWith(["dog", "cat", "elephant", "tiger"])
);

let i = 0;
let arrays: { stop: boolean; i: number }[] = [];

testObservable
  .pipe(
    mergeMap((a) => {
      i++;
      if (arrays[i - 1]) {
        arrays[i - 1].stop = true;
      }
      const thisArray = { stop: false, i };
      arrays[i] = thisArray;
      return from(a.map((_v) => ({ v: _v, ssTop: arrays[i] }))).pipe(
        mergeMap((d) => {
          return d.ssTop.stop
            ? NEVER
            : cache[d.v]
            ? of(`${d.v} is the returned from cache}`)
            : from(later(2000, d.v)).pipe(
                map((v: any) => {
                  cache[v] = v;
                  return `${v} is the returned value ${d.ssTop.i}`;
                })
              );
        }, 2)
      );
    })
  )
  .subscribe({
    next: (d) => {
      console.log(d);
    },
  });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM