使用 React 实现顺序作业队列

时间:2021-08-02 00:02:44

标签: asynchronous web-worker job-queue

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

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

电流输出

Math.random()

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

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 释放锁定变量 queue.shift() 时执行 _isBusy 指示当前承诺已解决并准备处理下一个。在此之前,它会在处理当前项目时在 getNonSequentialResponse 循环中等待。我的想法是,由于元素总是从头部移除,因此项目将按照它们输入的顺序进行处理。

错误: 但是,据我所知,这是错误的方法,因为 UI 线程正在等待并导致错误 潜在无限循环:超过 10001 次迭代。您可以通过创建 sandbox.config.json 文件来禁用此检查。

1 个答案:

答案 0 :(得分: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.