UNPKG

6.69 kBJavaScriptView Raw
1import { action, extendObservable } from "mobx";
2import { invariant } from "./utils";
3export var PENDING = "pending";
4export var FULFILLED = "fulfilled";
5export var REJECTED = "rejected";
6function caseImpl(handlers) {
7 switch (this.state) {
8 case PENDING:
9 return handlers.pending && handlers.pending(this.value);
10 case REJECTED:
11 return handlers.rejected && handlers.rejected(this.value);
12 case FULFILLED:
13 return handlers.fulfilled ? handlers.fulfilled(this.value) : this.value;
14 }
15}
16/**
17 * `fromPromise` takes a Promise, extends it with 2 observable properties that track
18 * the status of the promise and returns it. The returned object has the following observable properties:
19 * - `value`: either the initial value, the value the Promise resolved to, or the value the Promise was rejected with. use `.state` if you need to be able to tell the difference.
20 * - `state`: one of `"pending"`, `"fulfilled"` or `"rejected"`
21 *
22 * And the following methods:
23 * - `case({fulfilled, rejected, pending})`: maps over the result using the provided handlers, or returns `undefined` if a handler isn't available for the current promise state.
24 *
25 * The returned object implements `PromiseLike<TValue>`, so you can chain additional `Promise` handlers using `then`. You may also use it with `await` in `async` functions.
26 *
27 * Note that the status strings are available as constants:
28 * `mobxUtils.PENDING`, `mobxUtils.REJECTED`, `mobxUtil.FULFILLED`
29 *
30 * fromPromise takes an optional second argument, a previously created `fromPromise` based observable.
31 * This is useful to replace one promise based observable with another, without going back to an intermediate
32 * "pending" promise state while fetching data. For example:
33 *
34 * @example
35 * \@observer
36 * class SearchResults extends React.Component {
37 * \@observable.ref searchResults
38 *
39 * componentDidUpdate(nextProps) {
40 * if (nextProps.query !== this.props.query)
41 * this.searchResults = fromPromise(
42 * window.fetch("/search?q=" + nextProps.query),
43 * // by passing, we won't render a pending state if we had a successful search query before
44 * // rather, we will keep showing the previous search results, until the new promise resolves (or rejects)
45 * this.searchResults
46 * )
47 * }
48 *
49 * render() {
50 * return this.searchResults.case({
51 * pending: (staleValue) => {
52 * return staleValue || "searching" // <- value might set to previous results while the promise is still pending
53 * },
54 * fulfilled: (value) => {
55 * return value // the fresh results
56 * },
57 * rejected: (error) => {
58 * return "Oops: " + error
59 * }
60 * })
61 * }
62 * }
63 *
64 * Observable promises can be created immediately in a certain state using
65 * `fromPromise.reject(reason)` or `fromPromise.resolve(value?)`.
66 * The main advantage of `fromPromise.resolve(value)` over `fromPromise(Promise.resolve(value))` is that the first _synchronously_ starts in the desired state.
67 *
68 * It is possible to directly create a promise using a resolve, reject function:
69 * `fromPromise((resolve, reject) => setTimeout(() => resolve(true), 1000))`
70 *
71 * @example
72 * const fetchResult = fromPromise(fetch("http://someurl"))
73 *
74 * // combine with when..
75 * when(
76 * () => fetchResult.state !== "pending",
77 * () => {
78 * console.log("Got ", fetchResult.value)
79 * }
80 * )
81 *
82 * // or a mobx-react component..
83 * const myComponent = observer(({ fetchResult }) => {
84 * switch(fetchResult.state) {
85 * case "pending": return <div>Loading...</div>
86 * case "rejected": return <div>Ooops... {fetchResult.value}</div>
87 * case "fulfilled": return <div>Gotcha: {fetchResult.value}</div>
88 * }
89 * })
90 *
91 * // or using the case method instead of switch:
92 *
93 * const myComponent = observer(({ fetchResult }) =>
94 * fetchResult.case({
95 * pending: () => <div>Loading...</div>,
96 * rejected: error => <div>Ooops.. {error}</div>,
97 * fulfilled: value => <div>Gotcha: {value}</div>,
98 * }))
99 *
100 * // chain additional handler(s) to the resolve/reject:
101 *
102 * fetchResult.then(
103 * (result) => doSomeTransformation(result),
104 * (rejectReason) => console.error('fetchResult was rejected, reason: ' + rejectReason)
105 * ).then(
106 * (transformedResult) => console.log('transformed fetchResult: ' + transformedResult)
107 * )
108 *
109 * @param origPromise The promise which will be observed
110 * @param oldPromise The previously observed promise
111 * @returns origPromise with added properties and methods described above.
112 */
113export function fromPromise(origPromise, oldPromise) {
114 invariant(arguments.length <= 2, "fromPromise expects up to two arguments");
115 invariant(typeof origPromise === "function" ||
116 (typeof origPromise === "object" &&
117 origPromise &&
118 typeof origPromise.then === "function"), "Please pass a promise or function to fromPromise");
119 if (origPromise.isPromiseBasedObservable === true)
120 return origPromise;
121 if (typeof origPromise === "function") {
122 // If it is a (reject, resolve function, wrap it)
123 origPromise = new Promise(origPromise);
124 }
125 var promise = origPromise;
126 origPromise.then(action("observableFromPromise-resolve", function (value) {
127 promise.value = value;
128 promise.state = FULFILLED;
129 }), action("observableFromPromise-reject", function (reason) {
130 promise.value = reason;
131 promise.state = REJECTED;
132 }));
133 promise.isPromiseBasedObservable = true;
134 promise.case = caseImpl;
135 var oldData = oldPromise && (oldPromise.state === FULFILLED || oldPromise.state === PENDING)
136 ? oldPromise.value
137 : undefined;
138 extendObservable(promise, {
139 value: oldData,
140 state: PENDING,
141 }, {}, { deep: false });
142 return promise;
143}
144(function (fromPromise) {
145 fromPromise.reject = action("fromPromise.reject", function (reason) {
146 var p = fromPromise(Promise.reject(reason));
147 p.state = REJECTED;
148 p.value = reason;
149 return p;
150 });
151 function resolveBase(value) {
152 if (value === void 0) { value = undefined; }
153 var p = fromPromise(Promise.resolve(value));
154 p.state = FULFILLED;
155 p.value = value;
156 return p;
157 }
158 fromPromise.resolve = action("fromPromise.resolve", resolveBase);
159})(fromPromise || (fromPromise = {}));
160/**
161 * Returns true if the provided value is a promise-based observable.
162 * @param value any
163 * @returns {boolean}
164 */
165export function isPromiseBasedObservable(value) {
166 return value && value.isPromiseBasedObservable === true;
167}