fetch-dedupe
Advanced tools
Comparing version 3.0.0 to 4.0.0-beta.1
@@ -7,6 +7,4 @@ (function (global, factory) { | ||
// This is a cache of in-flight requests. Each request key maps to an | ||
// array of Promises. When the request resolves, each promise in the | ||
// array is pushed to. | ||
var requests = {}; | ||
var requestCache = {}; | ||
var responseCache = {}; | ||
@@ -30,9 +28,17 @@ function getRequestKey() { | ||
function isRequestInFlight(requestKey) { | ||
return Boolean(requests[requestKey]); | ||
return Boolean(requestCache[requestKey]); | ||
} | ||
function isResponseCached(requestKey) { | ||
return Boolean(responseCache[requestKey]); | ||
} | ||
function clearRequestCache() { | ||
requests = {}; | ||
requestCache = {}; | ||
} | ||
function clearResponseCache() { | ||
responseCache = {}; | ||
} | ||
// This loops through all of the handlers for the request and either | ||
@@ -45,7 +51,9 @@ // resolves or rejects them. | ||
var handlers = requests[requestKey] || []; | ||
var handlers = requestCache[requestKey] || []; | ||
handlers.forEach(function (handler) { | ||
if (res) { | ||
handler.resolve(res); | ||
var resToSend = new Response(res.body, res); | ||
resToSend.data = res.data; | ||
handler.resolve(resToSend); | ||
} else { | ||
@@ -58,5 +66,20 @@ handler.reject(err); | ||
// clear the handlers for the next request. | ||
requests[requestKey] = null; | ||
requestCache[requestKey] = null; | ||
} | ||
function CacheMissError() { | ||
var err = Error.apply(this, arguments); | ||
err.name = this.name = 'CacheMissError'; | ||
this.message = err.message; | ||
this.stack = err.stack; | ||
} | ||
CacheMissError.prototype = Object.create(Error.prototype, { | ||
constructor: { | ||
value: CacheMissError, | ||
writable: true, | ||
configurable: true | ||
} | ||
}); | ||
function fetchDedupe(input) { | ||
@@ -71,3 +94,3 @@ var init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
initToUse = init; | ||
} else if (init.responseType) { | ||
} else if (init && (init.responseType || init.dedupe || init.cachePolicy || init.requestKey)) { | ||
opts = init; | ||
@@ -85,6 +108,18 @@ initToUse = {}; | ||
_opts$dedupe = _opts.dedupe, | ||
dedupe = _opts$dedupe === undefined ? true : _opts$dedupe; | ||
dedupe = _opts$dedupe === undefined ? true : _opts$dedupe, | ||
cachePolicy = _opts.cachePolicy; | ||
var method = initToUse.method || input.method || ''; | ||
var upperCaseMethod = method.toUpperCase(); | ||
var appliedCachePolicy = void 0; | ||
if (cachePolicy) { | ||
appliedCachePolicy = cachePolicy; | ||
} else { | ||
var isReadRequest = upperCaseMethod === 'GET' || upperCaseMethod === 'OPTIONS' || upperCaseMethod === 'HEAD' || upperCaseMethod === ''; | ||
appliedCachePolicy = isReadRequest ? 'cache-first' : 'network-only'; | ||
} | ||
// Build the default request key if one is not passed | ||
var requestKeyToUse = requestKey || getRequestKey({ | ||
@@ -99,9 +134,24 @@ // If `input` is a request, then we use that URL | ||
var cachedResponse = void 0; | ||
if (appliedCachePolicy !== 'network-only') { | ||
cachedResponse = responseCache[requestKeyToUse]; | ||
if (cachedResponse) { | ||
var resp = new Response(cachedResponse.body, cachedResponse); | ||
resp.data = cachedResponse.data; | ||
resp.fromCache = true; | ||
return Promise.resolve(resp); | ||
} else if (cachePolicy === 'cache-only') { | ||
var cacheError = new CacheMissError('Response for fetch request not found in cache.'); | ||
return Promise.reject(cacheError); | ||
} | ||
} | ||
var proxyReq = void 0; | ||
if (dedupe) { | ||
if (!requests[requestKeyToUse]) { | ||
requests[requestKeyToUse] = []; | ||
if (!requestCache[requestKeyToUse]) { | ||
requestCache[requestKeyToUse] = []; | ||
} | ||
var handlers = requests[requestKeyToUse]; | ||
var handlers = requestCache[requestKeyToUse]; | ||
var requestInFlight = Boolean(handlers.length); | ||
@@ -137,2 +187,3 @@ var requestHandler = {}; | ||
res.data = data; | ||
responseCache[requestKeyToUse] = res; | ||
@@ -170,3 +221,5 @@ if (dedupe) { | ||
exports.isRequestInFlight = isRequestInFlight; | ||
exports.isResponseCached = isResponseCached; | ||
exports.clearRequestCache = clearRequestCache; | ||
exports.clearResponseCache = clearResponseCache; | ||
exports.fetchDedupe = fetchDedupe; | ||
@@ -173,0 +226,0 @@ |
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(e.FetchDedupe={})}(this,function(e){"use strict";var l={};function y(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{},t=e.url,n=e.method,r=e.responseType,o=void 0===r?"":r,u=e.body,i=void 0===u?"":u;return[void 0===t?"":t,(void 0===n?"":n).toUpperCase(),o,i].join("||")}function m(e){var t=e.requestKey,n=e.res,r=e.err;(l[t]||[]).forEach(function(e){n?e.resolve(n):e.reject(r)}),l[t]=null}e.getRequestKey=y,e.isRequestInFlight=function(e){return!!l[e]},e.clearRequestCache=function(){l={}},e.fetchDedupe=function(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{},n=arguments[2],r=void 0,o=void 0;n?(r=n,o=t):t.responseType?(r=t,o={}):(r={},o=t);var u=r.responseType,i=void 0===u?"":u,d=r.dedupe,s=void 0===d||d,f=r.requestKey||y({url:e.url||e,method:o.method||e.method||"",body:o.body||e.body||""}),c=void 0;if(s){l[f]||(l[f]=[]);var v=l[f],a=!!v.length,p={};if(c=new Promise(function(e,t){p.resolve=e,p.reject=t}),v.push(p),a)return c}var h=fetch(e,o).then(function(t){var e=void 0;return e=i instanceof Function?i(t):i||(204===t.status?"text":"json"),t[e]().then(function(e){if(t.data=e,!s)return t;m({requestKey:f,res:t})},function(){if(t.data=null,!s)return t;m({requestKey:f,res:t})})},function(e){if(!s)return Promise.reject(e);m({requestKey:f,err:e})});return s?c:h},Object.defineProperty(e,"__esModule",{value:!0})}); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(e.FetchDedupe={})}(this,function(e){"use strict";var P={},R={};function w(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{},t=e.url,r=e.method,o=e.responseType,n=void 0===o?"":o,i=e.body,s=void 0===i?"":i;return[void 0===t?"":t,(void 0===r?"":r).toUpperCase(),n,s].join("||")}function C(e){var t=e.requestKey,r=e.res,o=e.err;(P[t]||[]).forEach(function(e){if(r){var t=new Response(r.body,r);t.data=r.data,e.resolve(t)}else e.reject(o)}),P[t]=null}function K(){var e=Error.apply(this,arguments);e.name=this.name="CacheMissError",this.message=e.message,this.stack=e.stack}K.prototype=Object.create(Error.prototype,{constructor:{value:K,writable:!0,configurable:!0}}),e.getRequestKey=w,e.isRequestInFlight=function(e){return!!P[e]},e.isResponseCached=function(e){return!!R[e]},e.clearRequestCache=function(){P={}},e.clearResponseCache=function(){R={}},e.fetchDedupe=function(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:{},r=arguments[2],o=void 0,n=void 0;r?(o=r,n=t):t&&(t.responseType||t.dedupe||t.cachePolicy||t.requestKey)?(o=t,n={}):(o={},n=t);var i=o.requestKey,s=o.responseType,u=void 0===s?"":s,c=o.dedupe,a=void 0===c||c,d=o.cachePolicy,f=(n.method||e.method||"").toUpperCase(),h=void 0;h=d||("GET"===f||"OPTIONS"===f||"HEAD"===f||""===f?"cache-first":"network-only");var p=i||w({url:e.url||e,method:n.method||e.method||"",body:n.body||e.body||""}),v=void 0;if("network-only"!==h){if(v=R[p]){var l=new Response(v.body,v);return l.data=v.data,l.fromCache=!0,Promise.resolve(l)}if("cache-only"===d){var y=new K("Response for fetch request not found in cache.");return Promise.reject(y)}}var m=void 0;if(a){P[p]||(P[p]=[]);var b=P[p],q=!!b.length,j={};if(m=new Promise(function(e,t){j.resolve=e,j.reject=t}),b.push(j),q)return m}var g=fetch(e,n).then(function(t){var e=void 0;return e=u instanceof Function?u(t):u||(204===t.status?"text":"json"),t[e]().then(function(e){if(t.data=e,R[p]=t,!a)return t;C({requestKey:p,res:t})},function(){if(t.data=null,!a)return t;C({requestKey:p,res:t})})},function(e){if(!a)return Promise.reject(e);C({requestKey:p,err:e})});return a?m:g},Object.defineProperty(e,"__esModule",{value:!0})}); |
@@ -1,5 +0,3 @@ | ||
// This is a cache of in-flight requests. Each request key maps to an | ||
// array of Promises. When the request resolves, each promise in the | ||
// array is pushed to. | ||
var requests = {}; | ||
var requestCache = {}; | ||
var responseCache = {}; | ||
@@ -23,9 +21,17 @@ export function getRequestKey() { | ||
export function isRequestInFlight(requestKey) { | ||
return Boolean(requests[requestKey]); | ||
return Boolean(requestCache[requestKey]); | ||
} | ||
export function isResponseCached(requestKey) { | ||
return Boolean(responseCache[requestKey]); | ||
} | ||
export function clearRequestCache() { | ||
requests = {}; | ||
requestCache = {}; | ||
} | ||
export function clearResponseCache() { | ||
responseCache = {}; | ||
} | ||
// This loops through all of the handlers for the request and either | ||
@@ -38,7 +44,9 @@ // resolves or rejects them. | ||
var handlers = requests[requestKey] || []; | ||
var handlers = requestCache[requestKey] || []; | ||
handlers.forEach(function (handler) { | ||
if (res) { | ||
handler.resolve(res); | ||
var resToSend = new Response(res.body, res); | ||
resToSend.data = res.data; | ||
handler.resolve(resToSend); | ||
} else { | ||
@@ -51,5 +59,20 @@ handler.reject(err); | ||
// clear the handlers for the next request. | ||
requests[requestKey] = null; | ||
requestCache[requestKey] = null; | ||
} | ||
function CacheMissError() { | ||
var err = Error.apply(this, arguments); | ||
err.name = this.name = 'CacheMissError'; | ||
this.message = err.message; | ||
this.stack = err.stack; | ||
} | ||
CacheMissError.prototype = Object.create(Error.prototype, { | ||
constructor: { | ||
value: CacheMissError, | ||
writable: true, | ||
configurable: true | ||
} | ||
}); | ||
export function fetchDedupe(input) { | ||
@@ -64,3 +87,3 @@ var init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
initToUse = init; | ||
} else if (init.responseType) { | ||
} else if (init && (init.responseType || init.dedupe || init.cachePolicy || init.requestKey)) { | ||
opts = init; | ||
@@ -78,6 +101,18 @@ initToUse = {}; | ||
_opts$dedupe = _opts.dedupe, | ||
dedupe = _opts$dedupe === undefined ? true : _opts$dedupe; | ||
dedupe = _opts$dedupe === undefined ? true : _opts$dedupe, | ||
cachePolicy = _opts.cachePolicy; | ||
var method = initToUse.method || input.method || ''; | ||
var upperCaseMethod = method.toUpperCase(); | ||
var appliedCachePolicy = void 0; | ||
if (cachePolicy) { | ||
appliedCachePolicy = cachePolicy; | ||
} else { | ||
var isReadRequest = upperCaseMethod === 'GET' || upperCaseMethod === 'OPTIONS' || upperCaseMethod === 'HEAD' || upperCaseMethod === ''; | ||
appliedCachePolicy = isReadRequest ? 'cache-first' : 'network-only'; | ||
} | ||
// Build the default request key if one is not passed | ||
var requestKeyToUse = requestKey || getRequestKey({ | ||
@@ -92,9 +127,24 @@ // If `input` is a request, then we use that URL | ||
var cachedResponse = void 0; | ||
if (appliedCachePolicy !== 'network-only') { | ||
cachedResponse = responseCache[requestKeyToUse]; | ||
if (cachedResponse) { | ||
var resp = new Response(cachedResponse.body, cachedResponse); | ||
resp.data = cachedResponse.data; | ||
resp.fromCache = true; | ||
return Promise.resolve(resp); | ||
} else if (cachePolicy === 'cache-only') { | ||
var cacheError = new CacheMissError('Response for fetch request not found in cache.'); | ||
return Promise.reject(cacheError); | ||
} | ||
} | ||
var proxyReq = void 0; | ||
if (dedupe) { | ||
if (!requests[requestKeyToUse]) { | ||
requests[requestKeyToUse] = []; | ||
if (!requestCache[requestKeyToUse]) { | ||
requestCache[requestKeyToUse] = []; | ||
} | ||
var handlers = requests[requestKeyToUse]; | ||
var handlers = requestCache[requestKeyToUse]; | ||
var requestInFlight = Boolean(handlers.length); | ||
@@ -130,2 +180,3 @@ var requestHandler = {}; | ||
res.data = data; | ||
responseCache[requestKeyToUse] = res; | ||
@@ -132,0 +183,0 @@ if (dedupe) { |
@@ -6,8 +6,8 @@ 'use strict'; | ||
exports.isRequestInFlight = isRequestInFlight; | ||
exports.isResponseCached = isResponseCached; | ||
exports.clearRequestCache = clearRequestCache; | ||
exports.clearResponseCache = clearResponseCache; | ||
exports.fetchDedupe = fetchDedupe; | ||
// This is a cache of in-flight requests. Each request key maps to an | ||
// array of Promises. When the request resolves, each promise in the | ||
// array is pushed to. | ||
let requests = {}; | ||
let requestCache = {}; | ||
let responseCache = {}; | ||
@@ -26,17 +26,27 @@ function getRequestKey({ | ||
function isRequestInFlight(requestKey) { | ||
return Boolean(requests[requestKey]); | ||
return Boolean(requestCache[requestKey]); | ||
} | ||
function isResponseCached(requestKey) { | ||
return Boolean(responseCache[requestKey]); | ||
} | ||
function clearRequestCache() { | ||
requests = {}; | ||
requestCache = {}; | ||
} | ||
function clearResponseCache() { | ||
responseCache = {}; | ||
} | ||
// This loops through all of the handlers for the request and either | ||
// resolves or rejects them. | ||
function resolveRequest({ requestKey, res, err }) { | ||
const handlers = requests[requestKey] || []; | ||
const handlers = requestCache[requestKey] || []; | ||
handlers.forEach(handler => { | ||
if (res) { | ||
handler.resolve(res); | ||
const resToSend = new Response(res.body, res); | ||
resToSend.data = res.data; | ||
handler.resolve(resToSend); | ||
} else { | ||
@@ -49,5 +59,20 @@ handler.reject(err); | ||
// clear the handlers for the next request. | ||
requests[requestKey] = null; | ||
requestCache[requestKey] = null; | ||
} | ||
function CacheMissError() { | ||
var err = Error.apply(this, arguments); | ||
err.name = this.name = 'CacheMissError'; | ||
this.message = err.message; | ||
this.stack = err.stack; | ||
} | ||
CacheMissError.prototype = Object.create(Error.prototype, { | ||
constructor: { | ||
value: CacheMissError, | ||
writable: true, | ||
configurable: true | ||
} | ||
}); | ||
function fetchDedupe(input, init = {}, dedupeOptions) { | ||
@@ -58,3 +83,3 @@ let opts, initToUse; | ||
initToUse = init; | ||
} else if (init.responseType) { | ||
} else if (init && (init.responseType || init.dedupe || init.cachePolicy || init.requestKey)) { | ||
opts = init; | ||
@@ -67,4 +92,15 @@ initToUse = {}; | ||
const { requestKey, responseType = '', dedupe = true } = opts; | ||
const { requestKey, responseType = '', dedupe = true, cachePolicy } = opts; | ||
const method = initToUse.method || input.method || ''; | ||
const upperCaseMethod = method.toUpperCase(); | ||
let appliedCachePolicy; | ||
if (cachePolicy) { | ||
appliedCachePolicy = cachePolicy; | ||
} else { | ||
const isReadRequest = upperCaseMethod === 'GET' || upperCaseMethod === 'OPTIONS' || upperCaseMethod === 'HEAD' || upperCaseMethod === ''; | ||
appliedCachePolicy = isReadRequest ? 'cache-first' : 'network-only'; | ||
} | ||
// Build the default request key if one is not passed | ||
@@ -80,9 +116,24 @@ let requestKeyToUse = requestKey || getRequestKey({ | ||
let cachedResponse; | ||
if (appliedCachePolicy !== 'network-only') { | ||
cachedResponse = responseCache[requestKeyToUse]; | ||
if (cachedResponse) { | ||
var resp = new Response(cachedResponse.body, cachedResponse); | ||
resp.data = cachedResponse.data; | ||
resp.fromCache = true; | ||
return Promise.resolve(resp); | ||
} else if (cachePolicy === 'cache-only') { | ||
const cacheError = new CacheMissError(`Response for fetch request not found in cache.`); | ||
return Promise.reject(cacheError); | ||
} | ||
} | ||
let proxyReq; | ||
if (dedupe) { | ||
if (!requests[requestKeyToUse]) { | ||
requests[requestKeyToUse] = []; | ||
if (!requestCache[requestKeyToUse]) { | ||
requestCache[requestKeyToUse] = []; | ||
} | ||
const handlers = requests[requestKeyToUse]; | ||
const handlers = requestCache[requestKeyToUse]; | ||
const requestInFlight = Boolean(handlers.length); | ||
@@ -118,2 +169,3 @@ const requestHandler = {}; | ||
res.data = data; | ||
responseCache[requestKeyToUse] = res; | ||
@@ -120,0 +172,0 @@ if (dedupe) { |
{ | ||
"name": "fetch-dedupe", | ||
"version": "3.0.0", | ||
"version": "4.0.0-beta.1", | ||
"description": "A thin wrapper around fetch that prevents duplicate requests.", | ||
@@ -20,3 +20,3 @@ "main": "lib/index.js", | ||
"type": "git", | ||
"url": "https://github.com/jmeas/fetch-dedupe.git" | ||
"url": "https://github.com/jamesplease/fetch-dedupe.git" | ||
}, | ||
@@ -44,3 +44,3 @@ "keywords": [ | ||
"bugs": { | ||
"url": "https://github.com/jmeas/fetch-dedupe/issues" | ||
"url": "https://github.com/jamesplease/fetch-dedupe/issues" | ||
}, | ||
@@ -47,0 +47,0 @@ "files": [ |
@@ -114,2 +114,5 @@ # Fetch Dedupe | ||
* `cachePolicy` *(String)*: Determines interactions with the cache. Valid options are `"cache-first"`, `"cache-only"`, | ||
and `"network-only"`. For more, refer to the section on Caching. | ||
Given the two possible value types of `input`, optional second argument, there are a way few ways that you can | ||
@@ -208,2 +211,11 @@ call `fetchDedupe`. Let's run through valid calls to `fetchDedupe`: | ||
##### `isResponseCached( requestKey )` | ||
Pass in a `requestKey` to see if there is a cache entry for the request. This can be used | ||
to determine if a call to `fetchDedupe` will hit the cache or not. | ||
> Now: We **strongly** recommend that you manually pass in `requestKey` to `fetchDedupe` | ||
if you intend to use this method. In other words, _do not_ rely on being able to | ||
reliably reproduce the request key that is created when a `requestKey` is not passed in. | ||
##### `clearRequestCache()` | ||
@@ -215,2 +227,37 @@ | ||
##### `clearResponseCache()` | ||
Wipe the cache of responses. | ||
> Warning: this is **not** safe to use in application code. It is mostly useful for testing. | ||
### Guides | ||
##### Caching | ||
The way the cache works is like this: any time a response from the server is received, it will be | ||
cached using the request's request key. Subsequent requests are matched with existing cached server | ||
responses using their request key. | ||
Interactions with the cache can be controlled with the `cachePolicy` option. There are three possible | ||
values: | ||
**`cache-first`** | ||
This is the default behavior. | ||
Requests will first look at the cache to see if a response for the same request key exists. If a response is | ||
found, then it will be returned, and no network request will be made. | ||
If no response exists in the cache, then a network request will be made. | ||
**`network-only`** | ||
The cache is ignored, and a network request is always made. | ||
**`cache-only`** | ||
If a response exists in the cache, then it will be returned. If no response | ||
exists in the cache, then an error will be passed into the render prop function. | ||
### FAQ & Troubleshooting | ||
@@ -247,2 +294,19 @@ | ||
##### Is the data duplicated? | ||
Although you receive a new `Response` object with every call to `fetch-dedupe`, the body will be read, | ||
so the response's body stream will be empty. In addition, the `data` property between every | ||
`response` is shared. Accordingly, the data returned by the server is never duplicated. | ||
This is an optimization that allows `fetch-dedupe` to be used in applications that fetch | ||
large payloads. | ||
##### `res.bodyUsed` is `false` when the body has already been used | ||
As of Feb 2018, there is a bug in several browsers and `node-fetch`, where the value of `bodyUsed` | ||
will be `false` when it should, in fact, be `true`. | ||
As a workaround, when using `fetch-dedupe`, the body will always be used by the time you receive | ||
the Response. | ||
### Implementors | ||
@@ -249,0 +313,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
34908
534
319
1
2