diff --git a/README.md b/README.md index f4d4169..62d3428 100755 --- a/README.md +++ b/README.md @@ -57,3 +57,14 @@ Optionally you can assign the query as a third property rather than placing it d ``` If an error occurs as a result of one the requests to an endpoint it will be included in the response in the same location in the array as the request causing the issue. The error object will include an error property that you can interrogate. At this time the response is a 200 even when a request in the batch returns a different code. + +By default, requests in the `"requests"` array will be run concurrently, with the exception of pipelined requests which will be run sequentially. +To force all batched requests to run sequentially regardless of pipelining, pass in the `forceSequential: true` flag: + +```json +{ "forceSequential": true, + "requests": [ + {"method": "get", "path": "/users/1"}, + {"method": "get", "path": "/users/2"} +] } +``` diff --git a/lib/batch.js b/lib/batch.js index 445f662..315accd 100644 --- a/lib/batch.js +++ b/lib/batch.js @@ -28,6 +28,7 @@ module.exports.config = function (settings) { results: [], resultsMap: [] }; + const { forceSequential } = request.payload; let errorMessage = null; @@ -71,7 +72,7 @@ module.exports.config = function (settings) { } try { - await internals.process(request, requests, payloads, resultsData); + await internals.process(request, requests, payloads, resultsData, forceSequential); } catch (err) { // console.log("ERROR ", err); @@ -89,7 +90,8 @@ module.exports.config = function (settings) { path: Joi.string().required(), query: [Joi.object().unknown().allow(null),Joi.string()], payload: [Joi.object().unknown().allow(null),Joi.string()] - }).label('BatchRequest')).min(1).required() + }).label('BatchRequest')).min(1).required(), + forceSequential: Joi.bool().label('ForceSequential').optional().default(false) }).required().label('BatchRequestPayload') }, auth: settings.auth, @@ -97,7 +99,7 @@ module.exports.config = function (settings) { }; }; -internals.process = async function (request, requests, payloads, resultsData) { +internals.process = async function (request, requests, payloads, resultsData, forceSequential) { const fnsParallel = []; const fnsSerial = []; @@ -105,7 +107,7 @@ internals.process = async function (request, requests, payloads, resultsData) { requests.forEach((requestParts, idx) => { const payloadParts = payloads[idx]; - if (internals.hasRefPart(requestParts) || payloadParts.length) { + if (forceSequential || internals.hasRefPart(requestParts) || payloadParts.length) { return fnsSerial.push( async () => await internals.batch(request, resultsData, idx, requestParts, payloadParts) ); diff --git a/test/batch.js b/test/batch.js index 591862c..1247a28 100644 --- a/test/batch.js +++ b/test/batch.js @@ -169,6 +169,17 @@ describe('Batch', () => { expect(res[1].name).to.equal('Active Item'); }); + it('handles sequential requests with forceSequential', async () => { + + const res = await Internals.makeRequest(server, '{ "forceSequential": true, "requests": [{"method": "get", "path": "/sequential"}, {"method": "get", "path": "/sequential"}, {"method": "get", "path": "/sequential"}, {"method": "get", "path": "/sequential"}] }'); + + expect(res.length).to.equal(4); + expect(res[0]).to.equal(1); + expect(res[1]).to.equal(2); + expect(res[2]).to.equal(3); + expect(res[3]).to.equal(4); + }); + it('supports piping a response into the next request', async () => { const res = await Internals.makeRequest(server, '{ "requests": [ {"method": "get", "path": "/item"}, {"method": "get", "path": "/item/$0.id"}] }'); diff --git a/test/internals.js b/test/internals.js index 7ef70a6..3c03cc7 100644 --- a/test/internals.js +++ b/test/internals.js @@ -3,6 +3,14 @@ const Hapi = require('hapi'); const Bassmaster = require('../'); +const awaitDelay = function (ms) { + + return new Promise((resolve) => { + + return setTimeout(resolve, ms); + }); +}; + const profileHandler = function (request, h) { const id = request.query.id || 'fa0dbda9b1b'; @@ -171,6 +179,18 @@ const echoHandler = function (request, h) { return request.payload; }; +const sequentialHandler = async function (request, h) { + + if (!sequentialHandler.callCount) { + sequentialHandler.callCount = 0; + } + + ++sequentialHandler.callCount; + await awaitDelay(50 - (10 * sequentialHandler.callCount)); + + return sequentialHandler.callCount; +}; + const returnInputtedIntegerHandler = function (request, h) { return request.payload.id; @@ -221,6 +241,7 @@ module.exports.setupServer = async function () { } }, { method: 'GET', path: '/redirect', handler: redirectHandler }, + { method: 'GET', path: '/sequential', handler: sequentialHandler } { method: 'POST', path: '/returnInputtedInteger', handler: returnInputtedIntegerHandler }, { method: 'GET', path: '/getFalse', handler: getFalseHandler }, { method: 'POST', path: '/returnInputtedBoolean', handler: returnInputtedBooleanHandler }