From 8e6092c01ffd650379c9558f476dc0b6a385f257 Mon Sep 17 00:00:00 2001 From: Jaunius Date: Tue, 7 Feb 2017 15:50:01 +0100 Subject: [PATCH 1/6] Add support for timeout --- Readme.md | 1 + src/index.js | 19 +++++++++++++++++-- test/index.js | 26 +++++++++++++++++++++++--- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/Readme.md b/Readme.md index a1e0dce..4530d05 100644 --- a/Readme.md +++ b/Readme.md @@ -41,6 +41,7 @@ Where `url` and `params` are what you would pass as the first and second argumen * `statusText` - The text version of the status (e.g. 'OK') * `headers` - A [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers) * `value` - The deserialized value of the response. This may be an object or string, depending on the type of response (json or text). + * `timeout` - The numerical value (in ms) which is used for rejecting request promise if it takes longer time to complete than the value of this property ## Examples diff --git a/src/index.js b/src/index.js index 68d5c07..5cc6691 100644 --- a/src/index.js +++ b/src/index.js @@ -16,14 +16,29 @@ const FETCH = 'EFFECT_FETCH' */ function fetchMiddleware ({dispatch, getState}) { - return next => action => + return next => action => action.type === FETCH - ? g().fetch(action.payload.url, action.payload.params) + ? getRequestPromise(action.payload) .then(checkStatus) .then(createResponse, createErrorResponse) : next(action) } +function getRequestPromise ({ params, url }) { + const fetchPromise = g().fetch(url, params) + + if (params && typeof params.timeout === 'number') { + const rejectOnTimeout = new Promise((_, reject) => { + const error = new Error(`Request to ${url} timed out`) + setTimeout(() => reject(error), params.timeout) + }) + + return Promise.race([fetchPromise, rejectOnTimeout]) + } + + return fetchPromise +} + /** * g - Return the global object (in the browser or node) */ diff --git a/test/index.js b/test/index.js index bec69db..aa53c59 100644 --- a/test/index.js +++ b/test/index.js @@ -3,8 +3,23 @@ */ import test from 'tape' +import fetchMock from 'fetch-mock' import fetchMw, {fetch} from '../src' +const urlTimeout = 'http://localhost/timeout' + +const urlSuccess = 'http://localhost/200' + +const failureUrl = 'http://localhost/404' + +fetchMock.get(urlTimeout, new Promise(res => setTimeout(res, 2000)).then(() => 200)); + +fetchMock.get(urlSuccess, { + headers: { 'Content-Type': ['text/html'] }, +}); + +fetchMock.get(failureUrl, { status: 404 }); + /** * Setup */ @@ -21,8 +36,8 @@ const run = fetchMw(api)(() => {}) */ test('should work', t => { - run(fetch('https://www.google.com')).then(({url, headers, value, status, statusText}) => { - t.equal(url, 'https://www.google.com') + run(fetch(urlSuccess)).then(({url, headers, value, status, statusText}) => { + t.equal(url, urlSuccess) t.equal(status, 200) t.equal(statusText, 'OK') t.ok(headers.get('content-type').indexOf('text/html') !== -1) @@ -30,7 +45,12 @@ test('should work', t => { }) }) +test('should reject on timeout', t => { + t.plan(1) + run(fetch(urlTimeout, { timeout: 1000 })).then(() => t.fail(), (res) => t.pass()) +}) + test('should reject on invalid response', t => { t.plan(1) - run(fetch('https://www.google.com/notAValidUrl')).then(() => t.fail(), (res) => t.pass()) + run(fetch(failureUrl)).then(() => t.fail(), (res) => t.pass()) }) From f34de188ce843b8d9fbca5274f737f7f265c5556 Mon Sep 17 00:00:00 2001 From: Jaunius Date: Sat, 11 Mar 2017 17:38:33 +0100 Subject: [PATCH 2/6] Add fetch-mock --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 855b089..fd18385 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "babel-preset-es2015": "^6.3.13", "babel-preset-stage-1": "^6.5.0", "babel-tape-runner": "^2.0.0", + "fetch-mock": "5.9.4", "lodash": "^4.8.1", "tap-spec": "^4.1.1", "tape": "^4.2.0" From 6ef685542cfc823fcfe19845450ae9f595cdf268 Mon Sep 17 00:00:00 2001 From: Jaunius Date: Sat, 11 Mar 2017 17:39:55 +0100 Subject: [PATCH 3/6] Use action.meta for timeout --- src/index.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/index.js b/src/index.js index 5cc6691..9a5d37e 100644 --- a/src/index.js +++ b/src/index.js @@ -18,19 +18,19 @@ const FETCH = 'EFFECT_FETCH' function fetchMiddleware ({dispatch, getState}) { return next => action => action.type === FETCH - ? getRequestPromise(action.payload) + ? getRequestPromise(action.payload, action.meta) .then(checkStatus) .then(createResponse, createErrorResponse) : next(action) } -function getRequestPromise ({ params, url }) { +function getRequestPromise ({ params, url }, meta) { const fetchPromise = g().fetch(url, params) - if (params && typeof params.timeout === 'number') { + if (meta && typeof meta.timeout === 'number') { const rejectOnTimeout = new Promise((_, reject) => { const error = new Error(`Request to ${url} timed out`) - setTimeout(() => reject(error), params.timeout) + setTimeout(() => reject(error), meta.timeout) }) return Promise.race([fetchPromise, rejectOnTimeout]) @@ -110,13 +110,14 @@ function checkStatus (res) { * Action creator */ -function fetchActionCreator (url = '', params = {}) { +function fetchActionCreator (url = '', params = {}, meta = {}) { return { type: FETCH, payload: { url, params - } + }, + meta } } From 61291fd2e486a5f09f0d1cbf8a48a74fff8d32db Mon Sep 17 00:00:00 2001 From: Jaunius Date: Sat, 11 Mar 2017 17:40:13 +0100 Subject: [PATCH 4/6] Update tests --- test/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.js b/test/index.js index aa53c59..e0db656 100644 --- a/test/index.js +++ b/test/index.js @@ -47,7 +47,7 @@ test('should work', t => { test('should reject on timeout', t => { t.plan(1) - run(fetch(urlTimeout, { timeout: 1000 })).then(() => t.fail(), (res) => t.pass()) + run(fetch(urlTimeout, {}, { timeout: 1000 })).then(() => t.fail(), (res) => t.pass()) }) test('should reject on invalid response', t => { From 88beec79b68b99c1009fa1d2db11e0e6e71b91e3 Mon Sep 17 00:00:00 2001 From: Jaunius Date: Sat, 11 Mar 2017 18:05:41 +0100 Subject: [PATCH 5/6] Update Readme.md --- Readme.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index 4530d05..7268e96 100644 --- a/Readme.md +++ b/Readme.md @@ -30,18 +30,20 @@ You can create your own action creators for this package, or you can use the one payload: { url, params - } + }, + meta: { timeout } } ``` -Where `url` and `params` are what you would pass as the first and second arguments to the native `fetch` API. If you want your action creators to support some async flow control, you should use [redux-effects](https://github.com/redux-effects/redux-effects)' `bind` function. If you do, your fetch action will return you an object with the following properties: +Where `url` and `params` are what you would pass as the first and second arguments to the native `fetch` API. Third argument is action meta: `timeout` - the numerical value (in ms) which is used for rejecting request promise if it takes longer time to complete than the value of this property. + +If you want your action creators to support some async flow control, you should use [redux-effects](https://github.com/redux-effects/redux-effects)' `bind` function. If you do, your fetch action will return you an object with the following properties: * `url` - The url of the endpoint you requested (as returned by the request) * `status` - The numerical status code of the response (e.g. 200) * `statusText` - The text version of the status (e.g. 'OK') * `headers` - A [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers) * `value` - The deserialized value of the response. This may be an object or string, depending on the type of response (json or text). - * `timeout` - The numerical value (in ms) which is used for rejecting request promise if it takes longer time to complete than the value of this property ## Examples @@ -80,7 +82,7 @@ function signupUser (user) { bind(fetch(api + '/user', { method: 'POST', body: user - }), ({value}) => userDidLogin(value), ({value}) => setError(value)) + }, { timeout: 10000 }), ({value}) => userDidLogin(value), ({value}) => setError(value)) ] } From e63424b1350782979e00189b8635679ff28e0918 Mon Sep 17 00:00:00 2001 From: Jaunius Date: Sat, 11 Mar 2017 22:52:41 +0100 Subject: [PATCH 6/6] Rename consts --- test/index.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/index.js b/test/index.js index e0db656..e54edd5 100644 --- a/test/index.js +++ b/test/index.js @@ -6,15 +6,15 @@ import test from 'tape' import fetchMock from 'fetch-mock' import fetchMw, {fetch} from '../src' -const urlTimeout = 'http://localhost/timeout' +const timeoutUrl = 'http://localhost/timeout' -const urlSuccess = 'http://localhost/200' +const successUrl = 'http://localhost/200' const failureUrl = 'http://localhost/404' -fetchMock.get(urlTimeout, new Promise(res => setTimeout(res, 2000)).then(() => 200)); +fetchMock.get(timeoutUrl, new Promise(res => setTimeout(res, 2000)).then(() => 200)); -fetchMock.get(urlSuccess, { +fetchMock.get(successUrl, { headers: { 'Content-Type': ['text/html'] }, }); @@ -36,8 +36,8 @@ const run = fetchMw(api)(() => {}) */ test('should work', t => { - run(fetch(urlSuccess)).then(({url, headers, value, status, statusText}) => { - t.equal(url, urlSuccess) + run(fetch(successUrl)).then(({url, headers, value, status, statusText}) => { + t.equal(url, successUrl) t.equal(status, 200) t.equal(statusText, 'OK') t.ok(headers.get('content-type').indexOf('text/html') !== -1) @@ -47,7 +47,7 @@ test('should work', t => { test('should reject on timeout', t => { t.plan(1) - run(fetch(urlTimeout, {}, { timeout: 1000 })).then(() => t.fail(), (res) => t.pass()) + run(fetch(timeoutUrl, {}, { timeout: 1000 })).then(() => t.fail(), (res) => t.pass()) }) test('should reject on invalid response', t => {