Skip to content

Commit

Permalink
Refactor code that adds resolver fulfillment to selector calls (WordP…
Browse files Browse the repository at this point in the history
…ress#51009)

* mapSelector: always look at canonical mappedResolvers

* fulfillResolver: doesn't need to check resolver existence

* fulfillSelector: doesn't need to be async

* fulfillSelector: make it common for all mapped selectors

* fulfillResolver: inline into fulfillSelector

* fulfillSelector: add missing setTimeout param

* mapResolvers: map resolvers and resolving selectors separately
  • Loading branch information
jsnajdr authored and sethrubenstein committed Jul 13, 2023
1 parent d27d78e commit 15d5cc1
Showing 1 changed file with 75 additions and 100 deletions.
175 changes: 75 additions & 100 deletions packages/data/src/redux-store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ export default function createReduxStore( key, options ) {
lock( store, privateRegistrationFunctions );
const resolversCache = createResolversCache();

let resolvers;
const actions = mapActions(
{
...metadataActions,
Expand Down Expand Up @@ -236,15 +235,15 @@ export default function createReduxStore( key, options ) {
} )
);

let resolvers;
if ( options.resolvers ) {
const result = mapResolvers(
options.resolvers,
resolvers = mapResolvers( options.resolvers );
selectors = mapSelectorsWithResolvers(
selectors,
resolvers,
store,
resolversCache
);
resolvers = result.resolvers;
selectors = result.selectors;
}

const resolveSelectors = mapResolveSelectors( selectors, store );
Expand Down Expand Up @@ -504,20 +503,13 @@ function mapSuspendSelectors( selectors, store ) {
}

/**
* Returns resolvers with matched selectors for a given namespace.
* Resolvers are side effects invoked once per argument set of a given selector call,
* used in ensuring that the data needs for the selector are satisfied.
* Convert resolvers to a normalized form, an object with `fulfill` method and
* optional methods like `isFulfilled`.
*
* @param {Object} resolvers Resolvers to register.
* @param {Object} selectors The current selectors to be modified.
* @param {Object} store The redux store to which the resolvers should be mapped.
* @param {Object} resolversCache Resolvers Cache.
* @param {Object} resolvers Resolver to convert
*/
function mapResolvers( resolvers, selectors, store, resolversCache ) {
// The `resolver` can be either a function that does the resolution, or, in more advanced
// cases, an object with a `fullfill` method and other optional methods like `isFulfilled`.
// Here we normalize the `resolver` function to an object with `fulfill` method.
const mappedResolvers = mapValues( resolvers, ( resolver ) => {
function mapResolvers( resolvers ) {
return mapValues( resolvers, ( resolver ) => {
if ( resolver.fulfill ) {
return resolver;
}
Expand All @@ -527,99 +519,82 @@ function mapResolvers( resolvers, selectors, store, resolversCache ) {
fulfill: resolver, // Add the fulfill method.
};
} );
}

const mapSelector = ( selector, selectorName ) => {
const resolver = resolvers[ selectorName ];
if ( ! resolver ) {
selector.hasResolver = false;
return selector;
/**
* Returns resolvers with matched selectors for a given namespace.
* Resolvers are side effects invoked once per argument set of a given selector call,
* used in ensuring that the data needs for the selector are satisfied.
*
* @param {Object} selectors The current selectors to be modified.
* @param {Object} resolvers Resolvers to register.
* @param {Object} store The redux store to which the resolvers should be mapped.
* @param {Object} resolversCache Resolvers Cache.
*/
function mapSelectorsWithResolvers(
selectors,
resolvers,
store,
resolversCache
) {
function fulfillSelector( resolver, selectorName, args ) {
const state = store.getState();

if (
resolversCache.isRunning( selectorName, args ) ||
( typeof resolver.isFulfilled === 'function' &&
resolver.isFulfilled( state, ...args ) )
) {
return;
}

const selectorResolver = ( ...args ) => {
async function fulfillSelector() {
const state = store.getState();

if (
resolversCache.isRunning( selectorName, args ) ||
( typeof resolver.isFulfilled === 'function' &&
resolver.isFulfilled( state, ...args ) )
) {
return;
}
const { metadata } = store.__unstableOriginalGetState();

const { metadata } = store.__unstableOriginalGetState();

if (
metadataSelectors.hasStartedResolution(
metadata,
selectorName,
args
)
) {
return;
}
if (
metadataSelectors.hasStartedResolution(
metadata,
selectorName,
args
)
) {
return;
}

resolversCache.markAsRunning( selectorName, args );
resolversCache.markAsRunning( selectorName, args );

setTimeout( async () => {
resolversCache.clear( selectorName, args );
store.dispatch(
metadataActions.startResolution( selectorName, args )
);
try {
await fulfillResolver(
store,
mappedResolvers,
selectorName,
...args
);
store.dispatch(
metadataActions.finishResolution(
selectorName,
args
)
);
} catch ( error ) {
store.dispatch(
metadataActions.failResolution(
selectorName,
args,
error
)
);
}
} );
setTimeout( async () => {
resolversCache.clear( selectorName, args );
store.dispatch(
metadataActions.startResolution( selectorName, args )
);
try {
const action = resolver.fulfill( ...args );
if ( action ) {
await store.dispatch( action );
}
store.dispatch(
metadataActions.finishResolution( selectorName, args )
);
} catch ( error ) {
store.dispatch(
metadataActions.failResolution( selectorName, args, error )
);
}
}, 0 );
}

return mapValues( selectors, ( selector, selectorName ) => {
const resolver = resolvers[ selectorName ];
if ( ! resolver ) {
selector.hasResolver = false;
return selector;
}

fulfillSelector( ...args );
const selectorResolver = ( ...args ) => {
fulfillSelector( resolver, selectorName, args );
return selector( ...args );
};
selectorResolver.hasResolver = true;
return selectorResolver;
};

return {
resolvers: mappedResolvers,
selectors: mapValues( selectors, mapSelector ),
};
}

/**
* Calls a resolver given arguments
*
* @param {Object} store Store reference, for fulfilling via resolvers
* @param {Object} resolvers Store Resolvers
* @param {string} selectorName Selector name to fulfill.
* @param {Array} args Selector Arguments.
*/
async function fulfillResolver( store, resolvers, selectorName, ...args ) {
const resolver = resolvers[ selectorName ];
if ( ! resolver ) {
return;
}

const action = resolver.fulfill( ...args );
if ( action ) {
await store.dispatch( action );
}
} );
}

0 comments on commit 15d5cc1

Please sign in to comment.