为了防止对已卸载的 React 组件进行虚拟更新,React 会告诉您在卸载组件时取消组件上的任何待处理的 Promise(例如用于获取附加数据的 Promise)。使用 Bluebird Promise 可以很容易地实现这一点,它有一个 .cancel()
方法,导致
.then()
和
.catch()
处理程序永远不会响应。但是,ES6 Promise 不支持取消。此外,ES7 的
async
和
await
仅使用原生 Promises,不支持任何直接替换(例如 Bluebird)。这意味着,如果您希望能够在 React 中取消 Promise(正如他们告诉您的那样),您必须使用
.then()
和
.catch()
,并且还必须在
fetch()
等原生 Promise 方法上放置一个中间人,以便可以取消。这真的是 React 所期望的吗?
CPromise 包,您可以取消您的承诺链,包括嵌套的承诺链。它支持 AbortController 和生成器作为 ECMA 异步函数的替代品。目前该项目处于测试阶段。
发电机使用现场演示:
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);
输出:
timer cleared
Failed: CanceledError: canceled
所有阶段都完全可以取消/中止。 这是将其与 React 一起使用的示例Live Demo
export class TestComponent extends React.Component {
state = {};
async componentDidMount() {
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>
);
}
componentWillUnmount() {
console.log("unmounted");
this.controller.abort(); // kill all pending tasks
}
}
使用Hooks和cancel
方法
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>;
}
AbortController API 取消 Promise。