request queue dedupes requests by id
This commit is contained in:
parent
3f82b7d97a
commit
4f68ddfad5
@ -35,7 +35,6 @@ export default function Main({ sidebar, header, footer, Page }: Props) {
|
||||
const theme = useTheme();
|
||||
const quiz = useCurrentQuiz();
|
||||
const quizConfig = quiz?.config;
|
||||
const { questions } = useQuestionsStore();
|
||||
const { editQuizId } = useQuizStore();
|
||||
const currentStep = useQuizStore((state) => state.currentStep);
|
||||
const { isTestServer } = useDomainDefine();
|
||||
|
@ -380,15 +380,15 @@ export const findQuestionById = (quizId: number) => {
|
||||
let found = null;
|
||||
questionStore
|
||||
.getState()
|
||||
["listQuestions"][quizId].some(
|
||||
(quiz: AnyTypedQuizQuestion, index: number) => {
|
||||
if (quiz.backendId === quizId) {
|
||||
found = { quiz, index };
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
);
|
||||
[
|
||||
"listQuestions"
|
||||
][quizId].some((quiz: AnyTypedQuizQuestion, index: number) => {
|
||||
if (quiz.backendId === quizId) {
|
||||
found = { quiz, index };
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
return found;
|
||||
};
|
||||
|
||||
|
@ -13,21 +13,14 @@ import {
|
||||
UntypedQuizQuestion,
|
||||
createQuestionVariant,
|
||||
} from "@model/questionTypes/shared";
|
||||
import { defaultQuestionByType } from "../../constants/default";
|
||||
import { produce } from "immer";
|
||||
import { nanoid } from "nanoid";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { defaultQuestionByType } from "../../constants/default";
|
||||
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
|
||||
import { RequestQueue } from "../../utils/requestQueue";
|
||||
import { updateRootContentId } from "@root/quizes/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { QuestionsStore, useQuestionsStore } from "./store";
|
||||
import { useUiTools } from "../uiTools/store";
|
||||
import { withErrorBoundary } from "react-error-boundary";
|
||||
import { QuizQuestionResult } from "@model/questionTypes/result";
|
||||
import { replaceEmptyLinesToSpace } from "../../utils/replaceEmptyLinesToSpace";
|
||||
import { useQuizPreviewStore } from "@root/quizPreview";
|
||||
import { useQuizStore } from "@root/quizes/store";
|
||||
import { RequestQueue } from "../../utils/requestQueue";
|
||||
import { QuestionsStore, useQuestionsStore } from "./store";
|
||||
|
||||
export const setQuestions = (questions: RawQuestion[] | null | undefined) =>
|
||||
setProducedState(
|
||||
@ -244,9 +237,7 @@ export const cancelQuestionDeletion = (questionId: string) =>
|
||||
},
|
||||
);
|
||||
|
||||
const REQUEST_DEBOUNCE = 200;
|
||||
const requestQueue = new RequestQueue();
|
||||
let requestTimeoutId: ReturnType<typeof setTimeout>;
|
||||
|
||||
export const updateQuestion = async <T = AnyTypedQuizQuestion>(
|
||||
questionId: string,
|
||||
@ -275,8 +266,6 @@ export const updateQuestion = async <T = AnyTypedQuizQuestion>(
|
||||
},
|
||||
);
|
||||
|
||||
// clearTimeout(requestTimeoutId);
|
||||
|
||||
const request = async () => {
|
||||
const q =
|
||||
useQuestionsStore.getState().questions.find((q) => q.id === questionId) ||
|
||||
@ -321,9 +310,7 @@ export const updateQuestion = async <T = AnyTypedQuizQuestion>(
|
||||
return;
|
||||
}
|
||||
|
||||
// requestTimeoutId = setTimeout(() => {
|
||||
requestQueue.enqueue(request);
|
||||
// }, REQUEST_DEBOUNCE);
|
||||
requestQueue.enqueue(`updateQuestion-${questionId}`, request);
|
||||
};
|
||||
|
||||
export const addQuestionVariant = (questionId: string) => {
|
||||
@ -453,7 +440,7 @@ export const createTypedQuestion = async (
|
||||
questionId: string,
|
||||
type: QuestionType,
|
||||
) =>
|
||||
requestQueue.enqueue(async () => {
|
||||
requestQueue.enqueue(`createTypedQuestion-${questionId}`, async () => {
|
||||
const questions = useQuestionsStore.getState().questions;
|
||||
const question = questions.find((q) => q.id === questionId);
|
||||
if (!question) return;
|
||||
@ -501,7 +488,7 @@ export const createTypedQuestion = async (
|
||||
});
|
||||
|
||||
export const deleteQuestion = async (questionId: string) =>
|
||||
requestQueue.enqueue(async () => {
|
||||
requestQueue.enqueue(`deleteQuestion-${questionId}`, async () => {
|
||||
const question = useQuestionsStore
|
||||
.getState()
|
||||
.questions.find((q) => q.id === questionId);
|
||||
@ -525,7 +512,7 @@ export const deleteQuestion = async (questionId: string) =>
|
||||
});
|
||||
|
||||
export const copyQuestion = async (questionId: string, quizId: number) =>
|
||||
requestQueue.enqueue(async () => {
|
||||
requestQueue.enqueue(`copyQuestion-${quizId}-${questionId}`, async () => {
|
||||
const question = useQuestionsStore
|
||||
.getState()
|
||||
.questions.find((q) => q.id === questionId);
|
||||
@ -585,7 +572,7 @@ export const copyQuestion = async (questionId: string, quizId: number) =>
|
||||
}
|
||||
});
|
||||
|
||||
function setProducedState<A extends string | { type: unknown }>(
|
||||
function setProducedState<A extends string | { type: string }>(
|
||||
recipe: (state: QuestionsStore) => void,
|
||||
action?: A,
|
||||
) {
|
||||
@ -635,7 +622,7 @@ export const createResult = async (
|
||||
quizId: number | null | undefined,
|
||||
parentContentId?: string,
|
||||
) =>
|
||||
requestQueue.enqueue(async () => {
|
||||
requestQueue.enqueue(`createResult-${quizId}`, async () => {
|
||||
if (!quizId || !parentContentId) {
|
||||
console.error(
|
||||
"Нет данных для создания результата. quizId: ",
|
||||
|
@ -3,15 +3,14 @@ import { devlog, getMessageFromFetchError } from "@frontend/kitui";
|
||||
import { quizToEditQuizRequest } from "@model/quiz/edit";
|
||||
import { Quiz, RawQuiz, rawQuizToQuiz } from "@model/quiz/quiz";
|
||||
import { QuizConfig, maxQuizSetupSteps } from "@model/quizSettings";
|
||||
import { createUntypedQuestion, updateQuestion } from "@root/questions/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { produce } from "immer";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { NavigateFunction } from "react-router-dom";
|
||||
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
|
||||
import { RequestQueue } from "../../utils/requestQueue";
|
||||
import { QuizStore, useQuizStore } from "./store";
|
||||
import { createUntypedQuestion, updateQuestion } from "@root/questions/actions";
|
||||
import { useCurrentQuiz } from "./hooks";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
|
||||
export const setEditQuizId = (quizId: number | null) =>
|
||||
setProducedState(
|
||||
@ -157,7 +156,7 @@ export const updateQuiz = (
|
||||
clearTimeout(requestTimeoutId);
|
||||
requestTimeoutId = setTimeout(async () => {
|
||||
requestQueue
|
||||
.enqueue(async () => {
|
||||
.enqueue(`updateQuiz-${quizId}`, async () => {
|
||||
const quiz = useQuizStore
|
||||
.getState()
|
||||
.quizes.find((q) => q.id === quizId);
|
||||
@ -178,7 +177,7 @@ export const updateQuiz = (
|
||||
};
|
||||
|
||||
export const createQuiz = async (navigate: NavigateFunction) =>
|
||||
requestQueue.enqueue(async () => {
|
||||
requestQueue.enqueue("createQuiz", async () => {
|
||||
try {
|
||||
const rawQuiz = await quizApi.create();
|
||||
const quiz = rawQuizToQuiz(rawQuiz);
|
||||
@ -196,7 +195,7 @@ export const createQuiz = async (navigate: NavigateFunction) =>
|
||||
});
|
||||
|
||||
export const deleteQuiz = async (quizId: string) =>
|
||||
requestQueue.enqueue(async () => {
|
||||
requestQueue.enqueue(`deleteQuiz-${quizId}`, async () => {
|
||||
const quiz = useQuizStore.getState().quizes.find((q) => q.id === quizId);
|
||||
if (!quiz) return;
|
||||
|
||||
|
@ -33,7 +33,7 @@ const removeResult = (resultId: string) =>
|
||||
});
|
||||
|
||||
export const deleteResult = async (resultId: number) =>
|
||||
requestQueue.enqueue(async () => {
|
||||
requestQueue.enqueue(`deleteResult-${resultId}`, async () => {
|
||||
const result = useResultStore
|
||||
.getState()
|
||||
.results.find((r) => r.id === resultId);
|
||||
@ -54,32 +54,35 @@ export const obsolescenceResult = async (
|
||||
resultId: string,
|
||||
editQuizId: number,
|
||||
) =>
|
||||
requestQueue.enqueue(async () => {
|
||||
const result = useResultStore
|
||||
.getState()
|
||||
.results.find((r) => r.id === resultId);
|
||||
if (!result) return;
|
||||
if (result.new === false) return;
|
||||
let lossDebouncer: null | ReturnType<typeof setTimeout> = null;
|
||||
let lossId: string[] = [] as string[];
|
||||
if (!lossId.includes(resultId)) lossId.push(resultId);
|
||||
if (typeof lossDebouncer === "number") clearTimeout(lossDebouncer);
|
||||
lossDebouncer = setTimeout(async () => {
|
||||
//стреляем на лишение новизны
|
||||
try {
|
||||
await resultApi.obsolescence(lossId);
|
||||
//сбрасываем массив
|
||||
lossId = [];
|
||||
} catch (error) {
|
||||
devlog("Error", error);
|
||||
requestQueue.enqueue(
|
||||
`obsolescenceResult-${resultId}-${editQuizId}`,
|
||||
async () => {
|
||||
const result = useResultStore
|
||||
.getState()
|
||||
.results.find((r) => r.id === resultId);
|
||||
if (!result) return;
|
||||
if (result.new === false) return;
|
||||
let lossDebouncer: null | ReturnType<typeof setTimeout> = null;
|
||||
let lossId: string[] = [] as string[];
|
||||
if (!lossId.includes(resultId)) lossId.push(resultId);
|
||||
if (typeof lossDebouncer === "number") clearTimeout(lossDebouncer);
|
||||
lossDebouncer = setTimeout(async () => {
|
||||
//стреляем на лишение новизны
|
||||
try {
|
||||
await resultApi.obsolescence(lossId);
|
||||
//сбрасываем массив
|
||||
lossId = [];
|
||||
} catch (error) {
|
||||
devlog("Error", error);
|
||||
|
||||
const message = getMessageFromFetchError(error) ?? "";
|
||||
enqueueSnackbar(`Ошибка. ${message}`);
|
||||
}
|
||||
}, 3000);
|
||||
const resultList = await resultApi.getList(editQuizId);
|
||||
setResults(resultList);
|
||||
});
|
||||
const message = getMessageFromFetchError(error) ?? "";
|
||||
enqueueSnackbar(`Ошибка. ${message}`);
|
||||
}
|
||||
}, 3000);
|
||||
const resultList = await resultApi.getList(editQuizId);
|
||||
setResults(resultList);
|
||||
},
|
||||
);
|
||||
|
||||
export const answerResultListExport = async (
|
||||
editQuizId: number,
|
||||
@ -111,7 +114,7 @@ export const answerResultListExport = async (
|
||||
download();
|
||||
};
|
||||
|
||||
function setProducedState<A extends string | { type: unknown }>(
|
||||
function setProducedState<A extends string | { type: string }>(
|
||||
recipe: (state: ResultStore) => void,
|
||||
action?: A,
|
||||
) {
|
||||
|
@ -1,14 +1,15 @@
|
||||
export class RequestQueue<T = unknown> {
|
||||
export class RequestQueue<IdType, T = unknown> {
|
||||
private pendingPromise = false;
|
||||
private items: Array<{
|
||||
id: IdType;
|
||||
action: () => Promise<T>;
|
||||
resolve: (value: T) => void;
|
||||
reject: (reason?: any) => void;
|
||||
}> = [];
|
||||
|
||||
enqueue(action: () => Promise<T>) {
|
||||
enqueue(id: IdType, action: () => Promise<T>) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.items.push({ action, resolve, reject });
|
||||
this.items.push({ action, resolve, reject, id });
|
||||
this.dequeue();
|
||||
});
|
||||
}
|
||||
@ -19,6 +20,9 @@ export class RequestQueue<T = unknown> {
|
||||
const item = this.items.shift();
|
||||
if (!item) return;
|
||||
|
||||
// remove tasks with same id since they are outdated
|
||||
this.items = this.items.filter((i) => i.id !== item.id);
|
||||
|
||||
try {
|
||||
this.pendingPromise = true;
|
||||
const payload = await item.action();
|
||||
|
Loading…
Reference in New Issue
Block a user