UNPKG

async-states

Version:

Core of async-states

181 lines (178 loc) 7.28 kB
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