ts-remote-data
Version:
Utility type for working with snapshot views of asynchronously-available data
180 lines (179 loc) • 6.59 kB
JavaScript
;
// COPYRIGHT 2019 BY EXTRAHOP NETWORKS, INC.
//
// This file is subject to the terms and conditions defined in
// file 'LICENSE', which is part of this source code package.
Object.defineProperty(exports, "__esModule", { value: true });
// Establish constants for the different not-loaded states. Explicit types are
// used so these can be exported via `RemoteData` without being generalized
// to `string`.
var NOT_ASKED = 'RemoteData::NOT_ASKED';
var LOADING = 'RemoteData::LOADING';
var FAILURE_PROPERTY = 'RemoteData::FAILURE';
var RemoteData = {
/**
* Initial state for remote data. The client has not yet sent a request
* to the server.
*/
NOT_ASKED: NOT_ASKED,
/**
* State when the client has started a request but not yet received a
* response.
*/
LOADING: LOADING,
/**
* Create a failure object with no associated data.
*/
fail: function () { return RemoteData.failWith(undefined); },
/**
* State when the client has received a conclusive response from the server
* that was an error. If a non-initial update fails, it is not required
* that consumers of `RemoteData` overwrite the previous data with the
* failure, but they may do so if it is contextually appropriate.
*/
failWith: function (error) {
var _a;
return (_a = {},
_a[FAILURE_PROPERTY] = true,
_a.error = error,
_a);
},
/**
* State when the client has received a conclusive response from the server
* that was an error. If a non-initial update fails, it is not required
* that consumers of `RemoteData` overwrite the previous data with the
* failure, but they may do so if it is contextually appropriate.
*/
isFailure: function (rd) {
return typeof rd === 'object' &&
rd !== null &&
Object.prototype.hasOwnProperty.bind(rd, FAILURE_PROPERTY)();
},
/**
* Check if some remote data is available. This function acts as a type
* guard; if it returns `true` then `remoteData` can now be used as a `T`.
*/
isReady: function (remoteData) {
return RemoteData.isSettled(remoteData) && !RemoteData.isFailure(remoteData);
},
/**
* Check if some remote data is ready or in the failure state. The term
* "settled" comes from Promises, where it means "fulfilled or rejected".
* This function acts as a type guard.
*/
isSettled: function (remoteData) {
return remoteData !== NOT_ASKED && remoteData !== LOADING;
},
/**
* Check if some remote data is ready; if so return it, otherwise return
* `undefined`. This can be used with `||` to create a short-circuiting
* default.
*/
asReady: function (remoteData) {
return RemoteData.isReady(remoteData) ? remoteData : undefined;
},
/**
* Check if some remote data has failed; if so, return the failure,
* otherwise return `undefined`.
*/
asFailure: function (rd) {
return RemoteData.isFailure(rd) ? rd : undefined;
},
/**
* Check if some remote data has settled; if so return the value or the
* failure. The term "settled" come from Promises, where it means
* "fulfilled or rejected".
*/
asSettled: function (rd) {
return RemoteData.isSettled(rd) ? rd : undefined;
},
/**
* Get the value of some remote data if available, otherwise return a
* specified fallback value.
*/
getOr: function (remoteData, fallback) {
return RemoteData.isReady(remoteData) ? remoteData : fallback;
},
/**
* Get the value of some remote data if available, otherwise throw an
* exception.
*
* # Usage
* When the caller is certain that code is only reachable after some
* remote data is available, the value can be used by writing
* `RemoteData.unwrap(rd).someRdMethod();`
*
* @throws `Error` if `remoteData` is not in the ready state.
*/
unwrap: function (remoteData) {
if (RemoteData.isReady(remoteData))
return remoteData;
throw new Error("Attempted to unwrap " + remoteData);
},
/**
* Return the first `RemoteData` in the 'ready' state.
* This is used to kick off an initial data request but to avoid flattening
* data on subsequent updates, or to restart a query after a loading failure.
*/
or: function (lhs, rhs) {
return RemoteData.isReady(lhs) ? lhs : rhs;
},
/**
* Apply a transform function to a remote data if it is ready, otherwise
* return the current state.
*/
map: function (remoteData, mapFn) {
return RemoteData.isReady(remoteData) ? mapFn(remoteData) : remoteData;
},
/**
* Recover from a failure by using a fallback value, otherwise
* returning the passed in remote data.
*/
recover: function (remoteData, fallback) {
return RemoteData.isFailure(remoteData) ? fallback : remoteData;
},
/**
* Checks if all the input `RemoteData` are ready, and if so
* returns an array of the values. Otherwise, it returns a single
* status, in the following priority order:
*
* 1. Failure
* 2. `RemoteData.LOADING`
* 3. `RemoteData.NOT_ASKED`
*/
all: all,
};
/**
* Checks if all the input `RemoteData` are ready, and if so
* returns an array of the values. Otherwise, it returns a single
* status, in the following priority order:
*
* 1. Failure
* 2. `RemoteData.LOADING`
* 3. `RemoteData.NOT_ASKED`
*
* @param args An array or tuple of `RemoteData` values.
*/
// XXX This is not an arrow function because of the overloads.
function all() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
// If everything is ready, then return the array
if (args.every(RemoteData.isReady))
return args;
// If _any_ of the args are in the `failure` state, the whole thing
// is a failure.
var firstFailure = args.find(RemoteData.isFailure);
if (firstFailure)
return firstFailure;
// If _any_ of the args are in the `LOADING` state and _none_ of the
// args are failures, then the whole thing is `LOADING`.
if (args.includes(RemoteData.LOADING))
return RemoteData.LOADING;
// We have at least one non-ready item, and no items in failure or loading
// states. Therefore, we must not have asked for any of the items yet.
return RemoteData.NOT_ASKED;
}
exports.default = RemoteData;