[Dev] TeactN: Assert when setting global asynchronously

This commit is contained in:
Alexander Zinchuk 2022-04-09 01:17:42 +02:00
parent fbe03b556b
commit a7e2695f2e
5 changed files with 53 additions and 31 deletions

View File

@ -65,7 +65,7 @@ import {
selectSendAs,
selectSponsoredMessage,
} from '../../selectors';
import { debounce, rafPromise } from '../../../util/schedulers';
import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers';
import { isServiceNotificationMessage } from '../../helpers';
const uploadProgressCallbacks = new Map<number, ApiOnProgress>();
@ -113,7 +113,9 @@ addActionHandler('loadViewportMessages', (global, actions, payload) => {
}
if (!areAllLocal) {
void loadViewportMessages(chat, threadId, offsetId, LoadMoreDirection.Around, isOutlying, isBudgetPreload);
onTickEnd(() => {
void loadViewportMessages(chat, threadId, offsetId, LoadMoreDirection.Around, isOutlying, isBudgetPreload);
});
}
} else {
const offsetId = direction === LoadMoreDirection.Backwards ? viewportIds[0] : viewportIds[viewportIds.length - 1];
@ -127,7 +129,9 @@ addActionHandler('loadViewportMessages', (global, actions, payload) => {
global = safeReplaceViewportIds(global, chatId, threadId, newViewportIds);
}
void loadWithBudget(actions, areAllLocal, isOutlying, isBudgetPreload, chat, threadId, direction, offsetId);
onTickEnd(() => {
void loadWithBudget(actions, areAllLocal, isOutlying, isBudgetPreload, chat, threadId, direction, offsetId);
});
if (isBudgetPreload) {
return undefined;
@ -149,8 +153,6 @@ async function loadWithBudget(
}
if (!isBudgetPreload) {
// Let reducer return and update global
await Promise.resolve();
actions.loadViewportMessages({
chatId: chat.id, threadId, direction, isBudgetPreload: true,
});

View File

@ -47,6 +47,7 @@ import {
import {
getMessageContent, isUserId, isMessageLocal, getMessageText, checkIfReactionAdded,
} from '../../helpers';
import { onTickEnd } from '../../../util/schedulers';
const ANIMATION_DELAY = 350;
@ -497,10 +498,12 @@ addActionHandler('apiUpdate', (global, actions, update) => {
if (shouldNotify) {
const newMessage = selectChatMessage(global, chatId, id);
if (!chat || !newMessage) return;
notifyAboutMessage({
chat,
message: newMessage,
isReaction: true,
onTickEnd(() => {
notifyAboutMessage({
chat,
message: newMessage,
isReaction: true,
});
});
}
@ -548,13 +551,15 @@ function updateThreadUnread(global: GlobalState, actions: GlobalActions, message
if (originMessage) {
global = updateThreadUnreadFromForwardedMessage(global, originMessage, chatId, message.id, isDeleting);
} else {
actions.loadMessage({
chatId,
messageId: message.replyToMessageId,
threadUpdate: {
isDeleting,
lastMessageId: message.id,
},
onTickEnd(() => {
actions.loadMessage({
chatId,
messageId: message.replyToMessageId,
threadUpdate: {
isDeleting,
lastMessageId: message.id,
},
});
});
}
}

View File

@ -6,7 +6,10 @@ import { cloneDeep } from '../util/iteratees';
initCache();
addActionHandler('init', () => {
addActionHandler('init', (global) => {
const initial = cloneDeep(INITIAL_STATE);
return loadCache(initial) || initial;
return {
...global,
...(loadCache(initial) || initial),
};
});

View File

@ -13,7 +13,9 @@ import { isHeavyAnimating } from '../../hooks/useHeavyAnimationCheck';
export default React;
type GlobalState = AnyLiteral;
type GlobalState =
AnyLiteral
& { DEBUG_id: number };
type ActionNames = string;
type ActionPayload = any;
@ -35,6 +37,13 @@ type MapStateToProps<OwnProps = undefined> = ((global: GlobalState, ownProps: Ow
let currentGlobal = {} as GlobalState;
// eslint-disable-next-line @typescript-eslint/naming-convention
let DEBUG_currentGlobalId: number | undefined;
// eslint-disable-next-line @typescript-eslint/naming-convention
const DEBUG_resetIdThrottled = throttleWithTickEnd(() => {
DEBUG_currentGlobalId = Math.random();
});
const actionHandlers: Record<string, ActionHandler[]> = {};
const callbacks: Function[] = [updateContainers];
const actions = {} as Actions;
@ -61,6 +70,12 @@ function runCallbacks(forceOnHeavyAnimation = false) {
export function setGlobal(newGlobal?: GlobalState, options?: ActionOptions) {
if (typeof newGlobal === 'object' && newGlobal !== currentGlobal) {
if (DEBUG) {
if (newGlobal.DEBUG_id !== DEBUG_currentGlobalId) {
throw new Error('[TeactN.setGlobal] Attempt to set an outdated global');
}
}
currentGlobal = newGlobal;
if (options?.forceSyncOnIOs) {
runCallbacks(true);
@ -71,6 +86,12 @@ export function setGlobal(newGlobal?: GlobalState, options?: ActionOptions) {
}
export function getGlobal() {
if (DEBUG) {
DEBUG_currentGlobalId = Math.random();
currentGlobal.DEBUG_id = DEBUG_currentGlobalId;
DEBUG_resetIdThrottled();
}
return currentGlobal;
}
@ -80,12 +101,12 @@ export function getActions() {
function handleAction(name: string, payload?: ActionPayload, options?: ActionOptions) {
actionHandlers[name]?.forEach((handler) => {
const response = handler(currentGlobal, actions, payload);
const response = handler(DEBUG ? getGlobal() : currentGlobal, actions, payload);
if (!response || typeof response.then === 'function') {
return;
}
setGlobal(response, options);
setGlobal(response as GlobalState, options);
});
}

View File

@ -1,7 +1,6 @@
type Scheduler =
typeof requestAnimationFrame
| typeof onTickEnd
| typeof runNow;
| typeof onTickEnd;
export function debounce<F extends AnyToVoidFunction>(
fn: F,
@ -77,10 +76,6 @@ export function throttleWithTickEnd<F extends AnyToVoidFunction>(fn: F) {
return throttleWith(onTickEnd, fn);
}
export function throttleWithNow<F extends AnyToVoidFunction>(fn: F) {
return throttleWith(runNow, fn);
}
export function throttleWith<F extends AnyToVoidFunction>(schedulerFn: Scheduler, fn: F) {
let waiting = false;
let args: Parameters<F>;
@ -109,10 +104,6 @@ export function onIdle(cb: NoneToVoidFunction, timeout?: number) {
}
}
function runNow(fn: NoneToVoidFunction) {
fn();
}
export const pause = (ms: number) => new Promise<void>((resolve) => {
setTimeout(() => resolve(), ms);
});