Socket
Socket
Sign inDemoInstall

async-selector

Package Overview
Dependencies
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

async-selector - npm Package Compare versions

Comparing version 1.0.22 to 1.0.23

dist/index.d.ts

857

__tests__/index.test.js

@@ -1,12 +0,120 @@

import createAsyncSelector from '../src/index';
import _ from '../src/underscore';
import createAsyncSelector from "../dist/index";
/* random underscore functions */
const _ = {};
var restArguments = function(func, startIndex) {
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function() {
var length = Math.max(arguments.length - startIndex, 0),
rest = Array(length),
index = 0;
for (; index < length; index++) {
rest[index] = arguments[index + startIndex];
}
switch (startIndex) {
case 0:
return func.call(this, rest);
case 1:
return func.call(this, arguments[0], rest);
case 2:
return func.call(this, arguments[0], arguments[1], rest);
}
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
args[startIndex] = rest;
return func.apply(this, args);
};
};
_.delay = restArguments(function(func, wait, args) {
return setTimeout(function() {
return func.apply(null, args);
}, wait);
});
_.now =
Date.now ||
function() {
return new Date().getTime();
};
_.debounce = function(func, wait, immediate) {
var timeout, result;
var later = function(context, args) {
timeout = null;
if (args) result = func.apply(context, args);
};
var debounced = restArguments(function(args) {
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(later, wait);
if (callNow) result = func.apply(this, args);
} else {
timeout = _.delay(later, wait, this, args);
}
return result;
});
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
};
_.throttle = function(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = _.now();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
};
const state = {
text: "Ma",
}
text: "Ma"
};
let count = 0;
function getEmployees(text) {
return new Promise((resolve, reject) => {

@@ -16,3 +124,3 @@ setTimeout(() => {

if (text.length > 10) {
reject('Search Text Too Long');
reject("Search Text Too Long");
} else {

@@ -30,14 +138,14 @@ resolve(database.filter(name => name.includes(text)));

const params = {
sync:(text) => [],
async: getEmployees,
}
sync: text => [],
async: getEmployees
};
test('Test createAsyncSelector is function', () => {
expect(typeof createAsyncSelector === 'function').toBe(true);
test("Test createAsyncSelector is function", () => {
expect(typeof createAsyncSelector === "function").toBe(true);
});
test('Test createAsyncSelector default value', () => {
test("Test createAsyncSelector default value", () => {
const employees = createAsyncSelector(params, state => state.text);
const expected = {
const expected = {
value: [],

@@ -47,11 +155,11 @@ previous: undefined,

isResolved: false,
isRejected: false
}
isRejected: false
};
expect(deepEqual(employees(state), expected)).toBe(true);
});
test('Test createAsyncSelector async value', done => {
test("Test createAsyncSelector async value", done => {
const employees = createAsyncSelector(params, state => state.text);
const expected = {
const expected = {
value: ["Mark Metzger"],

@@ -61,5 +169,5 @@ previous: ["Mark Metzger"],

isResolved: true,
isRejected: false
}
employees(state)
isRejected: false
};
employees(state);
setTimeout(() => {

@@ -69,13 +177,12 @@ try {

} catch (e) {
done.fail(e)
done.fail(e);
}
done();
}, 200)
}, 200);
});
test('Test createAsyncSelector previous value', done => {
test("Test createAsyncSelector previous value", done => {
const employees = createAsyncSelector(params, state => state.text);
const expected = {
const expected = {
value: ["Steven Miller"],

@@ -85,8 +192,8 @@ previous: ["Steven Miller"],

isResolved: true,
isRejected: false
}
isRejected: false
};
employees(state)
employees(state);
setTimeout(() => {
employees(state)
employees(state);
state.text = "St";

@@ -98,13 +205,13 @@ employees(state);

} catch (e) {
done.fail(e)
done.fail(e);
}
done()
}, 200)
}, 200)
done();
}, 200);
}, 200);
});
test('Test createAsyncSelector rejected', done => {
test("Test createAsyncSelector rejected", done => {
const employees = createAsyncSelector(params, state => state.text);
const state = {text: 'Mar'};
const expected = {
const state = { text: "Mar" };
const expected = {
value: "Search Text Too Long",

@@ -114,8 +221,8 @@ previous: ["Mark Metzger"],

isResolved: false,
isRejected: true
}
isRejected: true
};
employees(state)
employees(state);
setTimeout(() => {
employees(state)
employees(state);
state.text = "Sttttttttttttt";

@@ -127,14 +234,19 @@ employees(state);

} catch (e) {
done.fail(e)
done.fail(e);
}
done()
}, 200)
}, 200)
done();
}, 200);
}, 200);
});
test('memoization', () => {
test("memoization", () => {
let count = 0;
let state = {text: 'gg'}
const sync = () => {count += 1};
const employees = createAsyncSelector({...params, sync}, state => state.text);
let state = { text: "gg" };
const sync = () => {
count += 1;
};
const employees = createAsyncSelector(
{ ...params, sync },
state => state.text
);

@@ -149,5 +261,10 @@ count = 0;

test('memoization', () => {
const sync = () => {count += 1};
const employees = createAsyncSelector({...params, sync}, state => state.text);
test("memoization", () => {
const sync = () => {
count += 1;
};
const employees = createAsyncSelector(
{ ...params, sync },
state => state.text
);

@@ -161,5 +278,10 @@ count = 0;

test('lack of memoization', () => {
const sync = () => {count += 1};
const employees = createAsyncSelector({...params, sync}, state => state.text);
test("lack of memoization", () => {
const sync = () => {
count += 1;
};
const employees = createAsyncSelector(
{ ...params, sync },
state => state.text
);

@@ -173,5 +295,10 @@ count = 0;

test('cancel', () => {
const onCancel = () => {count += 1};
const employees = createAsyncSelector({...params, onCancel}, state => state.text);
test("cancel", () => {
const onCancel = () => {
count += 1;
};
const employees = createAsyncSelector(
{ ...params, onCancel },
state => state.text
);

@@ -185,5 +312,10 @@ count = 0;

test('cancel', done => {
const onCancel = () => {count += 1};
const employees = createAsyncSelector({...params, onCancel}, state => state.text);
test("cancel", done => {
const onCancel = () => {
count += 1;
};
const employees = createAsyncSelector(
{ ...params, onCancel },
state => state.text
);

@@ -198,18 +330,25 @@ count = 0;

} catch (e) {
done.fail(e)
done.fail(e);
}
done()
done();
}, 500);
});
test('omitStatus', () => {
test("omitStatus", () => {
const omitStatus = true;
const employees = createAsyncSelector({...params, omitStatus}, state => state.text);
const employees = createAsyncSelector(
{ ...params, omitStatus },
state => state.text
);
expect(deepEqual(employees(state), [])).toBe(true);
});
test('onResolve', () => {
const onResolve = () => {count += 1};
const employees = createAsyncSelector({...params, onResolve}, state => state.text);
test("onResolve", () => {
const onResolve = () => {
count += 1;
};
const employees = createAsyncSelector(
{ ...params, onResolve },
state => state.text
);
count = 0;

@@ -222,7 +361,12 @@ employees(state);

test('onResolve', done => {
test("onResolve", done => {
let count = 0;
const onResolve = () => {count += 1};
const employees = createAsyncSelector({...params, onResolve}, state => state.text);
const onResolve = () => {
count += 1;
};
const employees = createAsyncSelector(
{ ...params, onResolve },
state => state.text
);
employees(state);

@@ -237,5 +381,5 @@ setTimeout(() => {

} catch (e) {
done.fail(e)
done.fail(e);
}
done()
done();
}, 200);

@@ -246,6 +390,11 @@ }, 200);

test('onResolve forceUpdate', done => {
test("onResolve forceUpdate", done => {
let count = 0;
const onResolve = () => {count += 1};
const employees = createAsyncSelector({...params, onResolve}, state => state.text);
const onResolve = () => {
count += 1;
};
const employees = createAsyncSelector(
{ ...params, onResolve },
state => state.text
);
count = 0;

@@ -261,5 +410,5 @@ employees(state);

} catch (e) {
done.fail(e)
done.fail(e);
}
done()
done();
}, 200);

@@ -270,6 +419,11 @@ }, 200);

test('onResolve', done => {
test("onResolve", done => {
let count = 0;
const onResolve = () => {count += 1};
const employees = createAsyncSelector({...params, onResolve}, state => state.text);
const onResolve = () => {
count += 1;
};
const employees = createAsyncSelector(
{ ...params, onResolve },
state => state.text
);
employees(state);

@@ -284,5 +438,5 @@ setTimeout(() => {

} catch (e) {
done.fail(e)
done.fail(e);
}
done()
done();
}, 200);

@@ -293,16 +447,21 @@ }, 200);

test('debounce', done => {
test("debounce", done => {
let c = 0;
const throttle = f => _.debounce(f, 300);
const onResolve = () => {c += 1};
const employees = createAsyncSelector({...params, onResolve, throttle}, state => state.text);
employees({text: 'a'});
const onResolve = () => {
c += 1;
};
const employees = createAsyncSelector(
{ ...params, onResolve, throttle },
state => state.text
);
employees({ text: "a" });
setTimeout(() => {
employees({text: 'b'});
employees({ text: "b" });
setTimeout(() => {
employees({text: 'c'});
employees({ text: "c" });
setTimeout(() => {
employees({text: 'd'});
employees({ text: "d" });
setTimeout(() => {
employees({text: 'e'});
employees({ text: "e" });
setTimeout(() => {

@@ -312,5 +471,5 @@ try {

} catch (e) {
done.fail(e)
done.fail(e);
}
done()
done();
}, 400);

@@ -323,16 +482,21 @@ }, 400);

test('debounce', done => {
test("debounce", done => {
let c = 0;
const throttle = f => _.debounce(f, 300);
const onResolve = () => {c += 1};
const employees = createAsyncSelector({...params, onResolve, throttle}, state => state.text);
employees({text: 'a'});
const onResolve = () => {
c += 1;
};
const employees = createAsyncSelector(
{ ...params, onResolve, throttle },
state => state.text
);
employees({ text: "a" });
setTimeout(() => {
employees({text: 'b'});
employees({ text: "b" });
setTimeout(() => {
employees({text: 'c'});
employees({ text: "c" });
setTimeout(() => {
employees({text: 'd'});
employees({ text: "d" });
setTimeout(() => {
employees({text: 'e'});
employees({ text: "e" });
setTimeout(() => {

@@ -342,5 +506,5 @@ try {

} catch (e) {
done.fail(e)
done.fail(e);
}
done()
done();
}, 800);

@@ -353,16 +517,21 @@ }, 400);

test('throttle', done => {
test("throttle", done => {
let c = 0;
const throttle = f => _.throttle(f, 200);
const onResolve = (val) => {c += 1;};
const employees = createAsyncSelector({...params, onResolve, throttle}, state => state.text);
employees({text: 'a'});
const onResolve = val => {
c += 1;
};
const employees = createAsyncSelector(
{ ...params, onResolve, throttle },
state => state.text
);
employees({ text: "a" });
setTimeout(() => {
employees({text: 'b'});
employees({ text: "b" });
setTimeout(() => {
employees({text: 'c'});
employees({ text: "c" });
setTimeout(() => {
employees({text: 'd'});
employees({ text: "d" });
setTimeout(() => {
employees({text: 'e'});
employees({ text: "e" });
setTimeout(() => {

@@ -372,5 +541,5 @@ try {

} catch (e) {
done.fail(e)
done.fail(e);
}
done()
done();
}, 60);

@@ -383,20 +552,25 @@ }, 60);

test('throttle', done => {
test("throttle", done => {
let c = 0;
const state = {text: 'Ma'};
const state = { text: "Ma" };
const throttle = f => _.throttle(f, 50);
const onResolve = () => {c += 1};
const employees = createAsyncSelector({...params, onResolve, throttle}, state => state.text);
const onResolve = () => {
c += 1;
};
const employees = createAsyncSelector(
{ ...params, onResolve, throttle },
state => state.text
);
const expected = {
const expected = {
value: [],
previous: ['Mark Metzger'],
previous: ["Mark Metzger"],
isWaiting: true,
isResolved: false,
isRejected: false
}
isRejected: false
};
employees(state);
setTimeout(() => {
employees({text: 'Marc'});
employees({ text: "Marc" });
const result = employees(state);

@@ -406,27 +580,31 @@ try {

} catch (e) {
done.fail(e)
done.fail(e);
}
done()
},200)
done();
}, 200);
});
test('throttle', done => {
test("throttle", done => {
let c = 0;
let state = {text: 'Ma'};
let state = { text: "Ma" };
const throttle = f => _.throttle(f, 50);
const onResolve = () => {c += 1};
const employees = createAsyncSelector({...params, onResolve, throttle}, state => state.text);
const onResolve = () => {
c += 1;
};
const employees = createAsyncSelector(
{ ...params, onResolve, throttle },
state => state.text
);
const expected = {
value: 'Search Text Too Long',
previous: ['Mark Metzger'],
const expected = {
value: "Search Text Too Long",
previous: ["Mark Metzger"],
isWaiting: false,
isResolved: false,
isRejected: true,
}
isRejected: true
};
employees(state);
setTimeout(() => {
state = {text: 'aaaaaaaaaaaaaa'};
state = { text: "aaaaaaaaaaaaaa" };
employees(state);

@@ -438,24 +616,28 @@ setTimeout(() => {

} catch (e) {
done.fail(e)
done.fail(e);
}
done()
done();
}, 500);
},200)
}, 200);
});
test('throttle memoization', done => {
test("throttle memoization", done => {
let c = 0;
let state = {text: 'Ma'};
let state = { text: "Ma" };
const throttle = f => _.throttle(f, 50);
const onResolve = () => {c += 1};
const employees = createAsyncSelector({...params, onResolve, throttle}, state => state.text);
const onResolve = () => {
c += 1;
};
const employees = createAsyncSelector(
{ ...params, onResolve, throttle },
state => state.text
);
const expected = {
value: 'Search Text Too Long',
previous: ['Mark Metzger'],
const expected = {
value: "Search Text Too Long",
previous: ["Mark Metzger"],
isWaiting: false,
isResolved: false,
isRejected: true,
}
isRejected: true
};

@@ -467,3 +649,3 @@ employees(state);

setTimeout(() => {
state = {text: 'aaaaaaaaaaaaaa'};
state = { text: "aaaaaaaaaaaaaa" };
employees(state);

@@ -475,12 +657,10 @@ setTimeout(() => {

} catch (e) {
done.fail(e)
done.fail(e);
}
done();
}, 500);
},200)
}, 200);
});
function getAges(employees, maxAge) {
return new Promise((resolve, reject) => {

@@ -491,6 +671,6 @@ setTimeout(() => {

if (maxAge < 3) {
reject('too young');
reject("too young");
} else {
const employeeAges = employees.map(name => {
return ages[database.indexOf(name)]
return ages[database.indexOf(name)];
});

@@ -504,13 +684,13 @@ resolve(employeeAges.filter(age => age <= maxAge));

const params2 = {
sync:(employees, ages) => [],
async: getAges,
}
sync: (employees, ages) => [],
async: getAges
};
test('multiple params', () => {
test("multiple params", () => {
let c = 0;
let state = {employees: ['Mark Metzger'], maxAge: 40};
let state = { employees: ["Mark Metzger"], maxAge: 40 };
const ages = createAsyncSelector(params2, s => s.employees, s => s.maxAge);
const result = ages(state);
const expected = {
const expected = {
value: [],

@@ -520,15 +700,14 @@ previous: undefined,

isResolved: false,
isRejected: false,
}
isRejected: false
};
expect(deepEqual(result, expected)).toBe(true);
});
test('multiple params2', done => {
test("multiple params2", done => {
let c = 0;
let state = {employees: ['Mark Metzger'], maxAge: 40};
let state = { employees: ["Mark Metzger"], maxAge: 40 };
const ages = createAsyncSelector(params2, s => s.employees, s => s.maxAge);
const expected = {
const expected = {
value: [12],

@@ -538,5 +717,5 @@ previous: [12],

isResolved: true,
isRejected: false,
}
isRejected: false
};
ages(state);

@@ -548,3 +727,3 @@ setTimeout(() => {

} catch (e) {
done.fail(e)
done.fail(e);
}

@@ -555,9 +734,9 @@ done();

test('multiple params3', done => {
test("multiple params3", done => {
let c = 0;
let state = {employees: ['Mark Metzger'], maxAge: 10};
let state = { employees: ["Mark Metzger"], maxAge: 10 };
const ages = createAsyncSelector(params2, s => s.employees, s => s.maxAge);
const result = ages(state);
const expected = {
const expected = {
value: [],

@@ -567,4 +746,4 @@ previous: [],

isResolved: true,
isRejected: false,
}
isRejected: false
};

@@ -574,7 +753,7 @@ ages(state);

const result = ages(state);
try {
expect(deepEqual(result, expected)).toBe(true);
} catch (e) {
done.fail(e)
done.fail(e);
}

@@ -585,11 +764,14 @@ done();

test('debounced memoized', done => {
test("debounced memoized", done => {
let c = 0;
let state = {employees: ['Mark Metzger'], maxAge: 10};
const throttle = f => _.debounce(f, 100)
const onResolve = () => {c++}
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);
{ ...params2, throttle, onResolve },
s => s.employees,
s => s.maxAge
);

@@ -609,3 +791,3 @@ ages(state);

} catch (e) {
done.fail(e)
done.fail(e);
}

@@ -620,13 +802,15 @@ done();

test('debounced memoized 2', done => {
test("debounced memoized 2", done => {
let c = 0;
let state = {employees: ['Mark Metzger'], maxAge: 10};
const throttle = f => _.debounce(f, 100)
const onResolve = () => {c++}
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);
{ ...params2, throttle, onResolve },
s => s.employees,
s => s.maxAge
);
ages(state);

@@ -646,3 +830,3 @@ ages(state);

} catch (e) {
done.fail(e)
done.fail(e);
}

@@ -657,11 +841,14 @@ done();

test('debounced memoized 3', done => {
test("debounced memoized 3", done => {
let c = 0;
let state = {employees: ['Mark Metzger'], maxAge: 10};
const throttle = f => _.debounce(f, 100)
const onResolve = () => {c++}
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);
{ ...params2, throttle, onResolve },
s => s.employees,
s => s.maxAge
);

@@ -675,3 +862,3 @@ ages(state);

setTimeout(() => {
ages({employees: ['Mark Metzger'], maxAge: 9})
ages({ employees: ["Mark Metzger"], maxAge: 9 });
setTimeout(() => {

@@ -683,3 +870,3 @@ ages(state);

} catch (e) {
done.fail(e)
done.fail(e);
}

@@ -694,13 +881,16 @@ done();

test('debounced memoized 4', done => {
test("debounced memoized 4", done => {
let c = 0;
let state = {employees: ['Mark Metzger'], maxAge: 100};
const throttle = f => _.debounce(f, 100)
const onResolve = () => {c++}
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);
{ ...params2, throttle, onResolve },
s => s.employees,
s => s.maxAge
);
const expected = {
const expected = {
value: [],

@@ -710,4 +900,4 @@ previous: [],

isResolved: true,
isRejected: false,
}
isRejected: false
};

@@ -721,3 +911,3 @@ ages(state);

setTimeout(() => {
const n = {employees: ['Mark Metzger'], maxAge: 9};
const n = { employees: ["Mark Metzger"], maxAge: 9 };
ages(n);

@@ -731,3 +921,3 @@ setTimeout(() => {

} catch (e) {
done.fail(e)
done.fail(e);
}

@@ -742,14 +932,16 @@ done();

test('debounced memoized 5', done => {
test("debounced memoized 5", done => {
let c = 0;
let state = {employees: ['Mark Metzger'], maxAge: 10};
const throttle = f => _.debounce(f, 100)
const onResolve = () => {c++}
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);
{ ...params2, throttle, onResolve },
s => s.employees,
s => s.maxAge
);
const expected = {
const expected = {
value: [],

@@ -759,4 +951,4 @@ previous: [12],

isResolved: false,
isRejected: false,
}
isRejected: false
};

@@ -770,3 +962,3 @@ ages(state);

setTimeout(() => {
const n = {employees: ['Mark Metzger'], maxAge: 20};
const n = { employees: ["Mark Metzger"], maxAge: 20 };
ages(n);

@@ -776,7 +968,7 @@ setTimeout(() => {

setTimeout(() => {
const result = ages({employees: ['Mark Metzger'], maxAge: 20});
const result = ages({ employees: ["Mark Metzger"], maxAge: 20 });
try {
expect(deepEqual(expected, result)).toBe(true);
} catch (e) {
done.fail(e)
done.fail(e);
}

@@ -791,24 +983,30 @@ done();

test('cancel result', () => {
let state = {employees: ['Mark Metzger'], maxAge: 10};
let result = null
const onCancel = (promise, n, a) => {result=[n,a]}
test("cancel result", () => {
let state = { employees: ["Mark Metzger"], maxAge: 10 };
let result = null;
const onCancel = (promise, n, a) => {
result = [n, a];
};
const ages = createAsyncSelector(
{...params2, onCancel },
s => s.employees,
s => s.maxAge);
{ ...params2, onCancel },
s => s.employees,
s => s.maxAge
);
ages(state);
ages({employees: ['Mark Metzger'], maxAge: 11});
expect(deepEqual(result, [['Mark Metzger'], 10])).toBe(true);
ages({ employees: ["Mark Metzger"], maxAge: 11 });
expect(deepEqual(result, [["Mark Metzger"], 10])).toBe(true);
});
test('resolve result', done => {
let state = {employees: ['Mark Metzger'], maxAge: 10};
let result = null
const onResolve = (r, n, a) => {result=[r,n,a]}
test("resolve result", done => {
let state = { employees: ["Mark Metzger"], maxAge: 10 };
let result = null;
const onResolve = (r, n, a) => {
result = [r, n, a];
};
const ages = createAsyncSelector(
{...params2, onResolve },
s => s.employees,
s => s.maxAge);
{ ...params2, onResolve },
s => s.employees,
s => s.maxAge
);

@@ -819,5 +1017,5 @@ ages(state);

try {
expect(deepEqual(result, [[], ['Mark Metzger'], 10])).toBe(true);
expect(deepEqual(result, [[], ["Mark Metzger"], 10])).toBe(true);
} catch (e) {
done.fail(e)
done.fail(e);
}

@@ -828,10 +1026,13 @@ done();

test('reject result', done => {
let state = {employees: ['Mark Metzger'], maxAge: 1};
let result = null
const onReject = (r, n, a) => {result=[r,n,a]}
test("reject result", done => {
let state = { employees: ["Mark Metzger"], maxAge: 1 };
let result = null;
const onReject = (r, n, a) => {
result = [r, n, a];
};
const ages = createAsyncSelector(
{...params2, onReject },
s => s.employees,
s => s.maxAge);
{ ...params2, onReject },
s => s.employees,
s => s.maxAge
);

@@ -842,5 +1043,5 @@ ages(state);

try {
expect(deepEqual(result, ['too young', ['Mark Metzger'], 1])).toBe(true);
expect(deepEqual(result, ["too young", ["Mark Metzger"], 1])).toBe(true);
} catch (e) {
done.fail(e)
done.fail(e);
}

@@ -851,11 +1052,14 @@ done();

test('throttled and forced', done => {
let state = {employees: ['Mark Metzger'], maxAge: 1};
let result = null
test("throttled and forced", done => {
let state = { employees: ["Mark Metzger"], maxAge: 1 };
let result = null;
const throttle = f => _.debounce(f, 150);
const onCancel = (r, n, a) => {result=[r,n,a]}
const onCancel = (r, n, a) => {
result = [r, n, a];
};
const ages = createAsyncSelector(
{...params2, throttle, onCancel},
s => s.employees,
s => s.maxAge);
{ ...params2, throttle, onCancel },
s => s.employees,
s => s.maxAge
);

@@ -869,3 +1073,3 @@ ages(state);

} catch (e) {
done.fail(e)
done.fail(e);
}

@@ -876,17 +1080,17 @@ done();

test('Throw error', () => {
test("Throw error", () => {
try {
createAsyncSelector();
} catch (e) {
expect(e.message).toBe('An object of parameters must be passed in');
expect(e.message).toBe("An object of parameters must be passed in");
}
});
test('selector.getResult', done => {
test("selector.getResult", done => {
let c = 0;
let state = {employees: ['Mark Metzger'], maxAge: 10};
let state = { employees: ["Mark Metzger"], maxAge: 10 };
const ages = createAsyncSelector(params2, s => s.employees, s => s.maxAge);
const result = ages(state);
const expected = {
const expected = {
value: [],

@@ -896,12 +1100,11 @@ previous: [],

isResolved: true,
isRejected: false,
}
isRejected: false
};
ages(state);
setTimeout(() => {
try {
expect(deepEqual(ages.getResult(), expected)).toBe(true);
} catch (e) {
done.fail(e)
done.fail(e);
}

@@ -912,13 +1115,13 @@ done();

test('debounces only memoized version', done => {
let state = {employees: ['Mark Metzger'], maxAge: 15};
let result = null
test("debounces only memoized version", done => {
let state = { employees: ["Mark Metzger"], maxAge: 15 };
let result = null;
const throttle = f => _.debounce(f, 150);
const ages = createAsyncSelector(
{...params2, throttle},
s => s.employees,
s => s.maxAge);
{ ...params2, throttle },
s => s.employees,
s => s.maxAge
);
const expected1 = {
const expected1 = {
value: [],

@@ -928,6 +1131,6 @@ previous: undefined,

isResolved: false,
isRejected: false,
}
isRejected: false
};
const expected2 = {
const expected2 = {
value: [12],

@@ -937,4 +1140,4 @@ previous: [12],

isResolved: true,
isRejected: false,
}
isRejected: false
};

@@ -963,11 +1166,10 @@ const result1 = ages(state);

test("sync by default returns undefined", done => {
let state = { employees: ["Mark Metzger"], maxAge: 15 };
const ages = createAsyncSelector({ ...params2, sync: null }, [
s => s.employees,
s => s.maxAge
]);
test('sync by default returns undefined', done => {
let state = {employees: ['Mark Metzger'], maxAge: 15};
const ages = createAsyncSelector(
{...params2, sync: null},
[s => s.employees,
s => s.maxAge]);
const expected1 = {
const expected1 = {
value: undefined,

@@ -977,4 +1179,4 @@ previous: undefined,

isResolved: false,
isRejected: false,
}
isRejected: false
};

@@ -986,15 +1188,16 @@ const result = ages(state);

test('passed in state and props', done => {
let state = {employees: ['Mark Metzger'], maxAge: 15};
let props = { id: 'wow' };
test("passed in state and props", done => {
let state = { employees: ["Mark Metzger"], maxAge: 15 };
let props = { id: "wow" };
const ages = createAsyncSelector(
{
async: (concat) => new Promise((resolve) => {
resolve(concat + '!');
}),
},
[(s, props) => s.maxAge + props.id]);
async: concat =>
new Promise(resolve => {
resolve(concat + "!");
})
},
[(s, props) => s.maxAge + props.id]
);
const expected1 = {
const expected1 = {
value: undefined,

@@ -1004,12 +1207,12 @@ previous: undefined,

isResolved: false,
isRejected: false,
}
isRejected: false
};
const expected2 = {
value: '15wow!',
previous: '15wow!',
const expected2 = {
value: "15wow!",
previous: "15wow!",
isWaiting: false,
isResolved: true,
isRejected: false,
}
isRejected: false
};

@@ -1025,20 +1228,19 @@ const result = ages(state, props);

test('cancelling works as expected', done => {
let state = {employees: ['Mark Metzger'], maxAge: 15};
test("cancelling works as expected", done => {
let state = { employees: ["Mark Metzger"], maxAge: 15 };
let success = false;
const ages = createAsyncSelector(
{
async: (n) => {
const promise = new Promise((resolve) => {
async: n => {
const promise = new Promise(resolve => {
setTimeout(() => resolve(n), 50);
});
promise.id = 'abc';
promise.id = "abc";
return promise;
},
onCancel: (promise) => {
success = (promise.id === 'abc' && success === false);
onCancel: promise => {
success = promise.id === "abc" && success === false;
}
},
[(s) => s.employees]
},
[s => s.employees]
);

@@ -1055,3 +1257,2 @@

});
});
});
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
exports.createAsyncSelector = createAsyncSelector;
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
Object.defineProperty(exports, "__esModule", { value: true });
function validateParams(params) {
if ((typeof params === "undefined" ? "undefined" : _typeof(params)) !== "object" || params === null) {
throw new Error("An object of parameters must be passed in");
}
if (typeof params.async !== "function") {
throw new Error('Looking for a function called "async". This function returns a promise which handles asynchronous code');
}
if (typeof params !== "object" || params === null) {
throw new Error("An object of parameters must be passed in");
}
if (typeof params.async !== "function") {
throw new Error('Looking for a function called "async". This function returns a promise which handles asynchronous code');
}
}
function hasChanged(oldValues, newValues) {
if (oldValues === null) return true;
if (oldValues.length !== newValues.length) return true;
for (var i = 0; i < oldValues.length; i++) {
if (newValues[i] !== oldValues[i]) {
return true;
if (oldValues === null)
return true;
if (oldValues.length !== newValues.length)
return true;
for (var i = 0; i < oldValues.length; i++) {
if (newValues[i] !== oldValues[i]) {
return true;
}
}
}
return false;
return false;
}
function createResultObject(value, previous, isWaiting, isResolved, isRejected, omitStatus) {
if (omitStatus) return value;
return { value: value, previous: previous, isWaiting: isWaiting, isResolved: isResolved, isRejected: isRejected };
if (omitStatus)
return value;
return { value: value, previous: previous, isWaiting: isWaiting, isResolved: isResolved, isRejected: isRejected };
}
var emptyFunction = function emptyFunction() {};
var emptyFunction = function () { };
function createAsyncSelector(params) {
for (var _len = arguments.length, selectors = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
selectors[_key - 1] = arguments[_key];
}
validateParams(params);
// if they passed in an array
if (selectors.length === 1 && Array.isArray(selectors[0])) {
selectors = selectors[0];
}
// User inputs
var sync = params.sync,
async = params.async,
onReject = params.onReject,
onResolve = params.onResolve,
onCancel = params.onCancel,
shouldUseAsync = params.shouldUseAsync,
omitStatus = params.omitStatus,
throttle = params.throttle;
sync = typeof sync === "function" ? sync : emptyFunction;
onReject = typeof onReject === "function" ? onReject : emptyFunction;
onResolve = typeof onResolve === "function" ? onResolve : emptyFunction;
onCancel = typeof onCancel === "function" ? onCancel : emptyFunction;
shouldUseAsync = typeof shouldUseAsync === "function" ? shouldUseAsync : function () {
return true;
};
omitStatus = omitStatus === void 0 ? false : omitStatus;
throttle = typeof throttle === "function" ? throttle : null;
//selector state
var memoizedResult = null;
var isPromisePending = false;
var oldInputs = null;
var oldPromise = null;
var previousResolution = void 0;
var f = null;
var func = function func(state, props) {
var forceUpdate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var internal = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
var mapped = selectors.map(function (f) {
return f(state, props);
});
var changed = forceUpdate || hasChanged(oldInputs, mapped);
if (changed) {
/* Handle throttling / debouncing if required */
if (f !== null && internal === false) {
f(state, props, forceUpdate);
memoizedResult = createResultObject(sync.apply(undefined, _toConsumableArray(mapped)), previousResolution, true, false, false, omitStatus);
var selectors = [];
for (var _i = 1; _i < arguments.length; _i++) {
selectors[_i - 1] = arguments[_i];
}
validateParams(params);
// if they passed in an array
if (selectors.length === 1 && Array.isArray(selectors[0])) {
selectors = selectors[0];
}
// User inputs
var sync = params.sync, async = params.async, onReject = params.onReject, onResolve = params.onResolve, onCancel = params.onCancel, shouldUseAsync = params.shouldUseAsync, omitStatus = params.omitStatus, throttle = params.throttle;
sync = typeof sync === "function" ? sync : emptyFunction;
onReject = typeof onReject === "function" ? onReject : emptyFunction;
onResolve = typeof onResolve === "function" ? onResolve : emptyFunction;
onCancel = typeof onCancel === "function" ? onCancel : emptyFunction;
shouldUseAsync =
typeof shouldUseAsync === "function" ? shouldUseAsync : function () { return true; };
omitStatus = omitStatus === void 0 ? false : omitStatus;
throttle = typeof throttle === "function" ? throttle : null;
//selector state
var memoizedResult = null;
var isPromisePending = false;
var oldInputs = null;
var oldPromise = null;
var previousResolution = void 0;
var f = null;
var func = function (state, props, forceUpdate, internal) {
if (forceUpdate === void 0) { forceUpdate = false; }
if (internal === void 0) { internal = false; }
var mapped = selectors.map(function (f) { return f(state, props); });
var changed = forceUpdate || hasChanged(oldInputs, mapped);
if (changed) {
/* Handle throttling / debouncing if required */
if (f !== null && internal === false) {
f(state, props, forceUpdate);
memoizedResult = createResultObject(sync.apply(void 0, mapped), previousResolution, true, false, false, omitStatus);
return memoizedResult;
}
/* //////////////////////////////////////////// */
if (isPromisePending) {
onCancel.apply(void 0, [oldPromise].concat(oldInputs));
}
oldInputs = mapped;
memoizedResult = createResultObject(sync.apply(void 0, mapped), previousResolution, true, false, false, omitStatus);
if (!shouldUseAsync.apply(void 0, mapped)) {
return memoizedResult;
}
var promise = params.async.apply(params, mapped);
oldPromise = promise;
isPromisePending = true;
promise
.then(function (promiseResolution) {
if (!hasChanged(oldInputs, mapped)) {
previousResolution = promiseResolution;
isPromisePending = false;
memoizedResult = createResultObject(promiseResolution, previousResolution, false, true, false, omitStatus);
onResolve.apply(void 0, [promiseResolution].concat(mapped));
}
})
.catch(function (promiseRejection) {
if (!hasChanged(oldInputs, mapped)) {
isPromisePending = false;
memoizedResult = createResultObject(promiseRejection, previousResolution, false, false, true, omitStatus);
onReject.apply(void 0, [promiseRejection].concat(mapped));
}
});
}
// If the inputs didn't change, simply return the old memoized result
return memoizedResult;
}
/* //////////////////////////////////////////// */
if (isPromisePending) {
onCancel.apply(undefined, [oldPromise].concat(_toConsumableArray(oldInputs)));
}
oldInputs = mapped;
memoizedResult = createResultObject(sync.apply(undefined, _toConsumableArray(mapped)), previousResolution, true, false, false, omitStatus);
if (!shouldUseAsync.apply(undefined, _toConsumableArray(mapped))) {
return memoizedResult;
}
var promise = params.async.apply(params, _toConsumableArray(mapped));
oldPromise = promise;
isPromisePending = true;
promise.then(function (promiseResolution) {
if (!hasChanged(oldInputs, mapped)) {
previousResolution = promiseResolution;
isPromisePending = false;
memoizedResult = createResultObject(promiseResolution, previousResolution, false, true, false, omitStatus);
onResolve.apply(undefined, [promiseResolution].concat(_toConsumableArray(mapped)));
}
}).catch(function (promiseRejection) {
if (!hasChanged(oldInputs, mapped)) {
isPromisePending = false;
memoizedResult = createResultObject(promiseRejection, previousResolution, false, false, true, omitStatus);
onReject.apply(undefined, [promiseRejection].concat(_toConsumableArray(mapped)));
}
});
};
if (throttle !== null && f === null) {
var throttled_1 = throttle(function (state, props) {
return func(state, props, true, true);
});
var old_1 = null;
f = function (state, props) {
var New = selectors.map(function (s) { return s(state, props); });
if (hasChanged(old_1, New)) {
old_1 = New;
throttled_1(state, props);
}
};
}
// If the inputs didn't change, simply return the old memoized result
return memoizedResult;
};
if (throttle !== null && f === null) {
var throttled = throttle(function (state, props) {
return func(state, props, true, true);
});
var old = null;
f = function f(state, props) {
var New = selectors.map(function (s) {
return s(state, props);
});
if (hasChanged(old, New)) {
old = New;
throttled(state, props);
}
func.forceUpdate = function (state, props) {
return func(state, props, true, false);
};
}
func.forceUpdate = function (state, props) {
return func(state, props, true, false);
};
func.getResult = function () {
return memoizedResult;
};
return func;
func.getResult = function () { return memoizedResult; };
return func;
}
exports.default = createAsyncSelector;
exports.createAsyncSelector = createAsyncSelector;
exports.default = createAsyncSelector;
{
"name": "async-selector",
"version": "1.0.22",
"version": "1.0.23",
"description": "Select values from databases using asynchronous selectors.",
"main": "./dist/index.js",
"types": "./src/index.d.ts",
"types": "./dist/index.d.ts",
"homepage": "https://github.com/humflelump/inline-connect",
"scripts": {
"build": "./node_modules/.bin/babel -d dist src/",
"start": "tsc --watch",
"build": "tsc",
"test": "jest",
"test:coverage": "jest --coverage && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"prepublish": "npm run test && npm run build"
"prepublish": "npm run build && npm run test"
},

@@ -26,7 +27,8 @@ "repository": {

"license": "MIT",
"bugs": {
"url": "https://github.com/humflelump/async-selector/issues"
"babel": {
"presets": [
"es2015",
"react"
]
},
"homepage": "https://github.com/humflelump/async-selector#readme",
"dependencies": {},
"devDependencies": {

@@ -41,16 +43,3 @@ "babel-cli": "^6.24.1",

"underscore": "^1.9.0"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.js"
],
"coverageThreshold": {
"global": {
"statements": 90,
"branches": 90,
"functions": 90,
"lines": 90
}
}
}
}
A simple, lightweight library inspired by [reselect](https://github.com/reduxjs/reselect) which allows you select data using async functions. This package will normally be used in conjunction with redux and reselect but it has no dependencies.
# Installation
```

@@ -9,5 +10,7 @@ npm install --save async-selector

# What it solves
Simply put, it solves the same problem normal selectors solve. It allows you to treat actions as the _implementation details_ of state. For example, if you were designing a search dropdown, the incorrect approach would be calculate the search results whenever a specific action is dispatched, say "TYPE_SEARCH_TEXT". This would be a brittle approach because if another developer adds a different action, say "CLEAR_TEXT" or "UNDO", the app will no longer function correctly. The search calculation should be done inside a selector whether it is done synchronously on the client or asynchronously on the server.
On that note, here are some concrete examples of problems async-selector solves:
- **Elegantly removes the boilerplate associated making an API call.** The reducers, actions, state variables, and middleware associated with getting the request status and results are replaced with a single selector.

@@ -24,7 +27,8 @@ - **Save/Reload will work without any added code.** You need to make sure no stale data coming from an API or request statuses are persisted or you risk buggy behavior. Treating this as derived data instantly solves this problem.

# Example Usage
Here is a basic usage example:
```js
import createAsyncSelector from 'async-selector'
import { store } from './index' // get store somehow
import createAsyncSelector from "async-selector";
import { store } from "./index"; // get store somehow

@@ -35,22 +39,22 @@ // searchText might be a value in some textfield

// Your real request code goes here. This is just for example.
const getEmployees = (searchText) => {
const getEmployees = searchText => {
return new Promise((resolve, reject) => {
const database = ['Mark Metzger', 'Steve Miller'];
const database = ["Mark Metzger", "Steve Miller"];
setTimout(() => {
if (searchText.length > 50) {
reject('Search Text too long');
reject("Search Text too long");
} else {
resolve(database.filter(name => searchText.includes(name)));
}
}, 1000)
}, 1000);
});
}
};
const params = {
sync: (searchText) => [],
sync: searchText => [],
async: getEmployees,
onResolve: (employees, searchText) => console.log(employees),
onReject: (error, searchText) => console.log(error),
onCancel: (promise, searchText) => console.log(promise),
}
onCancel: (promise, searchText) => console.log(promise)
};

@@ -68,2 +72,3 @@ const employees = createAsyncSelector(params, [getSearchText]);

```
When you call the new selector there are three types of values it could return, depending on the status of the promise.

@@ -99,6 +104,9 @@

```
### Example of real usage
An important thing missing in the example above is a way to re-render the app with the new data when the promise resolves. Let's create a new action which, by convention, will do nothing other than trigger a re-render. You will need to have this new "RERENDER_APP" action handled in your reducer. How you choose to handle it is up to you, but it is important a reference to a new state object is returned so everything is correctly re-rendered. Here is an example of how you might handle it. It logs the current state of the async selector for debugging convenience.
```js
import { combineReducers, createStore } from 'redux'
import { combineReducers, createStore } from "redux";

@@ -109,7 +117,7 @@ const rootReducer = combineReducers({

AsyncSelectorLog: (state, action) => {
if (action.type === 'RERENDER_APP') {
if (action.type === "RERENDER_APP") {
return { ...state, [action.key]: action.value };
}
return state;
},
}
});

@@ -119,14 +127,15 @@

```
Now we need to make sure the onResolve and onReject callbacks actually dispatch that action. Duplicating the logic for dispatching this action for every selector is not ideal. For this reason, it is recommended you make an async-selector wrapper containing any logic you want all or most of your instances to share. For example, it could look like this.
```js
import createAsyncSelector from 'async-selector';
import { store } from './store'
import createAsyncSelector from "async-selector";
import { store } from "./store";
// You could optionally throttle re-render events here.
function rerender(data, id='n/a') {
function rerender(data, id = "n/a") {
store.dispatch({
type: 'RERENDER_APP',
type: "RERENDER_APP",
key: id,
value: data,
value: data
});

@@ -136,7 +145,7 @@ }

// You could optionally show an error popup to the user here
function rerenderError(error, id='n/a') {
function rerenderError(error, id = "n/a") {
store.dispatch({
type: 'RERENDER_APP',
type: "RERENDER_APP",
key: id,
value: String(error),
value: String(error)
});

@@ -146,32 +155,43 @@ }

export function createCustomAsyncSelector(params, ...selectors) {
return createAsyncSelector({
onResolve: result => rerender(result, params.id),
onReject: error => rerenderError(error, params.id),
...params,
}, ...selectors);
return createAsyncSelector(
{
onResolve: result => rerender(result, params.id),
onReject: error => rerenderError(error, params.id),
...params
},
...selectors
);
}
```
Now that all that is setup, here is how you can use your async selector in an actual redux app.
```js
import { createCustomAsyncSelector } from './custom-async-selector';
import { getSearchResults } from './search-api';
import { createCustomAsyncSelector } from "./custom-async-selector";
import { getSearchResults } from "./search-api";
const getSearchText = state => state.searchText;
const getSearchResponse = createCustomAsyncSelector({
async: getSearchResults,
id: 'getSearchResponse', // id is so we can easily look up the result in state
}, [getSearchText]);
const getSearchResponse = createCustomAsyncSelector(
{
async: getSearchResults,
id: "getSearchResponse" // id is so we can easily look up the result in state
},
[getSearchText]
);
export const searchResults = createSelector(
[getSearchResponse], d => d.isResolved ? d.value : []
[getSearchResponse],
d => (d.isResolved ? d.value : [])
);
export const isWaitingForSearchResults = createSelector(
[getSearchResponse], d => d.isWaiting
)
[getSearchResponse],
d => d.isWaiting
);
export const searchResultsErrorMessage = createSelector(
[getSearchResponse], d => d.isRejected ? String(d.value) : null
)
[getSearchResponse],
d => (d.isRejected ? String(d.value) : null)
);

@@ -181,4 +201,7 @@ // If creating 3 separate selectors for each of the 3 states seems boilerplate-y

```
### Chaining async calls
A very powerful feature of using selectors for async calls is the ability to elegantly create a dependency graph just like you would with normal selectors. The only issue is that the selector may be in the "isWaiting" state and the value is useless. This is where the "shouldUseAsync" function is useful. With it, you can make the selector not make async calls unless the inputs are valid.
```js

@@ -191,22 +214,30 @@ // ....

onResolve: (ages, employees) => store.dispatch(triggerRerender()),
shouldUseAsync: (employees) => employees.isResolved === true,
}
shouldUseAsync: employees => employees.isResolved === true
};
const employeeAges = createAsyncSelector(params2, [employees]);
console.log('Ages:', employeeAges(store.getState()))
console.log("Ages:", employeeAges(store.getState()));
```
### 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 { debounce } from 'lodash';
import { debounce } from "lodash";
const params = {
sync: (searchText) => [],
sync: searchText => [],
async: getEmployees,
throttle: f => debounce(f, 250),
}
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).
### Custom caching
A common issue is the need to cache API calls. For example, if you are looking at last month's stock market data, then zoom to today's data, then zoom back to last month's data, you shouldn't have to query for a month's worth of data again. To handle this, simply put any custom caching logic in the async function you pass in. As long as the function returns a promise that resolves the same value given the same inputs, it is perfectly fine to do this. This caching logic can be as simple or complex as needed. A slight flaw in this approach is that it may not be desirable to dispatch an extra "RERENDER" action when the data already exists in memory. See the Recipes section for a solution to this.
### Handling stateful APIs
Generally, a basic assumption of a selector is the function is pure - the inputs fully determine the output. Unfortunately, that is an assumption can't always be made when querying a database. For example, you might have a button that allows the user to refresh the data if the user is worried the data was changed. Fortunately, this isn't actually a big issue thanks to the "forceUpdate" parameter!

@@ -216,3 +247,3 @@

function buttonClicked() {
const result = employeeAges.forceUpdate(store.getState()) // the selector will create a new promise regardless of whether the inputs changed. It will always return an object in the "isWaiting" state.
const result = employeeAges.forceUpdate(store.getState()); // the selector will create a new promise regardless of whether the inputs changed. It will always return an object in the "isWaiting" state.
}

@@ -224,6 +255,8 @@ ```

### Avoiding over-rendering
A useful feature of using an async selector is that every time a new set of inputs are used, it immediately returns a default value. This avoids the dangerous bug of having data show up that looks correct while the promise is waiting to be resolved. However, in some cases flipping between a default value and a resolved value is undesirable. For example, if the user is typing and the search suggestions are constantly appearing and disappearing, it could be jarring. The simple solution is to use ".previous" instead ".value". "previous" is initially undefined, but otherwise it is the result of the most recent promise resolution.
```js
const searchResults = createAsyncSelector(params, searchText);
console.log('results:', searchResults(store.getState()).previous || [])
console.log("results:", searchResults(store.getState()).previous || []);
// previous === value when isResolved is true.

@@ -233,11 +266,15 @@ ```

### Usage across multiple instances of a component
Just like in reselect, an async selector can take in two variables (for example, global state and component props). Reselect has examples of this [here](https://github.com/reduxjs/reselect#selectorstodoselectorsjs-1).
# Documentation
createAsyncSelector takes in two arguments:
createAsyncSelector takes in two arguments:
```js
function createAsyncSelector(params: Object, ...selectors) -> func
```
It outputs an object with the following form:
```

@@ -252,4 +289,7 @@ {

```
## selectors
Each selector is a function that takes in state (and optionally a second variable) 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 call ".forceUpdate(state)" of state which will automatically create a new promise and return an object in the "isWaiting" state.
```js

@@ -259,68 +299,98 @@ function selector(state, ?props) -> any

```
## params
params is an object
### params.sync (Optional)
```js
function sync(...selectorResults) -> any
```
sync is a function that takes as input all the selectors evaluated using state. It is usually used to simply return some default value like an empty list.
### params.async (Required)
```js
function async(...selectorResults) -> promise
```
async is a function that takes as input all the selectors evaluated using state and returns a promise. The promise should always either resolve or reject.
### params.onResolve
```js
function onResolve(resolvedValue, ...selectorResults) -> void
```
This is a callback function that takes in theresolved value of the promise and the selector results
### params.onReject
```js
function onReject(rejectedValue, ...selectorResults) -> void
```
This is a callback function that takes in the rejected value of the promise and the selector results
### params.onCancel
```js
function onCancel(cancelledPromise, ...selectorResults) -> void
```
This is a callback function that takes in the cancelled promise and the selector results. A promise is cancelled when a new promise is created before the old one is resolved or rejected.
### params.shouldUseAsync
```js
function shouldUseAsync(...selectorResults) -> bool
```
This function can be used to stop the creation of a new promise if the inputs are invalid. It takes in the selectorResults and should output true or false
### params.omitStatus
```js
omitStatus: bool
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.
# Recipes
Here are some patterns to can use to solve common problems
### Debouncing/Throttling reselect selectors
A very cool thing is the ability to throttle computationally expensive selectors. Here's a generic function that accomplishes the task
```js
import createAsyncSelector from 'async-selector';
import { createSelector } from 'reselect';
import { store, triggerRerender } from './somewhere';
import createAsyncSelector from "async-selector";
import { createSelector } from "reselect";
import { store, triggerRerender } from "./somewhere";
export function throttleSelector(selector, throttleFunc) {
const asyncSelector = createAsyncSelector({
async: val => new Promise(res => res(val)),
throttle: throttleFunc,
onResolve: () => store.dispatch(triggerRerender()),
}, [selector]);
const asyncSelector = createAsyncSelector(
{
async: val => new Promise(res => res(val)),
throttle: throttleFunc,
onResolve: () => store.dispatch(triggerRerender())
},
[selector]
);
return createSelector(
[asyncSelector, selector],
(resp, val) => {
return resp.isResolved
? resp.value
: (resp.previous || val);
return resp.isResolved ? resp.value : resp.previous || val;
}

@@ -330,4 +400,7 @@ );

```
### Handling custom caching
To handle custom caching, you should simply handle the caching logic in your async function. A problem you may have is that even if the data is in memory, a promise will need to be resolved and re-render action will be dispatched. In most cases, this is such a small issue that it isn't worth worrying about. But you can avoid this extra re-render action by following this pattern.
```js

@@ -376,14 +449,17 @@ import api from './api'

```
### Batching rerender events
A useful trick is to be able throttle re-render events for performance reasons. This can be safely done without risk of the app behaving incorrectly (but will be generally slightly slower). This is very simple to do in your async selector wrapper.
```js
import createAsyncSelector from 'async-selector';
import { store } from './store'
import { throttle } from 'lodash';
import createAsyncSelector from "async-selector";
import { store } from "./store";
import { throttle } from "lodash";
const rerender = throttle((data, id='n/a') => {
const rerender = throttle((data, id = "n/a") => {
store.dispatch({
type: 'RERENDER_APP',
type: "RERENDER_APP",
key: id,
value: data,
value: data
});

@@ -393,10 +469,16 @@ }, 100);

export function createCustomAsyncSelector(params, ...selectors) {
return createAsyncSelector({
onResolve: result => rerender(result, params.id),
...params,
}, ...selectors);
return createAsyncSelector(
{
onResolve: result => rerender(result, params.id),
...params
},
...selectors
);
}
```
### Handling boilerplate
A common thing you want to do is render a loading message or an error message depending on the status of the request. You could simply pass the results of the async selector directly into the component. This may not be ideal because it tightly couples the component to the async-selector package. The other option is to create 3 selectors (results, isWaiting, errorMessage) for every async selector. This is better but can result in a lot of boilerplate. We can make a helper function to reduce this boilerplate.
```js

@@ -403,0 +485,0 @@ import createAsyncSelector from 'async-selector';

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc