Skip to content

Commit

Permalink
Merge pull request #24 from kouhin/release/v1.0.0-rc.1
Browse files Browse the repository at this point in the history
Release/v1.0.0 rc.1
  • Loading branch information
kouhin authored Dec 21, 2016
2 parents 69efeec + 7023436 commit 3f3a42d
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 177 deletions.
9 changes: 0 additions & 9 deletions .travis.yml

This file was deleted.

19 changes: 19 additions & 0 deletions circle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
machine:
node:
version: 7
dependencies:
override:
- sudo apt-get install jq
- npm install
- npm run build
test:
override:
- npm test
deployment:
production:
branch: master
commands:
- git tag v`jq -r '.version' package.json`
- git push origin --tags
- echo -e "$NPM_USERNAME\n$NPM_PASSWORD\n$NPM_EMAIL" | npm login
- npm publish
51 changes: 26 additions & 25 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "redux-dataloader",
"version": "1.0.0-beta.6",
"version": "1.0.0-rc.1",
"description": "Loads async data for Redux apps focusing on preventing duplicated requests and dealing with async dependencies.",
"main": "lib/index.js",
"repository": {
Expand Down Expand Up @@ -41,33 +41,26 @@
"author": "Bin Hou",
"license": "MIT",
"devDependencies": {
"babel-cli": "^6.10.1",
"babel-core": "^6.10.4",
"babel-eslint": "^6.1.2",
"babel-loader": "^6.2.4",
"babel-plugin-transform-runtime": "^6.9.0",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.11.1",
"babel-preset-stage-0": "^6.5.0",
"babel-cli": "^6.18.0",
"babel-core": "^6.21.0",
"babel-eslint": "^7.1.1",
"babel-loader": "^6.2.10",
"babel-polyfill": "^6.20.0",
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-0": "^6.16.0",
"chai": "^3.5.0",
"chai-as-promised": "^5.3.0",
"eslint": "^3.0.1",
"eslint-config-airbnb": "^9.0.1",
"eslint-plugin-import": "^1.10.2",
"eslint-plugin-jsx-a11y": "^1.5.5",
"eslint-plugin-react": "^5.2.2",
"eslint-config-airbnb-deps": "^13.0.0",
"isparta": "^4.0.0",
"istanbul": "^0.4.4",
"mocha": "^2.5.3",
"rimraf": "^2.5.3",
"sinon": "^1.17.4",
"sinon-chai": "^2.8.0",
"webpack": "^1.13.1"
"istanbul": "^0.4.5",
"mocha": "^3.2.0",
"rimraf": "^2.5.4",
"sinon": "^1.17.6",
"webpack": "^1.14.0"
},
"dependencies": {
"babel-polyfill": "^6.9.1",
"debug": "^2.2.0",
"lodash": "^4.13.1"
"babel-runtime": "^6.20.0",
"lodash": "^4.17.2"
},
"eslintConfig": {
"parser": "babel-eslint",
Expand All @@ -80,7 +73,15 @@
"plugins": [
"react",
"import"
]
],
"rules": {
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": true
}
]
}
},
"babel": {
"presets": [
Expand Down
42 changes: 6 additions & 36 deletions src/data-loader.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import isEqual from 'lodash/isEqual';
import Debug from 'debug';

import { loadFailure, loadSuccess } from './action';
import { isAction } from './utils';
import { fixedWait } from './wait-strategies';

const debug = new Debug('redux-dataloader:data-loader');

const DEFAULT_OPTIONS = {
ttl: 10000, // Default TTL: 10s
retryTimes: 1,
Expand Down Expand Up @@ -56,66 +53,44 @@ class DataLoaderTask {

const disableInternalAction = !!options.disableInternalAction;

if (debug.enabled) {
debug('Excute with options', opts);
}

if (!this.params.shouldFetch(this.context)) {
if (debug.enabled) {
debug('shouldFetch() returns false');
}
if (!disableInternalAction) {
const successAction = loadSuccess(this.context.action);
this.context.dispatch(successAction); // load nothing
if (debug.enabled) {
debug('A success action is dispatched for shouldFetch() = false', successAction);
}
}
return null;
}
const loadingAction = this.params.loading(this.context);
this.context.dispatch(loadingAction);
if (debug.enabled) {
debug('A loading action is dispatched', loadingAction);
}

let currentRetry = 0;
let result;
let error;

for (;;) {
try {
if (debug.enabled) {
debug('Start fetching, try = ', (currentRetry + 1));
}
result = await this.params.fetch(this.context);
if (debug.enabled) {
debug('Fetching success, result = ', result);
}
break;
} catch (ex) {
debug('Fetching failed, ex = ', ex);
currentRetry++;
currentRetry += 1;
if (options.retryTimes && currentRetry < opts.retryTimes) {
const sleepTime = opts.retryWait.next().value;
if (debug.enabled) {
debug(`Sleeping for ${sleepTime} ms..., and retry`);
}
await sleep(sleepTime);
continue;
} else {
error = ex;
break;
}
error = ex;
break;
}
}

if (!error) {
const successAction = this.params.success(this.context, result);

// Check successAction
if (successAction.type === this.context.action.type) {
const errorAction = this.params.error(
this.context,
new Error('Result action type equals origial action type', this.context.action)
new Error('Result action type equals origial action type', this.context.action),
);
this.context.dispatch(errorAction);
if (!disableInternalAction) {
Expand All @@ -124,7 +99,6 @@ class DataLoaderTask {
return errorAction;
}

debug('Dispatch a success action', successAction);
this.context.dispatch(successAction);
if (!disableInternalAction) {
this.context.dispatch(loadSuccess(this.context.action, result));
Expand All @@ -142,7 +116,6 @@ class DataLoaderTask {
}
return errorAction;
}
debug('Dispatch an error action', errorAction);
this.context.dispatch(errorAction);
if (!disableInternalAction) {
this.context.dispatch(loadFailure(this.context.action, error));
Expand Down Expand Up @@ -192,9 +165,6 @@ class DataLoaderTaskDescriptor {
*/
function createLoader(pattern, params, options) {
const dataLoaderDescriptor = new DataLoaderTaskDescriptor(pattern, params, options);
if (debug.enabled) {
debug('Create a new data loader descriptor', dataLoaderDescriptor);
}
return dataLoaderDescriptor;
}

Expand Down
12 changes: 5 additions & 7 deletions src/load.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import Debug from 'debug';
import { loadRequest } from './action';
import { isAction } from './utils';

const debug = new Debug('redux-dataloader:load');
export const DATALOADER_ACTION_ID = () => {};

export default function load(action) {
if (!isAction(action)) {
throw new Error('action must be object', action);
}
if (debug.enabled) {
debug('load() an action = ', action);
debug('A Promise with a wrapped action is returned', loadRequest(action));
}
return Promise.resolve(loadRequest(action));
const asyncAction = Promise.resolve(loadRequest(action));
// eslint-disable-next-line no-underscore-dangle
asyncAction._id = DATALOADER_ACTION_ID;
return asyncAction;
}
37 changes: 8 additions & 29 deletions src/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,19 @@ import findKey from 'lodash/findKey';
import find from 'lodash/find';
import isEqual from 'lodash/isEqual';
import isInteger from 'lodash/isInteger';
import Debug from 'debug';

import { isPromise } from './utils';
import { LOAD_DATA_REQUEST_ACTION } from './action';

const debug = new Debug('redux-dataloader:middleware');
import { DATALOADER_ACTION_ID } from './load';

function findRunningTaskKey(runningTasksMap, action) {
return findKey(runningTasksMap, (o) => isEqual(o.action, action));
return findKey(runningTasksMap, o => isEqual(o.action, action));
}

export default function createDataLoaderMiddleware(loaders, args, opts) {
const runningTasks = {};

let currentId = 1;
const uniqueId = (prefix) => `${prefix}${currentId++}`;
currentId += 1;
const uniqueId = prefix => `${prefix}${currentId}`;

const middleware = ({ dispatch, getState }) => {
const ctx = {
Expand All @@ -26,30 +23,21 @@ export default function createDataLoaderMiddleware(loaders, args, opts) {
getState,
};

return (next) => (receivedAction) => {
if (!isPromise(receivedAction)) {
return next => (receivedAction) => {
// eslint-disable-next-line no-underscore-dangle
if (!receivedAction._id || receivedAction._id !== DATALOADER_ACTION_ID) {
return next(receivedAction);
}
debug('Received a promise action', receivedAction);

return receivedAction.then((asyncAction) => {
if (asyncAction.type !== LOAD_DATA_REQUEST_ACTION) {
debug(`Received promise action is not ${LOAD_DATA_REQUEST_ACTION}, pass it to the next middleware`); // eslint-disable-line max-len
return next(receivedAction);
}
debug(`Received promise action is ${LOAD_DATA_REQUEST_ACTION}, pass wrapped action to the next middleware`); // eslint-disable-line max-len
next(asyncAction); // dispatch data loader request action
const { action } = asyncAction.meta;
debug('Original action is', action);
const runningTaskKey = findRunningTaskKey(runningTasks, action);
debug('Find task from task cache');
if (runningTaskKey) {
debug('Cache hit!, Key = ', runningTaskKey);
return runningTasks[runningTaskKey].promise;
}

const taskDescriptor = find(loaders, (loader) => loader.supports(action));
debug('Cache does not hit, finding task descriptor', taskDescriptor);
const taskDescriptor = find(loaders, loader => loader.supports(action));

if (!taskDescriptor) {
throw new Error('No loader for action', action);
Expand All @@ -60,15 +48,8 @@ export default function createDataLoaderMiddleware(loaders, args, opts) {
...taskDescriptor.options,
...(asyncAction.meta.options || {}),
};
debug(
'Merge options from taskDescriptor and dispatched action',
taskDescriptor.options,
asyncAction.meta.options, options
);

const key = uniqueId(`${action.type}__`);
debug('Generate new cache key', key);
debug('Start executing');
const runningTask = taskDescriptor.newTask(ctx, action).execute(options);

if (isInteger(options.ttl) && options.ttl > 0) {
Expand All @@ -77,9 +58,7 @@ export default function createDataLoaderMiddleware(loaders, args, opts) {
promise: runningTask,
};
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
debug(`Set cache ttl for task[${key}], ttl = ${options.ttl}`);
setTimeout(() => {
debug(`Task[${key}] is removed from cache, for ttl = ${options.ttl} ms`);
delete runningTasks[key];
}, options.ttl);
}
Expand Down
6 changes: 1 addition & 5 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
export function isPromise(val) {
return val && typeof val.then === 'function';
}

export function isAction(action) {
const result = action && (typeof action) === 'object' &&
action.type && (typeof action.type) === 'string';
Expand All @@ -11,7 +7,7 @@ export function isAction(action) {
export function formatError(err) {
const error = (err instanceof Error) ? err : new Error(err);
const result = {};
Object.getOwnPropertyNames(error).forEach(key => {
Object.getOwnPropertyNames(error).forEach((key) => {
result[key] = error[key];
});
return result;
Expand Down
Loading

0 comments on commit 3f3a42d

Please sign in to comment.