UNPKG

react-relay-network-layer

Version:

Network Layer for React Relay and Express (Batch Queries, AuthToken, Logging, Retry)

110 lines (93 loc) 3.03 kB
/* eslint-disable no-console */ const timeoutError = new Error('fetch timeout'); export default function retryMiddleware(options) { const opts = options || {}; const fetchTimeout = opts.fetchTimeout || 15000; const retryDelays = opts.retryDelays || [1000, 3000]; const statusCodes = opts.statusCodes || false; const logger = opts.logger || console.log.bind(console, '[RELAY-NETWORK]'); const allowMutations = opts.allowMutations || false; const forceRetry = opts.forceRetry || false; let retryAfterMs = () => false; if (retryDelays) { if (Array.isArray(retryDelays)) { retryAfterMs = attempt => { if (retryDelays.length >= attempt) { return retryDelays[attempt - 1]; } return false; }; } else if (isFunction(retryDelays)) { retryAfterMs = retryDelays; } } return next => req => { if (req.relayReqType === 'mutation' && !allowMutations) { return next(req); } let attempt = 0; const sendTimedRequest = (timeout, delay = 0) => { attempt++; return promiseTimeoutDelay(next(req), timeout, delay, forceRetry).then(res => { let statusError = false; if (statusCodes) { statusError = statusCodes.indexOf(res.status) !== -1; } else { statusError = res.status < 200 || res.status > 300; } if (statusError) { const retryDelayMS = retryAfterMs(attempt); if (retryDelayMS) { logger(`response status ${res.status}, retrying after ${retryDelayMS} ms`); return sendTimedRequest(timeout, retryDelayMS); } } return res; }).catch(err => { const retryDelayMS = retryAfterMs(attempt); if (retryDelayMS) { logger(`response timeout or network error, retrying after ${retryDelayMS} ms`); return sendTimedRequest(timeout, retryDelayMS); } return new Promise((resolve, reject) => reject(err)); }); }; return sendTimedRequest(fetchTimeout, 0); }; } function isFunction(value) { return !!(value && value.constructor && value.call && value.apply); } function promiseTimeoutDelay(promise, timeoutMS, delayMS = 0, forceRetryWhenDelay) { return new Promise((resolve, reject) => { const timeoutPromise = () => { const timeoutId = setTimeout(() => { reject(timeoutError); }, timeoutMS); promise.then(res => { clearTimeout(timeoutId); resolve(res); }, err => { clearTimeout(timeoutId); reject(err); }); }; if (delayMS > 0) { let delayInProgress = true; const delayId = setTimeout(() => { delayInProgress = false; timeoutPromise(); }, delayMS); if (forceRetryWhenDelay) { forceRetryWhenDelay(() => { if (delayInProgress) { clearTimeout(delayId); timeoutPromise(); } }, delayMS); } } else { timeoutPromise(); } }); }