【问题标题】:How to do polling in svelte?如何在 Svelte 中进行轮询?
【发布时间】:2020-08-07 00:16:35
【问题描述】:

我有一个特定的应用程序应该执行以下操作:

  1. 您有一份要检查的作业列表
  2. 单击作业时,您会看到详细视图
  3. 详细视图将轮询 API 以查看实时进度更新

我在下面的 REPL 中做了一个基本的实现:https://svelte.dev/repl/fcdce26dc0d843dbb4b394dcd2c838af?version=3.20.1

这种方法有几个问题:

  1. Job.svelte 视图应该在您提供新 ID 并清除之前的所有轮询器时基本重置,但现在底部的响应式语句非常尴尬
  2. 由于轮询器执行异步获取,即使处理程序已经在执行,超时处理程序poller 也可能被清除。这会导致出现多个轮询器循环(您可以通过以 0 到 2 秒之间的随机间隔单击作业列表来重现此情况)
  3. 当前的方法对开发人员不友好,很容易被破坏。上面的“错误”可以通过跟踪引用/锁定之类的东西来修复,但这样就更难理解了。

对于这个用例,有什么更好的实现方式(在 Svelte 中)?

非常感谢!

【问题讨论】:

    标签: polling svelte


    【解决方案1】:

    我认为 1. 只是 Svelte 的方法。没有 ngOnChanges 像 angular。

    2./3.我认为这是一个独立于 Svelte 的问题。带有时间竞赛的异步东西总是很难的。 rxjs 之类的库可以更轻松地处理此问题,但学习曲线陡峭。一个例子:

    <script>
      import { interval, Subject } from 'rxjs';
      import { switchMap, take, map, startWith, tap } from 'rxjs/operators';
    
      const cache = {};
      const id$ = new Subject();
      const progress$ = id$.pipe(
        // every time id$ gets a new id, start new interval
        switchMap(id => {
          return interval(1000).pipe(
            // every time the interval emits, do an api call
            switchMap(() => {
              // fake api call
              return interval(200).pipe(
                take(1),
                map(() => id + '::' + Math.random()),
                // store value in cache
                tap(value => cache[id] = value)
              );
            }),
            // start with cached value
            startWith(cache[id]),
          );
        })
      );
    
      let id = 1;
      function switchPoll() {
        id = id === 2 ? 1 : 2;
        id$.next(id);
      }
    </script>
    
    <p>{$progress$}</p>
    <button on:click={switchPoll}>Switch</button>
    

    【讨论】:

    • 这太酷了!我以前玩过 rxjs,但我没有考虑过。感谢您的回复!如果 API 调用需要很长时间,或者我想做一些类似指数回退的事情,那么 1 秒的固定间隔可能不是一个好主意。这就是我在示例中使用setTimeout 的原因,你能在rxjs 中做类似的事情吗?再次感谢!
    • 如果您担心您的 API 可能需要太长时间,您可以使用 exhaustMap 而不是 switchMap。这意味着如果 API 花费的时间超过 1 秒,只要内部 observable(也称为 api 调用)未完成,接下来的传入间隔就会被忽略。指数退避有点难以实现,更多信息请参见stackoverflow.com/questions/53015170/…
    【解决方案2】:

    我建议使用来自 svelte 的 await blocks。有了它,您就拥有了开箱即用的异步功能所需的所有逻辑,例如生成加载屏幕或捕获错误。我修改了您的REPL,使用自定义超时功能(睡眠)来模拟从远程服务器获取数据:

    <script>    
        let selectedJob;
        let progress = 0
            function sleep(ms) {
          return new Promise(resolve => setTimeout(resolve, ms));
        }
    
        const pollProgress = async (id) => {    
            if(progress < 10) {;
          await sleep(300)
                progress += 1
                return pollProgress(id)
            }
            return "Done"
        }   
    
        function handleClick(id) {
            selectedJob = id
            promise = pollProgress(id);
        }
        let promise
    </script>
    
    <style>
        .list {
            background-color: yellow;
            cursor: pointer;
        }
    
        .details {
            background-color: cyan;
        }
    </style>
    
    <div class="list">
      {#each new Array(10).fill().map((_, i) => i) as i}
           <div on:click={() => (handleClick(i))}>
                 Job {i}
             </div>
        {/each}
    </div>
    
    {#if selectedJob !== undefined}
        <div class="details">
            <h2>Job details</h2>
            <div>
            <p>
            ID: {selectedJob}
        </p>
    {#await promise}
        <p>Progress: {progress}</p>
    {:then res}
        <p>
            Progress: {res}
        </p>
    {:catch error}
        <p style="color: red">{error.message}</p>
    {/await}
    </div>
        </div>
    {/if}
    

    当它获取数据和字符串Done时,它将显示一个更新进度以模拟轮询,当它完成时,它作为异步函数的结果接收到。

    【讨论】:

    • 非常感谢您的回复!我想反复轮询进度,而不仅仅是一次,所以这个解决方案不适合我的用例。
    • 我用递归函数更新了 repl 和我的代码,模拟了希望的轮询功能。请看一看。
    • 感谢更新代码!这是一种递归生成新 Promise 的有趣方法,我没有考虑过。但是,进度现在在作业之间共享,如果我切换作业并在控制台中打印,我可以看到承诺链没有中断,这意味着您的示例遇到了我在初始帖子中描述的相同问题。
    【解决方案3】:

    Job.svelte 视图应该在您提供新 id 时基本重置,并清除任何以前的轮询器,但现在底部的响应式语句非常尴尬

    就像之前的回复已经指出的那样,这是 Svelte 方式。一开始很尴尬,直到你意识到它非常简单、方便和优雅。

    我会在您的代码中改进两点:

    • 消除每个 ID + 单个进度值分配的混乱缓存;这就是导致您的延迟获取在视觉上显示的原因(延迟获取回复本身并不是一个错误,但是将其显示给您的用户 is);我只是将您的缓存对象重命名为 progress 并选择显示 progress[id],这样延迟获取的回复将在后台更新,但不会在视觉上干扰您当前显示的作业
    • 使用setInterval 而不是多个setTimeout 进行定期轮询
    <script>
        export let id
    
        let progress = {}
        let poller
    
        const setupPoller = (id) => {
            if (poller) {
                clearInterval(poller)
            }
            poller = setInterval(doPoll(id), 2000)
        }
    
        const doPoll = (id) => async () => {
            console.log(`polling ${id}`)
            progress[id] = await new Promise(resolve => setTimeout(() => {
                resolve((progress[id] || 0) + 1)
            }, 500))
        }
    
        $: setupPoller(id)
    </script>
    
    <div>
        <p>
            ID: {id}
        </p>
        <p>
            Progress: {progress[id] || 0}
        <p>
    </div>
    

    this REPL

    【讨论】:

    • 非常感谢您的回复!这真的有助于更好地理解 Svelte 的方式。您是否有一些资源(文章、博客)供我阅读更多相关信息?关于您建议的改进的说明:在实际用例中,我希望能够从后端响应完成/失败并实现指数回退之类的东西,这就是为什么我选择 setTimeout 而不是 @987654329 @.
    • @Wilco 非常欢迎您。想不出任何具体的文章或博客,但我确实关注了 Twitter 上的 @sveltejs@SvelteSociety 帐户。 Svelte Society 上周日就各种主题召开了一次会议(还没有时间观看但计划观看):youtube.com/watch?v=0rnG-OlzGSs。至于setInterval 的建议,只是一个建议。如果您的需求需要不同的方法,这是一个完全正当的理由。您也可以使用套接字而不是轮询和推送进度更新。给猫剥皮的多种方法(尽管听起来很可怕);)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 2019-08-08
    • 2022-01-02
    • 1970-01-01
    • 1970-01-01
    • 2018-11-16
    相关资源
    最近更新 更多