簡體   English   中英

如何使用 AbortController 取消 React 中的 Promise?

[英]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 中。 例子:

  • 設置超時
  • Web 工人消息
  • 文件讀取器事件

但是在異步文件中,您的返回值將已經轉換為 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 但您已經擁有價值的情況。

AbortController 僅用於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 只是半異步的

你提到你正在使用 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM