[英]How to use the AbortController to cancel Promises in React?
我想使用 AbortController 在我的 React 應用程序中取消AbortController
,不幸的是, abort event
無法識別,因此我無法對其做出反應。
我的設置如下所示:
WrapperComponent.tsx:這里我正在創建 AbortController 並將信號傳遞給我的方法calculateSomeStuff
,該方法返回 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()}
/>
);
}
我在這里做錯了什么? 我的 console.logs 不可見,並且在調用handleAbort
處理程序后signal
永遠不會設置為true
。
根據您的代碼,需要進行一些更正:
async
function 中使用new Promise()
如果您采用基於事件但自然異步的東西,則使用new Promise
,並將其包裝到 Promise 中。 例子:
但是在異步文件中,您的返回值將已經轉換為 promise。 拒絕將自動轉換為您可以使用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 caught here
}
}
調用者將立即獲得 promise,但在代碼遇到 return 語句或 throw 之前它不會被解析。
如果您的工作需要使用Promise
構造函數進行包裝,並且您想從async
function 中完成,該怎么辦? 將Promise
構造函數放在單獨的非async
function 中。 然后await
來自async
function 的 function。
function wrapSomeApi() {
return new Promise(...);
}
async function myAsyncFunction() {
await wrapSomeApi();
}
new Promise(...)
時,必須在工作完成之前返回 promise您的代碼應大致遵循以下模式:
function MyAsyncWrapper() {
return new Promise((resolve, reject) => {
const workDoer = new WorkDoer();
workDoer.on('done', result => resolve(result));
workDoer.on('error', result => reject(error));
// exits right away while work completes in background
})
}
您幾乎從不想使用Promise.resolve(value)
或Promise.reject(error)
。 這些僅適用於您的接口需要promise 但您已經擁有價值的情況。
fetch
運行 TC39 的人一直在想辦法取消,但現在沒有官方取消 API。
fetch
接受AbortController
以取消 HTTP 請求,這很有用。 但這並不意味着取消常規的舊工作。
幸運的是,你可以自己做。 使用 async/await 的一切都是協同程序,沒有先發制人的多任務處理,您可以在其中中止線程或強制拒絕。 相反,您可以創建一個簡單的令牌 object 並將其傳遞給您長期運行的異步 function:
const token = { cancelled: false };
await doLongRunningTask(params, token);
要取消,只需更改cancelled
的值。
someElement.on('click', () => token.cancelled = true);
長時間運行的工作通常涉及某種循環。 只需檢查循環中的令牌,如果它被取消則退出循環
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。 這個庫看起來是異步的,因為它的所有 API 都會返回 Promise。 但實際上,它只是在 WASM 加載器上await
。 它在第一次運行后被緩存,之后所有的計算都是同步的。
實際上不await
的異步代碼沒有任何好處。 它不會暫停以解除阻塞線程。
那么,如果你有像 hash-wasm 使用的 CPU 密集型代碼,你怎么能讓你的代碼喘不過氣來呢? 您可以增量完成工作,並使用setTimeout
安排這些增量:
for (const step of stepsToDo) {
// insert cancel token check here
// 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 構造函數,但立即等待而不是返回它)
上述技術將比僅執行任務運行得更慢。 但是通過讓出線程,像 React 更新這樣的東西可以在沒有尷尬的掛起的情況下執行。
如果您真的需要性能,請查看 Web Workers,它可以讓您在線程外執行 CPU 繁重的工作,因此它不會阻塞主線程。 workerize 之類的庫可以幫助您將異步函數轉換為在 worker 中運行。
這就是我現在所擁有的一切,我很抱歉寫小說
我可以建議我的庫( use-async-effect2 )來管理異步任務/承諾的取消。 這是一個帶有嵌套異步 function 取消的簡單演示:
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>
);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.