async-states
Version:
Core of async-states
181 lines (178 loc) • 7.28 kB
JavaScript
import { pending } from '../enums.js';
import { isFunction, __DEV__, cloneProducerProps, emptyArray } from '../utils.js';
import { now, noop, freeze } from '../helpers/core.js';
import { hasCacheEnabled, computeRunHash, getCachedState, didCachedStateExpire, removeCachedStateAndSpreadOnLanes } from './StateCache.js';
import { createProps } from './StateProps.js';
import { run } from '../wrapper.js';
import devtools from '../devtools/Devtools.js';
import { replaceInstanceState, startAlteringState, stopAlteringState } from './StateUpdate.js';
function runcInstance(instance, props) {
let config = instance.config;
let effectDurationMs = Number(config.runEffectDurationMs) || 0;
let shouldRunImmediately = !config.runEffect || effectDurationMs === 0;
if (shouldRunImmediately) {
let clonedPayload = Object.assign({}, instance.payload);
return runInstanceImmediately(instance, clonedPayload, props);
}
return runInstanceWithEffects(instance, config.runEffect, effectDurationMs, props);
}
function scheduleDelayedRun(instance, durationMs, props) {
let abortCallback = null;
let timeoutId = setTimeout(function theDelayedRunExecution() {
instance.pendingTimeout = null;
let clonedPayload = Object.assign({}, instance.payload);
abortCallback = runInstanceImmediately(instance, clonedPayload, props);
}, durationMs);
instance.pendingTimeout = { at: now(), id: timeoutId };
return function abortCleanup(reason) {
clearTimeout(timeoutId);
instance.pendingTimeout = null;
if (isFunction(abortCallback)) {
abortCallback(reason);
}
};
}
function runInstanceWithEffects(instance, effect, durationMs, props) {
switch (effect) {
case "delay":
case "debounce": {
if (instance.pendingTimeout) {
let rightNow = now();
let deadline = instance.pendingTimeout.at + durationMs;
if (rightNow < deadline) {
clearTimeout(instance.pendingTimeout.id);
}
}
return scheduleDelayedRun(instance, durationMs, props);
}
case "throttle": {
if (instance.pendingTimeout) {
let rightNow = now();
let deadline = instance.pendingTimeout.at + durationMs;
if (rightNow <= deadline) {
return function noop() {
// do nothing when throttled
};
}
break;
}
else {
return scheduleDelayedRun(instance, durationMs, props);
}
}
}
let clonedPayload = Object.assign({}, instance.payload);
return runInstanceImmediately(instance, clonedPayload, props);
}
function cleanInstancePendingStateBeforeImmediateRun(instance) {
if (instance.pendingUpdate) {
clearTimeout(instance.pendingUpdate.id);
instance.pendingUpdate = null;
}
instance.actions.abort();
instance.currentAbort = undefined;
}
function replaceStateBecauseNoProducerProvided(instance, props) {
let args = (props?.args ?? emptyArray);
// keep these for readability
// we can't really type these
// this is supported for backward compatibility
let newStateData = args[0];
let newStateStatus = args[1];
instance.actions.setState(newStateData, newStateStatus, props);
return noop;
}
function replaceStateAndBailoutRunFromCachedState(instance, cachedState) {
let actualState = instance.state;
let nextState = cachedState.state;
// this means that the current state reference isn't the same
if (actualState !== nextState) {
// this sets the new state and notifies subscriptions
// true for notifying
// todo: update latest run
replaceInstanceState(instance, nextState, true);
}
}
function runInstanceImmediately(instance, payload, props) {
// when there is no producer attached to the instance, skip everything
// and replace state immediately. This will skip cache too.
instance.promise = null;
if (!instance.fn) {
return replaceStateBecauseNoProducerProvided(instance, props);
}
let wasAltering = startAlteringState();
// the pendingUpdate has always a pending status, it is delayed because
// of the config.skipPendingDelayMs configuration option.
let hasPendingUpdate = instance.pendingUpdate !== null;
let isCurrentlyPending = instance.state.status === pending;
if (isCurrentlyPending || hasPendingUpdate) {
cleanInstancePendingStateBeforeImmediateRun(instance);
}
// this should not be into the previous if, because we need all the time
// to invoke the currentAbort so that cleanups of the previous run are invoked
if (isFunction(instance.currentAbort)) {
instance.currentAbort();
}
if (hasCacheEnabled(instance)) {
let cacheConfig = instance.config.cacheConfig;
let runHash = computeRunHash(payload, props, cacheConfig?.hash);
let cachedState = getCachedState(instance, runHash);
// only use a cached entry if not expired
if (cachedState && !didCachedStateExpire(cachedState)) {
replaceStateAndBailoutRunFromCachedState(instance, cachedState);
if (__DEV__)
devtools.emitRun(instance, true);
return;
}
// this means that the cache entry was expired, we need to removed it
// from the cache
if (cachedState) {
removeCachedStateAndSpreadOnLanes(instance, runHash);
}
}
let indicators = { index: 1, done: false, cleared: false, aborted: false };
let producerProps = createProps(instance, indicators, payload, props);
instance.latestRun = {
args: producerProps.args,
payload: producerProps.payload,
};
instance.currentAbort = producerProps.abort;
let runResult = run(instance.fn, producerProps, indicators, onSettled, instance.config.retryConfig, props // callbacks
);
if (runResult) {
// Promise<TData>
if (__DEV__)
devtools.emitRun(instance, false);
instance.promise = runResult;
let currentState = instance.state;
if (currentState.status === pending) {
currentState = currentState.prev;
}
let savedProps = cloneProducerProps(producerProps);
let pendingState = {
data: null,
timestamp: now(),
props: savedProps,
prev: currentState,
status: pending,
};
replaceInstanceState(instance, pendingState, true, props);
stopAlteringState(wasAltering);
return producerProps.abort;
}
if (__DEV__)
devtools.emitRun(instance, false);
stopAlteringState(wasAltering);
return producerProps.abort;
function onSettled(data, status, savedProps, callbacks) {
let state = freeze({
status,
data,
props: savedProps,
timestamp: now(),
});
replaceInstanceState(instance, state, true, callbacks);
}
}
export { runInstanceImmediately, runcInstance };
//# sourceMappingURL=StateRun.js.map