diff --git a/classes/response.js b/classes/response.js index a6c9341..04ed248 100644 --- a/classes/response.js +++ b/classes/response.js @@ -33,6 +33,10 @@ export class NFCResponse extends Response { } static fromCachedResponse(bodyStream, rawMetaData, ejectSelfFromCache) { + if (bodyStream.readableEnded) { + throw new Error('Cache returned a body stream that has already been read to end.'); + } + return new NFCResponse(bodyStream, rawMetaData, ejectSelfFromCache, true); } diff --git a/index.js b/index.js index f21f4fb..36020b7 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ import fetch from 'node-fetch'; import fs from 'fs'; import { URLSearchParams } from 'url'; import crypto from 'crypto'; +import locko from 'locko'; import { NFCResponse } from './classes/response.js'; import { MemoryCache } from './classes/caching/memory_cache.js'; @@ -73,15 +74,11 @@ function getCacheKey(requestArguments) { async function getResponse(cache, requestArguments) { const cacheKey = getCacheKey(requestArguments); - const cachedValue = await cache.get(cacheKey); + let cachedValue = await cache.get(cacheKey); const ejectSelfFromCache = () => cache.remove(cacheKey); if (cachedValue) { - if (cachedValue.bodyStream.readableEnded) { - throw new Error('Cache returned a body stream that has already been read to end.'); - } - return NFCResponse.fromCachedResponse( cachedValue.bodyStream, cachedValue.metaData, @@ -89,19 +86,33 @@ async function getResponse(cache, requestArguments) { ); } - const fetchResponse = await fetch(...requestArguments); - const nfcResponse = NFCResponse.fromNodeFetchResponse(fetchResponse, ejectSelfFromCache); - const contentLength = Number.parseInt(nfcResponse.headers.get('content-length'), 10) || 0; - const nfcResponseSerialized = nfcResponse.serialize(); + await locko.lock(cacheKey); + try { + cachedValue = await cache.get(cacheKey); + if (cachedValue) { + return NFCResponse.fromCachedResponse( + cachedValue.bodyStream, + cachedValue.metaData, + ejectSelfFromCache, + ); + } - await cache.set( - cacheKey, - nfcResponseSerialized.bodyStream, - nfcResponseSerialized.metaData, - contentLength, - ); + const fetchResponse = await fetch(...requestArguments); + const nfcResponse = NFCResponse.fromNodeFetchResponse(fetchResponse, ejectSelfFromCache); + const contentLength = Number.parseInt(nfcResponse.headers.get('content-length'), 10) || 0; + const nfcResponseSerialized = nfcResponse.serialize(); - return nfcResponse; + await cache.set( + cacheKey, + nfcResponseSerialized.bodyStream, + nfcResponseSerialized.metaData, + contentLength, + ); + + return nfcResponse; + } finally { + locko.unlock(cacheKey); + } } function createFetchWithCache(cache) { diff --git a/package-lock.json b/package-lock.json index bdc246b..685ff4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2323,6 +2323,11 @@ "path-exists": "^3.0.0" } }, + "locko": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/locko/-/locko-0.0.3.tgz", + "integrity": "sha512-ekhPWcejAum9WHN2ClkFA8RAUTDyYDlRRb4dSq1wCEPhIS6IMsdSKoWHl1qineCrlMEMbeD1/o2uautG4QEc7w==" + }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", diff --git a/package.json b/package.json index 75ccc91..e679712 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "cacache": "^15.2.0", + "locko": "0.0.3", "node-fetch": "2.6.1" }, "husky": { diff --git a/test/tests.js b/test/tests.js index e39bd36..dc5d16a 100644 --- a/test/tests.js +++ b/test/tests.js @@ -404,6 +404,16 @@ describe('Data tests', function() { assert(err.message.includes('Unsupported body type')); } }); + + it('Uses cache even if you make multiple requests at the same time', async function() { + const [res1, res2] = await Promise.all([ + cachedFetch('http://httpbin.org/status/200'), + cachedFetch('http://httpbin.org/status/200'), + ]); + + // One should be false, the other should be true + assert(res1.fromCache !== res2.fromCache); + }); }).timeout(10000); describe('Memory cache tests', function() { @@ -466,3 +476,4 @@ describe('File system cache tests', function() { assert.strictEqual(res.fromCache, true); }); }); +console.log(process.cwd()) \ No newline at end of file