使用 React 实现顺序作业队列
Implementing sequential job queue using React
我希望实现一个作业队列,确保 API 的响应按输入项的顺序 return 编辑,即使每次 API 接听电话也是如此可能有可变的时间。
在此处查看 codesandbox https://codesandbox.io/s/sequential-api-response-eopue - 当我在输入字段中输入 item
例如 1、12、1234、12345 并按 Enter 键时,它会转到一个模拟后端,我 return 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
中,只有当锁变量_isBusy
被getNonSequentialResponse
释放时才做queue.shift()
表示当前的promise已解决并准备处理下一个。在此之前,它会在处理当前项目时在 while
循环中等待。我的想法是,由于元素总是被移除头部,因此项目将按照输入的顺序进行处理。
错误:
但是,据我所知,这是错误的方法,因为 UI 线程正在等待并导致错误 Potential infinite loop: exceeded 10001 iterations。您可以通过创建 sandbox.config.json 文件来禁用此检查。
这里有几件事需要考虑。
- while 循环在这里是错误的方法 - 因为我们在 JavaScript 中使用异步操作,所以我们需要记住事件循环的工作原理(here's a good talk 如果您需要入门).您的 while 循环将占用调用堆栈并防止事件循环的其余部分(包括处理 Promises 的 ES6 作业队列和处理超时的回调队列)发生。
- 那么在没有 while 循环的情况下,JavaScript 中是否有一种方法可以控制何时解析一个函数,以便我们可以继续下一个函数?当然 - 这是 Promises!我们会将作业包装在一个 Promise 中,并且仅在我们准备好继续前进时才解决该 Promise,如果出现错误则拒绝它。
- 既然我们在谈论一个特定的数据结构,一个队列,让我们使用一些更好的术语来改进我们的心智模型。我们不是在“处理”这些作业,而是在“排队”它们。如果我们同时处理它们(即“处理 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>
);
}
我希望实现一个作业队列,确保 API 的响应按输入项的顺序 return 编辑,即使每次 API 接听电话也是如此可能有可变的时间。
在此处查看 codesandbox https://codesandbox.io/s/sequential-api-response-eopue - 当我在输入字段中输入 item
例如 1、12、1234、12345 并按 Enter 键时,它会转到一个模拟后端,我 return 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
中,只有当锁变量_isBusy
被getNonSequentialResponse
释放时才做queue.shift()
表示当前的promise已解决并准备处理下一个。在此之前,它会在处理当前项目时在 while
循环中等待。我的想法是,由于元素总是被移除头部,因此项目将按照输入的顺序进行处理。
错误: 但是,据我所知,这是错误的方法,因为 UI 线程正在等待并导致错误 Potential infinite loop: exceeded 10001 iterations。您可以通过创建 sandbox.config.json 文件来禁用此检查。
这里有几件事需要考虑。
- while 循环在这里是错误的方法 - 因为我们在 JavaScript 中使用异步操作,所以我们需要记住事件循环的工作原理(here's a good talk 如果您需要入门).您的 while 循环将占用调用堆栈并防止事件循环的其余部分(包括处理 Promises 的 ES6 作业队列和处理超时的回调队列)发生。
- 那么在没有 while 循环的情况下,JavaScript 中是否有一种方法可以控制何时解析一个函数,以便我们可以继续下一个函数?当然 - 这是 Promises!我们会将作业包装在一个 Promise 中,并且仅在我们准备好继续前进时才解决该 Promise,如果出现错误则拒绝它。
- 既然我们在谈论一个特定的数据结构,一个队列,让我们使用一些更好的术语来改进我们的心智模型。我们不是在“处理”这些作业,而是在“排队”它们。如果我们同时处理它们(即“处理 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>
);
}