Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support batch requests in data layer #26024

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/api-fetch/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import batchMiddleware from './middlewares/batch';
import createNonceMiddleware from './middlewares/nonce';
import createRootURLMiddleware from './middlewares/root-url';
import createPreloadingMiddleware from './middlewares/preloading';
Expand Down Expand Up @@ -48,6 +49,7 @@ const middlewares = [
namespaceEndpointMiddleware,
httpV1Middleware,
fetchAllMiddleware,
batchMiddleware,
];

function registerMiddleware( middleware ) {
Expand Down Expand Up @@ -149,6 +151,7 @@ function apiFetch( options ) {
apiFetch.use = registerMiddleware;
apiFetch.setFetchHandler = setFetchHandler;

apiFetch.batchMiddleware = batchMiddleware;
apiFetch.createNonceMiddleware = createNonceMiddleware;
apiFetch.createPreloadingMiddleware = createPreloadingMiddleware;
apiFetch.createRootURLMiddleware = createRootURLMiddleware;
Expand Down
87 changes: 87 additions & 0 deletions packages/api-fetch/src/middlewares/batch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Internal dependencies
*/
import apiFetch from '../index';

const BATCH_TIME_WINDOW = 1000;
const MAX_BATCH_SIZE = 20;
const awaitingBatches = {};

/**
* Middleware handling request batching.
*
* @param {Object} options Fetch options.
* @param {Function} next [description]
*
* @return {*} The evaluated result of the remaining middleware chain.
*/
function batchRequestMiddleware( options, next ) {
if (
! [ 'POST', 'PUT', 'PATCH', 'DELETE' ].includes( options.method ) ||
! options.batchAs ||
! endpointSupportsBatch( options.path )
) {
return next( options );
}

const batchId = JSON.stringify( [ options.batchAs, options.method ] );
const requestIdx = addRequestToBatch( batchId, options );
const save = requestIdx + 1 >= MAX_BATCH_SIZE ? commit : commitEventually;
return save( batchId ).then(
( subResponses ) => subResponses[ requestIdx ]
);
}

function endpointSupportsBatch( path ) {
// This should be more sophisticated in reality:
return path.indexOf( '/v2/template-parts' ) !== -1;
}

function addRequestToBatch( batchId, options ) {
if ( ! awaitingBatches[ batchId ] ) {
awaitingBatches[ batchId ] = {
promise: null,
requests: [],
};
}

awaitingBatches[ batchId ].requests.push( options );
return awaitingBatches[ batchId ].requests.length - 1;
}

function commitEventually( batchId ) {
const batch = awaitingBatches[ batchId ];
if ( ! batch.promise ) {
batch.promise = new Promise( ( resolve ) => {
setTimeout( () => resolve( commit( batchId ) ), BATCH_TIME_WINDOW );
} );
}
return batch.promise;
}

function commit( batchId ) {
// Pop unit of work so it cannot be altered by outside code
const unitOfWork = awaitingBatches[ batchId ];
delete awaitingBatches[ batchId ];

// Clear eventual commit in case commit was called before commitEventually kicked in
clearTimeout( unitOfWork.promise );

// Maybe we could reuse raw options instead of mapping like that
const requests = unitOfWork.requests.map( ( options ) => ( {
path: options.path,
body: options.body,
headers: options.headers,
} ) );

return apiFetch( {
path: '/wp/__experimental/batch',
method: 'POST',
data: {
validation: 'require-all-validate',
requests,
},
} );
}

export default batchRequestMiddleware;
6 changes: 6 additions & 0 deletions packages/core-data/src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ export function* deleteEntityRecord( kind, name, recordId, query ) {
deletedRecord = yield apiFetch( {
path,
method: 'DELETE',
// Just a placeholder to demonstrate the idea, this should come from the caller:
batchAs: `${ kind }-${ name }`,
} );

yield removeItems( kind, name, recordId, true );
Expand Down Expand Up @@ -413,6 +415,8 @@ export function* saveEntityRecord(
path: `${ path }/autosaves`,
method: 'POST',
data,
// Just a placeholder to demonstrate the idea, this should come from the caller:
batchAs: `${ kind }-${ name }`,
} );
// An autosave may be processed by the server as a regular save
// when its update is requested by the author and the post had
Expand Down Expand Up @@ -505,6 +509,8 @@ export function* saveEntityRecord(
path,
method: recordId ? 'PUT' : 'POST',
data,
// Just a placeholder to demonstrate the idea, this should come from the caller:
batchAs: `${ kind }-${ name }`,
} );
yield receiveEntityRecords(
kind,
Expand Down