React 中如何使用 AbortController 取消 Promise?
How to use the AbortController to cancel Promises in React?
我想在我的 React 应用程序中使用 AbortController
取消承诺,不幸的是 abort event
无法识别,因此我无法对其做出反应。
我的设置如下所示:
WrapperComponent.tsx: 在这里我创建了 AbortController 并将信号传递给我的方法 calculateSomeStuff
即 returns 一个 Promise。 controller
我将作为道具传递给我的 Table 组件。
export const WrapperComponent = () => {
const controller = new AbortController();
const signal = abortController.signal;
// This function gets called in my useEffect
// I'm passing signal to the method calculateSomeStuff
const doSomeStuff = (file: any): void => {
calculateSomeStuff(signal, file)
.then((hash) => {
// do some stuff
})
.catch((error) => {
// throw error
});
};
return (<Table controller={controller} />)
}
calculateSomeStuff
方法如下所示:
export const calculateSomeStuff = async (signal, file): Promise<any> => {
if (signal.aborted) {
console.log('signal.aborted', signal.aborted);
return Promise.reject(new DOMException('Aborted', 'AbortError'));
}
for (let i = 0; i <= 10; i++) {
// do some stuff
}
const secret = 'ojefbgwovwevwrf';
return new Promise((resolve, reject) => {
console.log('Promise Started');
resolve(secret);
signal.addEventListener('abort', () => {
console.log('Aborted');
reject(new DOMException('Aborted', 'AbortError'));
});
});
};
在我的 Table 组件中,我这样调用 abort()
方法:
export const Table = ({controller}) => {
const handleAbort = ( fileName: string) => {
controller.abort();
};
return (
<Button
onClick={() => handleAbort()}
/>
);
}
我在这里做错了什么?在调用 handleAbort
处理程序后,我的 console.logs 不可见并且 signal
从未设置为 true
。
根据您的代码,需要进行一些更正:
不要在 async
函数中 return new Promise()
如果您采用基于事件但自然异步的方式并将其包装到 Promise 中,则使用 new Promise
。示例:
- 设置超时
- 网络工作者消息
- FileReader 事件
但在异步函数中,您的 return 值将已经转换为承诺。拒绝将自动转换为您可以使用 try
/catch
捕获的异常。示例:
async function MyAsyncFunction(): Promise<number> {
try {
const value1 = await functionThatReturnsPromise(); // unwraps promise
const value2 = await anotherPromiseReturner(); // unwraps promise
if (problem)
throw new Error('I throw, caller gets a promise that is eventually rejected')
return value1 + value2; // I return a value, caller gets a promise that is eventually resolved
} catch(e) {
// rejected promise and other errors caught here
console.error(e);
throw e; // rethrow to caller
}
}
调用者会立即得到一个 promise,但在代码遇到 return 语句或抛出之前它不会被解析。
如果您有工作需要用 Promise
构造函数包装,并且您想从 async
函数中完成怎么办?将 Promise
构造函数放在一个单独的非 async
函数中。然后 await
来自 async
函数的非 async
函数。
function wrapSomeApi() {
return new Promise(...);
}
async function myAsyncFunction() {
await wrapSomeApi();
}
使用 new Promise(...)
时,必须 returned 承诺才能 工作完成
您的代码应大致遵循以下模式:
function MyAsyncWrapper() {
return new Promise((resolve, reject) => {
const workDoer = new WorkDoer();
workDoer.on('done', result => resolve(result));
workDoer.on('error', error => reject(error));
// exits right away while work completes in background
})
}
您几乎从未想要使用Promise.resolve(value)
或Promise.reject(error)
。这些仅适用于您的接口 需要 承诺但您已经拥有价值的情况。
AbortController 仅适用于 fetch
运行 TC39 的人们一直在努力解决取消问题 for a while,但现在还没有官方取消 API。
AbortController
被 fetch
接受用于取消 HTTP 请求,这很有用。但这并不意味着取消常规的旧工作。
幸运的是,你可以自己做。 async/await 中的所有内容都是协同例程,没有抢先式多任务处理,您可以在其中中止线程或强制拒绝。相反,您可以创建一个简单的令牌对象并将其传递给您的 long 运行ning 异步函数:
const token = { cancelled: false };
await doLongRunningTask(params, token);
要取消,只需更改 cancelled
的值即可。
someElement.on('click', () => token.cancelled = true);
长 运行ning 工作通常涉及某种循环。只检查循环中的token,如果取消就退出循环
async function doLongRunningTask(params: string, token: { cancelled: boolean }) {
for (const task of workToDo()) {
if (token.cancelled)
throw new Error('task got cancelled');
await task.doStep();
}
}
由于您使用的是 React,因此您需要 token
在渲染之间成为相同的引用。因此,您可以为此使用 useRef
挂钩:
function useCancelToken() {
const token = useRef({ cancelled: false });
const cancel = () => token.current.cancelled = true;
return [token.current, cancel];
}
const [token, cancel] = useCancelToken();
// ...
return <>
<button onClick={ () => doLongRunningTask(token) }>Start work</button>
<button onClick={ () => cancel() }>Cancel</button>
</>;
hash-wasm 只是半异步的
您提到您正在使用 hash-wasm。这个库 看起来 是异步的,正如其所有 API 的 return 所承诺的那样。但实际上,它只是 await
-ing 在 WASM 加载器上。在第一个 运行 之后被缓存,之后所有计算都是同步的。
实际上 await
没有的异步代码没有任何好处。它不会暂停以解除线程阻塞。
那么,如果你有像 hash-wasm 使用的 CPU 密集型代码,你怎么能让你的代码呼吸呢?您可以逐步完成工作,并使用 setTimeout
:
安排这些增量
for (const step of stepsToDo) {
if (token.cancelled)
throw new Error('task got cancelled');
// schedule the step to run ASAP, but let other events process first
await new Promise(resolve => setTimeout(resolve, 0));
const chunk = await loadChunk();
updateHash(chunk);
}
(请注意,我在这里使用了 Promise 构造函数,但立即等待而不是 returning 它)
上面的技术 将 运行 比只做任务慢。但是通过让出线程,像 React 更新这样的东西可以在没有尴尬挂起的情况下执行。
如果您确实需要性能,请查看 Web Workers,它可以让您执行 CPU-繁重的线程外工作,因此不会阻塞主线程。 workerize 等库可以帮助您将异步函数转换为 worker 中的 运行。
暂时就这些了,写小说不好意思
我可以推荐我的库 (use-async-effect2) 来管理异步取消 tasks/promises。
这是一个带有嵌套异步函数取消的 simple demo:
import React, { useState } from "react";
import { useAsyncCallback } from "use-async-effect2";
import { CPromise } from "c-promise2";
// just for testing
const factorialAsync = CPromise.promisify(function* (n) {
console.log(`factorialAsync::${n}`);
yield CPromise.delay(500);
return n != 1 ? n * (yield factorialAsync(n - 1)) : 1;
});
function TestComponent({ url, timeout }) {
const [text, setText] = useState("");
const myTask = useAsyncCallback(
function* (n) {
for (let i = 0; i <= 5; i++) {
setText(`Working...${i}`);
yield CPromise.delay(500);
}
setText(`Calculating Factorial of ${n}`);
const factorial = yield factorialAsync(n);
setText(`Done! Factorial=${factorial}`);
},
{ cancelPrevious: true }
);
return (
<div>
<div>{text}</div>
<button onClick={() => myTask(15)}>
Run task
</button>
<button onClick={myTask.cancel}>
Cancel task
</button>
</div>
);
}
我想在我的 React 应用程序中使用 AbortController
取消承诺,不幸的是 abort event
无法识别,因此我无法对其做出反应。
我的设置如下所示:
WrapperComponent.tsx: 在这里我创建了 AbortController 并将信号传递给我的方法 calculateSomeStuff
即 returns 一个 Promise。 controller
我将作为道具传递给我的 Table 组件。
export const WrapperComponent = () => {
const controller = new AbortController();
const signal = abortController.signal;
// This function gets called in my useEffect
// I'm passing signal to the method calculateSomeStuff
const doSomeStuff = (file: any): void => {
calculateSomeStuff(signal, file)
.then((hash) => {
// do some stuff
})
.catch((error) => {
// throw error
});
};
return (<Table controller={controller} />)
}
calculateSomeStuff
方法如下所示:
export const calculateSomeStuff = async (signal, file): Promise<any> => {
if (signal.aborted) {
console.log('signal.aborted', signal.aborted);
return Promise.reject(new DOMException('Aborted', 'AbortError'));
}
for (let i = 0; i <= 10; i++) {
// do some stuff
}
const secret = 'ojefbgwovwevwrf';
return new Promise((resolve, reject) => {
console.log('Promise Started');
resolve(secret);
signal.addEventListener('abort', () => {
console.log('Aborted');
reject(new DOMException('Aborted', 'AbortError'));
});
});
};
在我的 Table 组件中,我这样调用 abort()
方法:
export const Table = ({controller}) => {
const handleAbort = ( fileName: string) => {
controller.abort();
};
return (
<Button
onClick={() => handleAbort()}
/>
);
}
我在这里做错了什么?在调用 handleAbort
处理程序后,我的 console.logs 不可见并且 signal
从未设置为 true
。
根据您的代码,需要进行一些更正:
不要在 async
函数中 return new Promise()
如果您采用基于事件但自然异步的方式并将其包装到 Promise 中,则使用 new Promise
。示例:
- 设置超时
- 网络工作者消息
- FileReader 事件
但在异步函数中,您的 return 值将已经转换为承诺。拒绝将自动转换为您可以使用 try
/catch
捕获的异常。示例:
async function MyAsyncFunction(): Promise<number> {
try {
const value1 = await functionThatReturnsPromise(); // unwraps promise
const value2 = await anotherPromiseReturner(); // unwraps promise
if (problem)
throw new Error('I throw, caller gets a promise that is eventually rejected')
return value1 + value2; // I return a value, caller gets a promise that is eventually resolved
} catch(e) {
// rejected promise and other errors caught here
console.error(e);
throw e; // rethrow to caller
}
}
调用者会立即得到一个 promise,但在代码遇到 return 语句或抛出之前它不会被解析。
如果您有工作需要用 Promise
构造函数包装,并且您想从 async
函数中完成怎么办?将 Promise
构造函数放在一个单独的非 async
函数中。然后 await
来自 async
函数的非 async
函数。
function wrapSomeApi() {
return new Promise(...);
}
async function myAsyncFunction() {
await wrapSomeApi();
}
使用 new Promise(...)
时,必须 returned 承诺才能 工作完成
您的代码应大致遵循以下模式:
function MyAsyncWrapper() {
return new Promise((resolve, reject) => {
const workDoer = new WorkDoer();
workDoer.on('done', result => resolve(result));
workDoer.on('error', error => reject(error));
// exits right away while work completes in background
})
}
您几乎从未想要使用Promise.resolve(value)
或Promise.reject(error)
。这些仅适用于您的接口 需要 承诺但您已经拥有价值的情况。
AbortController 仅适用于 fetch
运行 TC39 的人们一直在努力解决取消问题 for a while,但现在还没有官方取消 API。
AbortController
被 fetch
接受用于取消 HTTP 请求,这很有用。但这并不意味着取消常规的旧工作。
幸运的是,你可以自己做。 async/await 中的所有内容都是协同例程,没有抢先式多任务处理,您可以在其中中止线程或强制拒绝。相反,您可以创建一个简单的令牌对象并将其传递给您的 long 运行ning 异步函数:
const token = { cancelled: false };
await doLongRunningTask(params, token);
要取消,只需更改 cancelled
的值即可。
someElement.on('click', () => token.cancelled = true);
长 运行ning 工作通常涉及某种循环。只检查循环中的token,如果取消就退出循环
async function doLongRunningTask(params: string, token: { cancelled: boolean }) {
for (const task of workToDo()) {
if (token.cancelled)
throw new Error('task got cancelled');
await task.doStep();
}
}
由于您使用的是 React,因此您需要 token
在渲染之间成为相同的引用。因此,您可以为此使用 useRef
挂钩:
function useCancelToken() {
const token = useRef({ cancelled: false });
const cancel = () => token.current.cancelled = true;
return [token.current, cancel];
}
const [token, cancel] = useCancelToken();
// ...
return <>
<button onClick={ () => doLongRunningTask(token) }>Start work</button>
<button onClick={ () => cancel() }>Cancel</button>
</>;
hash-wasm 只是半异步的
您提到您正在使用 hash-wasm。这个库 看起来 是异步的,正如其所有 API 的 return 所承诺的那样。但实际上,它只是 await
-ing 在 WASM 加载器上。在第一个 运行 之后被缓存,之后所有计算都是同步的。
实际上 await
没有的异步代码没有任何好处。它不会暂停以解除线程阻塞。
那么,如果你有像 hash-wasm 使用的 CPU 密集型代码,你怎么能让你的代码呼吸呢?您可以逐步完成工作,并使用 setTimeout
:
for (const step of stepsToDo) {
if (token.cancelled)
throw new Error('task got cancelled');
// schedule the step to run ASAP, but let other events process first
await new Promise(resolve => setTimeout(resolve, 0));
const chunk = await loadChunk();
updateHash(chunk);
}
(请注意,我在这里使用了 Promise 构造函数,但立即等待而不是 returning 它)
上面的技术 将 运行 比只做任务慢。但是通过让出线程,像 React 更新这样的东西可以在没有尴尬挂起的情况下执行。
如果您确实需要性能,请查看 Web Workers,它可以让您执行 CPU-繁重的线程外工作,因此不会阻塞主线程。 workerize 等库可以帮助您将异步函数转换为 worker 中的 运行。
暂时就这些了,写小说不好意思
我可以推荐我的库 (use-async-effect2) 来管理异步取消 tasks/promises。 这是一个带有嵌套异步函数取消的 simple demo:
import React, { useState } from "react";
import { useAsyncCallback } from "use-async-effect2";
import { CPromise } from "c-promise2";
// just for testing
const factorialAsync = CPromise.promisify(function* (n) {
console.log(`factorialAsync::${n}`);
yield CPromise.delay(500);
return n != 1 ? n * (yield factorialAsync(n - 1)) : 1;
});
function TestComponent({ url, timeout }) {
const [text, setText] = useState("");
const myTask = useAsyncCallback(
function* (n) {
for (let i = 0; i <= 5; i++) {
setText(`Working...${i}`);
yield CPromise.delay(500);
}
setText(`Calculating Factorial of ${n}`);
const factorial = yield factorialAsync(n);
setText(`Done! Factorial=${factorial}`);
},
{ cancelPrevious: true }
);
return (
<div>
<div>{text}</div>
<button onClick={() => myTask(15)}>
Run task
</button>
<button onClick={myTask.cancel}>
Cancel task
</button>
</div>
);
}