Skip to content

Commit

Permalink
Added waitForAllSettled helper
Browse files Browse the repository at this point in the history
Summary: There are scenarios where you want to wait for all underlying RecoilValues to settle to either an Error or Value state. waitForAllSettled does exactly that. If any of the underlying loadables are in a loading state, then it will be as well.

Reviewed By: drarmstr

Differential Revision: D25656496

fbshipit-source-id: 5381bd31416bdea3b04b374b8b59d30d46defe14
  • Loading branch information
habond authored and facebook-github-bot committed Jan 29, 2021
1 parent 0f08542 commit 4c95591
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 25 deletions.
2 changes: 2 additions & 0 deletions src/Recoil_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const selectorFamily = require('./recoil_values/Recoil_selectorFamily');
const {
noWait,
waitForAll,
waitForAllSettled,
waitForAny,
waitForNone,
} = require('./recoil_values/Recoil_WaitFor');
Expand Down Expand Up @@ -122,6 +123,7 @@ module.exports = {
waitForNone,
waitForAny,
waitForAll,
waitForAllSettled,

// Other functions
isRecoilValue,
Expand Down
85 changes: 78 additions & 7 deletions src/recoil_values/Recoil_WaitFor.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ const selectorFamily = require('./Recoil_selectorFamily');
/////////////////
// TRUTH TABLE
/////////////////
// Dependencies waitForNone waitForAny waitForAll
// [loading, loading] [Promise, Promise] Promise Promise
// [value, loading] [value, Promise] [value, Promise] Promise
// [value, value] [value, value] [value, value] [value, value]
// Dependencies waitForNone waitForAny waitForAll waitForAllSettled
// [loading, loading] [Promise, Promise] Promise Promise Promise
// [value, loading] [value, Promise] [value, Promise] Promise Promise
// [value, value] [value, value] [value, value] [value, value] [value, value]
//
// [error, loading] [Error, Promise] [Error, Promise] Error
// [error, error] [Error, Error] [Error, Error] Error
// [value, error] [value, Error] [value, Error] Error
// [error, loading] [Error, Promise] [Error, Promise] Error Promise
// [error, error] [Error, Error] [Error, Error] Error [error, error]
// [value, error] [value, Error] [value, Error] Error [value, error]

// Issue parallel requests for all dependencies and return the current
// status if they have results, have some error, or are still pending.
Expand Down Expand Up @@ -274,6 +274,76 @@ const waitForAll: <
},
});

const waitForAllSettled: <
RecoilValues:
| $ReadOnlyArray<RecoilValueReadOnly<mixed>>
| $ReadOnly<{[string]: RecoilValueReadOnly<mixed>, ...}>,
>(
RecoilValues,
) => RecoilValueReadOnly<
$ReadOnlyArray<mixed> | $ReadOnly<{[string]: mixed, ...}>,
> = selectorFamily({
key: '__waitForAllSettled',
get: (
dependencies:
| $ReadOnly<{[string]: RecoilValueReadOnly<mixed>}>
| $ReadOnlyArray<RecoilValueReadOnly<mixed>>,
) => ({get}) => {
// Issue requests for all dependencies in parallel.
// Exceptions can either be Promises of pending results or real errors
const deps = unwrapDependencies(dependencies);
const [results, exceptions] = concurrentRequests(get, deps);

// If all results are available, return the results
if (exceptions.every(exp => !isPromise(exp))) {
return wrapLoadables(dependencies, results, exceptions);
}

// Wait for all results to settle
if (gkx('recoil_async_selector_refactor')) {
return (
Promise.all(
exceptions.map((exp, i) =>
isPromise(exp)
? exp
.then(result => {
results[i] = getValueFromLoadablePromiseResult(result);
exceptions[i] = undefined;
})
.catch(error => {
results[i] = undefined;
exceptions[i] = error;
})
: null,
),
)
// Then wrap them as loadables
.then(() => wrapLoadables(dependencies, results, exceptions))
);
} else {
throw (
Promise.all(
exceptions.map((exp, i) =>
isPromise(exp)
? exp
.then(result => {
results[i] = getValueFromLoadablePromiseResult(result);
exceptions[i] = undefined;
})
.catch(error => {
results[i] = undefined;
exceptions[i] = error;
})
: null,
),
)
// Then wrap them as loadables
.then(() => wrapLoadables(dependencies, results, exceptions))
);
}
},
});

const noWait: (
RecoilValue<mixed>,
) => RecoilValueReadOnly<Loadable<mixed>> = selectorFamily({
Expand All @@ -293,5 +363,6 @@ module.exports = {
waitForNone,
waitForAny,
waitForAll,
waitForAllSettled,
noWait,
};
12 changes: 12 additions & 0 deletions src/recoil_values/Recoil_WaitFor.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ declare function waitForAll<
RecoilValues,
): RecoilValueReadOnly<UnwrapObjRecoilValues<RecoilValues>>;

declare function waitForAllSettled<
RecoilValues: $ReadOnlyArray<RecoilValue<any>>, // flowlint-line unclear-type:off
>(
RecoilValues,
): RecoilValueReadOnly<UnwrapArrayRecoilValueLoadables<RecoilValues>>;
declare function waitForAllSettled<
RecoilValues: $ReadOnly<{[string]: RecoilValue<any>, ...}>, // flowlint-line unclear-type:off
>(
RecoilValues,
): RecoilValueReadOnly<UnwrapObjRecoilValueLoadables<RecoilValues>>;

/* eslint-enable no-redeclare */

declare function noWait<T>(RecoilValue<T>): RecoilValueReadOnly<Loadable<T>>;
Expand All @@ -73,5 +84,6 @@ module.exports = {
waitForNone,
waitForAny,
waitForAll,
waitForAllSettled,
noWait,
};
41 changes: 40 additions & 1 deletion src/recoil_values/__flowtests__/Recoil_WaitFor-flowtest.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ import type {RecoilState} from '../../core/Recoil_RecoilValue';
const {useRecoilValue} = require('../../hooks/Recoil_Hooks');
const atom = require('../Recoil_atom');
const readOnlySelector = require('../Recoil_readOnlySelector');
const {noWait, waitForAll, waitForNone} = require('../Recoil_WaitFor');
const {
noWait,
waitForAll,
waitForAllSettled,
waitForNone,
} = require('../Recoil_WaitFor');

const numberAtom: RecoilState<number> = atom({key: 'number', default: 0});
const stringAtom: RecoilState<string> = atom({key: 'string', default: ''});
Expand Down Expand Up @@ -86,6 +91,40 @@ num = objResultsNone.str.valueOrThrow();
// $FlowExpectedError
str = objResultsNone.num.valueOrThrow();

//////////////
// waitForAllSettled
//////////////

// Test tuple unwrapping of types
// eslint-disable-next-line fb-www/react-hooks
const arrayResultsAllSettled = useRecoilValue(
waitForAllSettled([
// $FlowExpectedError
readOnlySelector(numberAtom),
// $FlowExpectedError
readOnlySelector(stringAtom),
]),
);
num = arrayResultsAllSettled[0].valueOrThrow();
str = arrayResultsAllSettled[1].valueOrThrow();
// $FlowExpectedError
num = arrayResultsAllSettled[1].valueOrThrow();
// $FlowExpectedError
str = arrayResultsAllSettled[0].valueOrThrow();

// Test object unwrapping of types
// eslint-disable-next-line fb-www/react-hooks
const objResultsAllSettled = useRecoilValue(
// $FlowExpectedError
waitForAllSettled({num: numberAtom, str: stringAtom}),
);
num = objResultsAllSettled.num.valueOrThrow();
str = objResultsAllSettled.str.valueOrThrow();
// $FlowExpectedError
num = objResultsAllSettled.str.valueOrThrow();
// $FlowExpectedError
str = objResultsAllSettled.num.valueOrThrow();

//////////////
// noWait
//////////////
Expand Down

0 comments on commit 4c95591

Please sign in to comment.