From 4bd65be88b9962bc8cc03e1dc1de33e33239524f Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Sat, 28 Sep 2019 14:14:30 +0200 Subject: [PATCH 1/2] Make sure the promise render prop is always a Promise. --- packages/react-async/src/Async.js | 10 ++++++++-- packages/react-async/src/reducer.js | 4 +++- packages/react-async/src/useAsync.js | 10 ++++++++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/react-async/src/Async.js b/packages/react-async/src/Async.js index 8a5733e3..86dea2e9 100644 --- a/packages/react-async/src/Async.js +++ b/packages/react-async/src/Async.js @@ -3,7 +3,13 @@ import React from "react" import globalScope from "./globalScope" import { IfInitial, IfPending, IfFulfilled, IfRejected, IfSettled } from "./helpers" import propTypes from "./propTypes" -import { actionTypes, init, dispatchMiddleware, reducer as asyncReducer } from "./reducer" +import { + neverSettle, + actionTypes, + init, + dispatchMiddleware, + reducer as asyncReducer, +} from "./reducer" /** * createInstance allows you to create instances of Async that are bound to a specific promise. @@ -32,7 +38,7 @@ export const createInstance = (defaultProps = {}, displayName = "Async") => { this.mounted = false this.counter = 0 this.args = [] - this.promise = undefined + this.promise = neverSettle this.abortController = { abort: () => {} } this.state = { ...init({ initialValue, promise, promiseFn }), diff --git a/packages/react-async/src/reducer.js b/packages/react-async/src/reducer.js index 27bf65a2..fa883969 100644 --- a/packages/react-async/src/reducer.js +++ b/packages/react-async/src/reducer.js @@ -1,5 +1,7 @@ import { getInitialStatus, getIdleStatus, getStatusProps, statusTypes } from "./status" +export const neverSettle = new Promise(() => {}) + export const actionTypes = { start: "start", cancel: "cancel", @@ -16,7 +18,7 @@ export const init = ({ initialValue, promise, promiseFn }) => ({ finishedAt: initialValue ? new Date() : undefined, ...getStatusProps(getInitialStatus(initialValue, promise || promiseFn)), counter: 0, - promise: undefined, + promise: neverSettle, }) export const reducer = (state, { type, payload, meta }) => { diff --git a/packages/react-async/src/useAsync.js b/packages/react-async/src/useAsync.js index 1490a6b8..ff5756d2 100644 --- a/packages/react-async/src/useAsync.js +++ b/packages/react-async/src/useAsync.js @@ -1,7 +1,13 @@ import { useCallback, useDebugValue, useEffect, useMemo, useRef, useReducer } from "react" import globalScope from "./globalScope" -import { actionTypes, init, dispatchMiddleware, reducer as asyncReducer } from "./reducer" +import { + neverSettle, + actionTypes, + init, + dispatchMiddleware, + reducer as asyncReducer, +} from "./reducer" const noop = () => {} @@ -12,7 +18,7 @@ const useAsync = (arg1, arg2) => { const isMounted = useRef(true) const lastArgs = useRef(undefined) const lastOptions = useRef(undefined) - const lastPromise = useRef(undefined) + const lastPromise = useRef(neverSettle) const abortController = useRef({ abort: noop }) const { devToolsDispatcher } = globalScope.__REACT_ASYNC__ From 6b6b67dade45df205f7fa8b0347638288a602730 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Sat, 28 Sep 2019 14:28:47 +0200 Subject: [PATCH 2/2] Add a warning about providing a rejection handler. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a3b142ea..2f794ee1 100644 --- a/README.md +++ b/README.md @@ -686,6 +686,9 @@ A reference to the internal wrapper promise created when starting a new promise `run` / `reload`). It fulfills or rejects along with the provided `promise` / `promiseFn` / `deferFn`. Useful as a chainable alternative to the `onResolve` / `onReject` callbacks. +Warning! If you chain on `promise`, you MUST provide a rejection handler (e.g. `.catch(...)`). Otherwise React will +throw an exception and crash if the promise rejects. + #### `run` > `function(...args: any[]): void`