async-selector
Advanced tools
Comparing version 1.0.7 to 1.0.8
@@ -357,3 +357,3 @@ import createAsyncSelector from '../src/index'; | ||
test('throttle', () => { | ||
test('throttle', done => { | ||
let c = 0; | ||
@@ -375,5 +375,10 @@ const state = {text: 'Ma'}; | ||
setTimeout(() => { | ||
employees(state); | ||
employees({text: 'Marc'}); | ||
const result = employees(state); | ||
expect(deepEqual(result, expected)).toBe(true); | ||
try { | ||
expect(deepEqual(result, expected)).toBe(true); | ||
} catch (e) { | ||
done.fail(e) | ||
} | ||
done() | ||
},200) | ||
@@ -383,3 +388,3 @@ | ||
test('throttle', () => { | ||
test('throttle', done => { | ||
let c = 0; | ||
@@ -405,3 +410,9 @@ let state = {text: 'Ma'}; | ||
const result = employees(state); | ||
expect(deepEqual(result, expected)).toBe(true); | ||
try { | ||
expect(deepEqual(result, expected)).toBe(true); | ||
} catch (e) { | ||
done.fail(e) | ||
} | ||
done() | ||
}, 500); | ||
@@ -411,3 +422,3 @@ },200) | ||
test('throttle memoization', () => { | ||
test('throttle memoization', done => { | ||
let c = 0; | ||
@@ -436,3 +447,8 @@ let state = {text: 'Ma'}; | ||
const result = employees(state); | ||
expect(c).toBe(1); | ||
try { | ||
expect(c).toBe(1); | ||
} catch (e) { | ||
done.fail(e) | ||
} | ||
done(); | ||
}, 500); | ||
@@ -444,2 +460,3 @@ },200) | ||
function getAges(employees, maxAge) { | ||
return new Promise((resolve, reject) => { | ||
@@ -449,4 +466,4 @@ setTimeout(() => { | ||
const ages = [12, 34]; | ||
if (text.length > 10) { | ||
reject('Search Text Too Long'); | ||
if (maxAge < 3) { | ||
reject('too young'); | ||
} else { | ||
@@ -485,3 +502,3 @@ const employeeAges = employees.map(name => { | ||
test('multiple params2', () => { | ||
test('multiple params2', done => { | ||
let c = 0; | ||
@@ -493,3 +510,3 @@ let state = {employees: ['Mark Metzger'], maxAge: 40}; | ||
value: [12], | ||
previous: undefined, | ||
previous: [12], | ||
isWaiting: false, | ||
@@ -502,10 +519,13 @@ isResolved: true, | ||
setTimeout(() => { | ||
console.log(expected); | ||
const result = ages(state); | ||
console.log('blahhhhh', result); | ||
expect(deepEqual(result, expected)).toBe(true); | ||
try { | ||
expect(deepEqual(result, expected)).toBe(true); | ||
} catch (e) { | ||
done.fail(e) | ||
} | ||
done(); | ||
}, 300); | ||
}); | ||
test('multiple params3', () => { | ||
test('multiple params3', done => { | ||
let c = 0; | ||
@@ -518,3 +538,3 @@ let state = {employees: ['Mark Metzger'], maxAge: 10}; | ||
value: [], | ||
previous: undefined, | ||
previous: [], | ||
isWaiting: false, | ||
@@ -528,5 +548,202 @@ isResolved: true, | ||
const result = ages(state); | ||
expect(deepEqual(result, expected)).toBe(true); | ||
try { | ||
expect(deepEqual(result, expected)).toBe(true); | ||
} catch (e) { | ||
done.fail(e) | ||
} | ||
done(); | ||
}); | ||
}, 200); | ||
}); | ||
test('debounced memoized', done => { | ||
let c = 0; | ||
let state = {employees: ['Mark Metzger'], maxAge: 10}; | ||
const throttle = f => _.debounce(f, 100) | ||
const onResolve = () => {c++} | ||
const ages = createAsyncSelector( | ||
{...params2, throttle, onResolve }, | ||
s => s.employees, | ||
s => s.maxAge); | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
try { | ||
expect(c).toBe(1); | ||
} catch (e) { | ||
done.fail(e) | ||
} | ||
done(); | ||
}, 122); | ||
}, 122); | ||
}, 122); | ||
}, 120); | ||
}, 120); | ||
}); | ||
test('debounced memoized 2', done => { | ||
let c = 0; | ||
let state = {employees: ['Mark Metzger'], maxAge: 10}; | ||
const throttle = f => _.debounce(f, 100) | ||
const onResolve = () => {c++} | ||
const ages = createAsyncSelector( | ||
{...params2, throttle, onResolve }, | ||
s => s.employees, | ||
s => s.maxAge); | ||
ages(state); | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
try { | ||
expect(c).toBe(1); | ||
} catch (e) { | ||
done.fail(e) | ||
} | ||
done(); | ||
}, 122); | ||
}, 122); | ||
}, 122); | ||
}, 120); | ||
}, 120); | ||
}); | ||
test('debounced memoized 3', done => { | ||
let c = 0; | ||
let state = {employees: ['Mark Metzger'], maxAge: 10}; | ||
const throttle = f => _.debounce(f, 100) | ||
const onResolve = () => {c++} | ||
const ages = createAsyncSelector( | ||
{...params2, throttle, onResolve }, | ||
s => s.employees, | ||
s => s.maxAge); | ||
ages(state); | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
ages({employees: ['Mark Metzger'], maxAge: 9}) | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
try { | ||
expect(c).toBe(2); | ||
} catch (e) { | ||
done.fail(e) | ||
} | ||
done(); | ||
}, 88); | ||
}, 188); | ||
}, 88); | ||
}, 80); | ||
}, 180); | ||
}); | ||
test('debounced memoized 4', done => { | ||
let c = 0; | ||
let state = {employees: ['Mark Metzger'], maxAge: 100}; | ||
const throttle = f => _.debounce(f, 100) | ||
const onResolve = () => {c++} | ||
const ages = createAsyncSelector( | ||
{...params2, throttle, onResolve }, | ||
s => s.employees, | ||
s => s.maxAge); | ||
const expected = { | ||
value: [], | ||
previous: [], | ||
isWaiting: false, | ||
isResolved: true, | ||
isRejected: false, | ||
} | ||
ages(state); | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
const n = {employees: ['Mark Metzger'], maxAge: 9}; | ||
ages(n); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
const result = ages(n); | ||
console.log(result); | ||
try { | ||
expect(deepEqual(expected, result)).toBe(true); | ||
} catch (e) { | ||
done.fail(e) | ||
} | ||
done(); | ||
}, 88); | ||
}, 188); | ||
}, 88); | ||
}, 80); | ||
}, 180); | ||
}); | ||
test('debounced memoized 5', done => { | ||
let c = 0; | ||
let state = {employees: ['Mark Metzger'], maxAge: 10}; | ||
const throttle = f => _.debounce(f, 100) | ||
const onResolve = () => {c++} | ||
const ages = createAsyncSelector( | ||
{...params2, throttle, onResolve }, | ||
s => s.employees, | ||
s => s.maxAge); | ||
const expected = { | ||
value: [], | ||
previous: [12], | ||
isWaiting: true, | ||
isResolved: false, | ||
isRejected: false, | ||
} | ||
ages(state); | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
const n = {employees: ['Mark Metzger'], maxAge: 20}; | ||
ages(n); | ||
setTimeout(() => { | ||
ages(state); | ||
setTimeout(() => { | ||
const result = ages({employees: ['Mark Metzger'], maxAge: 20}); | ||
try { | ||
expect(deepEqual(expected, result)).toBe(true); | ||
} catch (e) { | ||
done.fail(e) | ||
} | ||
done(); | ||
}, 88); | ||
}, 188); | ||
}, 88); | ||
}, 80); | ||
}, 180); | ||
}); |
@@ -87,4 +87,4 @@ 'use strict'; | ||
if (throttle !== null && f === null) { | ||
f = throttle(function (state, forceUpdate) { | ||
return func(state, forceUpdate, true); | ||
f = throttle(function (state) { | ||
return func(state, true, true); | ||
}); | ||
@@ -94,3 +94,3 @@ } | ||
f(state, forceUpdate); | ||
memoizedResult = createResultObject(sync(mapped), previousResolution, true, false, false, omitStatus); | ||
memoizedResult = createResultObject(sync.apply(undefined, _toConsumableArray(mapped)), previousResolution, true, false, false, omitStatus); | ||
return memoizedResult; | ||
@@ -97,0 +97,0 @@ } |
{ | ||
"name": "async-selector", | ||
"version": "1.0.7", | ||
"version": "1.0.8", | ||
"description": "Select values from databases using asynchronous selectors.", | ||
@@ -5,0 +5,0 @@ "main": "./dist/index.js", |
@@ -11,9 +11,9 @@ A simple, lightweight library inspired by [reselect](https://github.com/reduxjs/reselect) which allows you select data out of a remote database almost as easily as you would from local state. This library will normally be used in conjunction with redux and reselect but it has no dependencies. | ||
### Problems | ||
This way has many common problem that just about everybody has experienced | ||
This way has many common problems that just about everybody has experienced. | ||
- An old request could potential overwrite the data of a newer request if you don't handle that edge case. | ||
- During the time between the request being sent and the response received, you might be rendering stale data. | ||
- Having many variables in state and associated actions/reducers for the query status is a lot of redundant, tedious code | ||
- Having many variables in state and associated actions/reducers for the query status is a lot of redundant, tedious code. | ||
- If you have requests that depend on previous responses, this could result in callback hell and/or difficulty in reuse of the first response's data. | ||
- There is no guarantee that multiple duplicate requests won't be made. | ||
- Sending queries in response to user actions can result in brittle code and isn't really in the spirit of redux/reselect (Unless it's actually necessary) | ||
- Sending queries in response to user actions can result in brittle code and isn't really in the spirit of redux/reselect (Unless it's actually necessary). For example, if you call a function in a server based on many user inputs, you will have to write code to send a query for every input field. | ||
@@ -26,3 +26,3 @@ # Async selectors to the rescue! | ||
``` | ||
```js | ||
import createAsyncSelector from 'async-selector' | ||
@@ -62,3 +62,3 @@ import { store } from './index' // get store somehow | ||
``` | ||
```js | ||
// Request in progress | ||
@@ -76,3 +76,3 @@ { | ||
value: ['Mark'], | ||
previous: ['Steve Miller'], | ||
previous: ['Mark'], // when resolved, previous always equals value | ||
isWaiting: false, | ||
@@ -93,5 +93,5 @@ isResolved: true, | ||
### Usage in redux | ||
An serious problem with the above code is that when the promise resolves, the app doesn't re-render to show the new data. Instead of simply logging the employees in the onResolve callback, we need to dispatch an action to tell the app to re-render and call the selector. This is a bit of a hack because nothing in the state was changed. One thing to make sure of is the action that is called action changes state in some way or a re-render event won't be triggered. Another thing to be careful of is recursion resulting from the action causing the inputs of the selector to change. | ||
A serious problem with the above code is that when the promise resolves, the app doesn't re-render to show the new data. Instead of simply logging the employees in the onResolve callback, we need to dispatch an action to tell the app to re-render and call the selector. This is a bit of a hack because nothing in the state was changed. One thing to make sure of is that the action changes state in some way or a re-render event won't be triggered. Another thing to be careful to avoid is recursion resulting from the action causing the inputs of the selector to change. | ||
``` | ||
```js | ||
const triggerRerender = () => { | ||
@@ -114,3 +114,3 @@ return { | ||
``` | ||
```js | ||
// .... | ||
@@ -133,3 +133,3 @@ const employees = createAsyncSelector(params, [getSearchText]); | ||
``` | ||
```js | ||
function buttonClicked() { | ||
@@ -142,7 +142,21 @@ employeeAges(store.getState(), true) // "true" indicates that the selector should create a new promise regardless of whether the inputs changed | ||
### Throttling queries | ||
Often, you don't want to send queries too frequently. For example, if the user is typing into a textfield, you might only want to send a query after the user finished, so as to not spam the API. To solve this, you can use the "throttle" parameter. | ||
```js | ||
import _ from 'underscore'; | ||
const params = { | ||
sync: (searchText) => [], | ||
async: getEmployees, | ||
throttle: f => _.debounce(f, 250), | ||
} | ||
``` | ||
Internally, a debounced version of the selector is generated and it is (recursively) called every time the selector is called (if the inputs were changed). | ||
# Documentation | ||
createAsyncSelector takes in two arguments: | ||
``` | ||
```js | ||
function createAsyncSelector(params, ...selectors) -> obj | ||
@@ -162,3 +176,3 @@ ``` | ||
Each selector is a function that takes in state as its argument just like in reselect. It memoizes its results so the only way for it to return a different value for the same inputs is if it contained a promise that was resolved. An async selector is only different from a normal selector in that you can pass in "forceUpdate" bool as the second parameter to force a promise to be made. | ||
``` | ||
```js | ||
function selector(state, forceUpdate=false) -> any | ||
@@ -169,3 +183,3 @@ ``` | ||
### params.sync (Required) | ||
``` | ||
```js | ||
function sync(...selectorResults) -> any | ||
@@ -175,3 +189,3 @@ ``` | ||
### params.async (Required) | ||
``` | ||
```js | ||
function async(...selectorResults) -> promise | ||
@@ -181,3 +195,3 @@ ``` | ||
### params.onResolve | ||
``` | ||
```js | ||
function onResolve(resolvedValue, ...selectorResults) -> void | ||
@@ -187,3 +201,3 @@ ``` | ||
### params.onReject | ||
``` | ||
```js | ||
function onReject(rejectedValue, ...selectorResults) -> void | ||
@@ -193,3 +207,3 @@ ``` | ||
### params.onCancel | ||
``` | ||
```js | ||
function onCancel(cancelledPromise, ...selectorResults) -> void | ||
@@ -199,3 +213,3 @@ ``` | ||
### params.shouldUseAsync | ||
``` | ||
```js | ||
function shouldUseAsync(...selectorResults) -> bool | ||
@@ -205,5 +219,11 @@ ``` | ||
### params.omitStatus | ||
``` | ||
```js | ||
omitStatus: bool | ||
``` | ||
This is a convenience variable. If it is set to true, the selector will only output a value not the entire object. | ||
### params.throttle | ||
```js | ||
function throttle(func) -> func | ||
``` | ||
This function is passed a selector and the new version of that function is recursively called every time the selector is called and the inputs were changed. |
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
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
141132
868
213