In order to prevent phantom updates to an unmounted React ponent, React tells you to cancel any pending promises on a ponent (such as promises for fetching additional data) when it unmounts. This is very easy to acplish with Bluebird promises, which have a .cancel()
method on them that causes the .then()
and .catch()
handlers to never respond.
However, ES6 Promises do not support cancellation. In addition, ES7's async
and await
only use native Promises and do not support any drop-in replacements (such as Bluebird). This means that if you want to be able to cancel Promises in React, as they tell you to do, you have to use .then()
and .catch()
and also have to place a middleman on native Promise methods like fetch()
so that it can be cancelled.
Is this really what React expects?
In order to prevent phantom updates to an unmounted React ponent, React tells you to cancel any pending promises on a ponent (such as promises for fetching additional data) when it unmounts. This is very easy to acplish with Bluebird promises, which have a .cancel()
method on them that causes the .then()
and .catch()
handlers to never respond.
However, ES6 Promises do not support cancellation. In addition, ES7's async
and await
only use native Promises and do not support any drop-in replacements (such as Bluebird). This means that if you want to be able to cancel Promises in React, as they tell you to do, you have to use .then()
and .catch()
and also have to place a middleman on native Promise methods like fetch()
so that it can be cancelled.
Is this really what React expects?
Share Improve this question edited Nov 30, 2020 at 20:26 Bergi 667k161 gold badges1k silver badges1.5k bronze badges asked Jan 2, 2020 at 18:43 TheHans255TheHans255 2,2552 gold badges24 silver badges40 bronze badges 6- A mon pattern is to move async loading into your store (something like Redux) and make code inside your React ponents strictly synchronous. This is not an indication that React is on its way out, it's that the React team has identified mixing async code in your views leads to more problems despite its short term convenience and speed. You can also read the 2015 post that still applies today about isMounted is an Antipattern for other ways to address this. – Ross Allen Commented Jan 2, 2020 at 18:49
- 1 Would you please place a link to React documentation page with that advice in it? – x00 Commented Jan 2, 2020 at 18:49
-
I suggest starting with
useReducer
, which ships with React, to understand the flow of a central storage object for your application's state. There are other questions about doing async fetches withuseReducer
that would enable your setup too. – Ross Allen Commented Jan 2, 2020 at 18:55 - you tried to add an setTimeout inside to throw an error or reject the promise? – Psartek Commented Jan 3, 2020 at 10:21
- if you just want awaitable "cancelable" Promises here's a jsfiddle using a Promise wrapped monkey patch: jsfiddle/x6ah7qog/1 It's a bit inelegant, admittedly. – user120242 Commented May 27, 2020 at 20:23
2 Answers
Reset to default 3The latest React docs (Aug 2023), suggest 2 solutions to this issue:
Create a new 'ignore' variable that is set to true during the Effect cleanup. When the Promise returns, ignore the result based on the variable.
Use the AbortController API to cancel the asynchronous action.
Check the solution to 'Challenge 4 of 4' on this page of the React Docs.
Just for your reference. Using CPromise package, you can cancel your promise chains, including nested ones. It supports AbortController and generators as a replacement for ECMA async functions. Currently the project in the beta stage.
Generator usage Live Demo :
import CPromise from "c-promise2";
const chain = CPromise.resolve()
.then(function* () {
const value1 = yield new CPromise((resolve, reject, { onCancel }) => {
const timer = setTimeout(resolve, 1000, 3);
onCancel(() => {
console.log("timer cleared");
clearTimeout(timer);
});
});
// Run promises in parallel using CPromise.all (shortcut syntax)
const [value2, value3] = yield [
CPromise.delay(1000, 4),
CPromise.delay(1000, 5)
];
return value1 + value2 + value3;
})
.then(
(value) => {
console.log(`Done: ${value}`); // Done: 12 (without calling cancel)
},
(err) => {
console.log(`Failed: ${err}`); // Failed: CanceledError: canceled
}
);
setTimeout(() => chain.cancel(), 100);
Output:
timer cleared
Failed: CanceledError: canceled
All stages there are pletely cancelable/abortable. Here is an example of using it with React Live Demo
export class TestComponent extends React.Component {
state = {};
async ponentDidMount() {
console.log("mounted");
this.controller = new CPromise.AbortController();
try {
const json = await this.myAsyncTask(
"https://run.mocky.io/v3/7b038025-fc5f-4564-90eb-4373f0721822?mocky-delay=2s"
);
console.log("json:", json);
await this.myAsyncTaskWithDelay(1000, 123); // just another async task
this.setState({ text: JSON.stringify(json) });
} catch (err) {
if (CPromise.isCanceledError(err)) {
console.log("tasks terminated");
}
}
}
myAsyncTask(url) {
return CPromise.from(function* () {
const response = yield cpFetch(url); // cancellable request
return yield response.json();
}).listen(this.controller.signal);
}
myAsyncTaskWithDelay(ms, value) {
return new CPromise((resolve, reject, { onCancel }) => {
const timer = setTimeout(resolve, ms, value);
onCancel(() => {
console.log("timeout cleared");
clearTimeout(timer);
});
}).listen(this.controller.signal);
}
render() {
return (
<div>
AsyncComponent: <span>{this.state.text || "fetching..."}</span>
</div>
);
}
ponentWillUnmount() {
console.log("unmounted");
this.controller.abort(); // kill all pending tasks
}
}
Using Hooks and cancel
method
import React, { useEffect, useState } from "react";
import CPromise from "c-promise2";
import cpFetch from "cp-fetch";
export function TestComponent(props) {
const [text, setText] = useState("fetching...");
useEffect(() => {
console.log("mount");
const promise = cpFetch(props.url)
.then(function* (response) {
const json = yield response.json();
setText(`Delay for 2000ms...`);
yield CPromise.delay(2000);
setText(`Success: ${JSON.stringify(json)}`);
})
.canceled()
.catch((err) => {
setText(`Failed: ${err}`);
});
return () => {
console.log("unmount");
promise.cancel();
};
}, [props.url]);
return <p>{text}</p>;
}
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745004436a4605690.html
评论列表(0条)