【问题标题】:Implementing sequential job queue using React使用 React 实现顺序作业队列
【发布时间】:2021-08-02 00:02:44
【问题描述】:

我希望实现一个作业队列,以确保 API 的响应按输入的输入项的顺序返回,即使每个 API 调用都可能花费可变的时间。

在此处查看代码框 https://codesandbox.io/s/sequential-api-response-eopue - 当我在输入字段中输入 item(例如 1、12、1234、12345)并按 Enter 键时,它会转到模拟后端,我将 item+-response 返回到表示相应输入的输出。但是,我使用Math.random() 对每个调用使用了不同的超时来模拟 API 可能需要不确定的时间量的真实场景。

电流输出

processing:  1 
processing:  12 
processing:  123 
processing:  1234 
processing:  12345 
processing:  123456 
response: 1234-response 
response: 12-response 
response: 123456-response 
response: 123-response 
response: 1-response 
response: 12345-response 

预期输出 我想看到的输出是

processing:  1 
processing:  12 
processing:  123 
processing:  1234 
processing:  12345 
processing:  123456 
response: 1-response 
response: 12-response 
response: 123-response 
response: 1234-response 
response: 12345-response 
response: 123456-response 

我的尝试: 我试图实现函数getSequentialResponse(它是函数getNonSequentialResponse 的包装器,会生成上面的错误输出)。此函数将用户输入的item 添加到queue 中,并且仅当getNonSequentialResponse 释放锁定变量_isBusy 时才执行queue.shift(),表明当前promise 已解决并准备好处理下一个promise。在此之前,它会在处理当前项目时在while 循环中等待。我的想法是,由于元素总是从头部移除,因此项目将按照输入的顺序进行处理。

错误: 但是,据我了解,这是错误的方法,因为 UI 线程正在等待并导致错误 Potential infinite loop: exceeded 10001 迭代。您可以通过创建一个 sandbox.config.json 文件来禁用此检查。

【问题讨论】:

    标签: asynchronous web-worker job-queue


    【解决方案1】:

    这里有几件事要考虑。

    1. while 循环在这里是错误的方法 - 因为我们在 JavaScript 中使用异步操作,所以我们需要记住事件循环的工作原理(here's a good talk 如果您需要入门)。您的 while 循环将占用调用堆栈并阻止事件循环的其余部分(包括处理 Promise 的 ES6 作业队列和处理超时的回调队列)发生。
    2. 那么在没有 while 循环的情况下,JavaScript 中是否有一种方法可以控制何时解析一个函数,以便我们可以进入下一个函数?当然——这是承诺!我们会将作业包装在 Promise 中,并且只有在我们准备好继续前进时才解决该 Promise,或者如果出现错误则拒绝它。
    3. 既然我们谈论的是一个特定的数据结构,一个队列,让我们使用一些更好的术语来改进我们的心智模型。我们不是在“处理”这些工作,而是在“排队”它们。如果我们同时处理它们(即“处理 1”、“处理 2”等),我们就不会按顺序执行它们。
    export default class ItemProvider {
      private _queue: any;
      private _isBusy: boolean;
    
      constructor() {
        this._queue = [];
        this._isBusy = false;
      }
    
      public enqueue(job: any) {
        console.log("Enqueing", job);
        // we'll wrap the job in a promise and include the resolve and reject functions in the job we'll enqueue, so we can control when we resolve and execute them sequentially
        new Promise((resolve, reject) => {
          this._queue.push({ job, resolve, reject });
        });
        // we'll add a nextJob function and call it when we enqueue a new job; we'll use _isBusy to make sure we're executing the next job sequentially
        this.nextJob();
      }
    
      private nextJob() {
        if (this._isBusy) return;
        const next = this._queue.shift();
        // if the array is empty shift() will return undefined
        if (next) {
          this._isBusy = true;
          next
            .job()
            .then((value: any) => {
              console.log(value);
              next.resolve(value);
              this._isBusy = false;
              this.nextJob();
            })
            .catch((error: any) => {
              console.error(error);
              next.reject(error);
              this._isBusy = false;
              this.nextJob();
            });
        }
      }
    }
    

    现在在我们的 React 代码中,我们将使用您创建的辅助函数创建一个虚假的异步函数并将工作排入队列!

    import "./styles.css";
    import ItemProvider from "./ItemProvider";
    // import { useRef } from "react";
    
    // I've modified your getNonSequentialResponse function as a helper function to return a fake async job function that resolves to our item
    const getFakeAsyncJob = (item: any) => {
      const timeout = Math.floor(Math.random() * 2000) + 500;
      // const timeout = 0;
      return () =>
        new Promise((resolve) => {
          setTimeout(() => {
            resolve(item + "-response");
          }, timeout);
        });
    };
    
    export default function App() {
      const itemProvider = new ItemProvider();
    
      function keyDownEventHandler(ev: KeyboardEvent) {
        if (ev.keyCode === 13) {
          const textFieldValue = (document.getElementById("textfieldid") as any)
            .value;
    
          // not sequential
          // itemProvider.getNonSequentialResponse(textFieldValue).then((response) => {
          //   console.log("response: " + response);
          // });
          
          // we make a fake async function tht resolves to our textFieldValue
          const myFakeAsyncJob = getFakeAsyncJob(textFieldValue);
          // and enqueue it 
          itemProvider.enqueue(myFakeAsyncJob);
        }
      }
    
      return (
        <div className="App">
          <input
            id="textfieldid"
            placeholder={"Type and hit Enter"}
            onKeyDown={keyDownEventHandler}
            type="text"
          />
    
          <div className="displaylabelandbox">
            <label>Display box below</label>
            <div className="displaybox">hello</div>
          </div>
        </div>
      );
    }
    

    Here's the codesandbox.

    【讨论】:

    • 谢谢!我读了几次你的实现并玩了一下。我喜欢假异步作业的概念,它是一个返回承诺并了解它是如何被 _isBusy 标志保护的函数。但是,我不明白为什么 enque 需要有一个承诺。为什么队列需要在 Promise 中?我稍微修改了 enqueue 以删除此处的承诺,并且仍然得到相同的输出,除非我弄错了。 codesandbox.io/s/sequential-api-response-forked-6b9ej?file=/src/…你能解释一下为什么 enqueue func 需要 Promise 吗?
    • 几个原因 - 如果您不能保证作业是异步的怎么办? (我意识到我们需要重构 nextJob() 函数来测试 Promise 或在这种情况下使用 async/await 语法)。包装 Promise 可确保队列按我们希望的方式运行。此外,如果出于某种原因此 API 的用户想要作业返回的值怎么办?在您的实现中,您很容易在该值上调用了 console.log(),但 nextJob() 是 ItemProvider 类的私有方法,该 API 的用户如何获得该值?我们 resolve() 它在链上
    猜你喜欢
    • 1970-01-01
    • 2015-07-06
    • 2012-12-18
    • 1970-01-01
    • 1970-01-01
    • 2021-12-01
    • 2014-08-19
    • 1970-01-01
    • 2011-10-25
    相关资源
    最近更新 更多