您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關JavaScript取消異步任務的方法,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
在等待數據返回這段時間,JavaScript是不能處理其他任務的,此時頁面的交互,滾動等任何操作也都會被阻塞,這顯然是及其不友好,不可接受的,而這正是需要異步編程大顯身手的場景。我們想通過Ajax請求數據來渲染頁面,這是一個在我們前端當中很常見渲染頁面的方式。基本每 個頁面都會都這樣的過程。
中止信號(Abort signal)
在將 Promise
引入 ES2015 并出現了一些支持新異步解決方案的 Web API 之后不久,需要取消異步任務的需求就出現了。最初的嘗試集中在創建通用解決方案上,并期待以后可以成為 ECMAScript 標準的一部分。但是,討論很快陷入僵局,無法解決問題。因此,WHATWG 準備了自己的解決方案,并以 AbortController
的形式將其直接引入 DOM。這種解決方案的明顯缺點是 Node.js 中不提供 AbortController
,從而在該環境沒有任何優雅或官方的方式來取消異步任務。
正如你在 DOM 規范中所看到的,AbortController
是用一種非常通用的方式描述的。所以你可以在任何類型的異步 API 中使用 —— 甚至是那些目前還不存在的 API。目前只有 Fetch API 正式支持,但是你也可以在自己的代碼中使用它!
在開始之前,讓我們花點時間分析一下 AbortController
的工作原理:
const abortController = new AbortController(); // 1 const abortSignal = abortController.signal; // 2 fetch( 'http://example.com', { signal: abortSignal // 3 } ).catch( ( { message } ) => { // 5 console.log( message ); } ); abortController.abort(); // 4
查看上面的代碼,你會發現在開始時創建了 AbortController
DOM 接口的新實例(1),并將其 signal
屬性綁定到變量(2)。然后調用 fetch()
并傳遞 signal
作為其選項之一(3)。要中止獲取資源,你只需調用abortController.abort()
(4)。它將自動拒絕 fetch()
的 promise,并且控件將傳遞給 catch()
塊(5)。
signal
屬性本身非常有趣,它是該節目的主要明星。該屬性是 AbortSignal
DOM 接口的實例,該實例具有 aborted
屬性,其中包含有關用戶是否已調用 abortController.abort()
方法的信息。你還可以將 abort
事件偵聽器綁定到將要調用 abortController.abort()
時調用的事件監聽器。換句話說:AbortController
只是 AbortSignal
的公共接口。
可終止函數
假設我們用一個異步函數執行一些非常復雜的計算(例如,異步處理來自大數組的數據)。為簡單起見,示例函數通過先等待五秒鐘然后再返回結果來模擬這一工作:
function calculate() { return new Promise( ( resolve, reject ) => { setTimeout( ()=> { resolve( 1 ); }, 5000 ); } ); } calculate().then( ( result ) => { console.log( result ); } );
但有時用戶希望能夠中止這種代價高昂的操作。沒錯,他們應該有這樣的能力。添加一個能夠啟動和停止計算的按鈕:
<button id="calculate">Calculate</button> <script type="module"> document.querySelector('#calculate').addEventListener('click',async({ target })=>{//1 target.innerText = 'Stop calculation'; const result = await calculate(); // 2 alert( result ); // 3 target.innerText = 'Calculate'; } ); function calculate() { return new Promise( ( resolve, reject ) => { setTimeout( ()=> { resolve( 1 ); }, 5000 ); } ); } </script>
在上面的代碼中,向按鈕(1)添加一個異步 click
事件偵聽器,并在其中調用 calculate()
函數(2)。五秒鐘后,將顯示帶有結果的警報對話框(3)。另外, script [type = module]
用于強制 JavaScript 代碼進入嚴格模式——因為它比 'use strict'
編譯指示更為優雅。
現在添加中止異步任務的功能:
{ // 1 let abortController = null; // 2 document.querySelector('#calculate').addEventListener('click',async ( { target } )=>{ if ( abortController ) { abortController.abort(); // 5 abortController = null; target.innerText = 'Calculate'; return; } abortController = new AbortController(); // 3 target.innerText = 'Stop calculation'; try { const result = await calculate( abortController.signal ); // 4 alert( result ); } catch { alert( 'WHY DID YOU DO THAT?!' ); // 9 } finally { // 10 abortController = null; target.innerText = 'Calculate'; } } ); function calculate( abortSignal ) { return new Promise( ( resolve, reject ) => { const timeout = setTimeout( ()=> { resolve( 1 ); }, 5000 ); abortSignal.addEventListener( 'abort', () => { // 6 const error = new DOMException( 'Calculation aborted by the user', 'AbortError' ); clearTimeout( timeout ); // 7 reject( error ); // 8 } ); } ); } }
如你所見,代碼變得更長了。但是沒有理由驚慌,它并沒有變得更難理解!
一切都包含在塊(1)中,該塊相當于IIFE。因此,abortController
變量(2)不會泄漏到全局作用域內。
首先,將其值設置為 null
。鼠標單擊按鈕時,此值會更改。然后將其值設置為 AbortController
的新實例(3)。之后,將實例的 signal
屬性直接傳遞給你的 calculate()
函數(4)。
如果用戶在五秒鐘之內再次單擊該按鈕,則將導致調用 abortController.abort()
函數(5)。反過來,這將在你先前傳遞給 calculate()
的 AbortSignal
實例上觸發 abort
事件(6)。
在 abort
事件偵聽器內部,刪除了滴答計時器(7)并拒絕了帶有適當錯誤的promise (8; 根據規范 ,它必須是類型為 'AbortError'
的 DOMException
)。該錯誤最終把控制權傳遞給 catch
(9)和 finally
塊(10)。
你還應該準備處理如下情況的代碼:
const abortController = new AbortController(); abortController.abort(); calculate( abortController.signal );
在這種情況下,abort
事件將不會被觸發,因為它發生在將信號傳遞給 calculate()
函數之前。因此你應該進行一些重構:
function calculate( abortSignal ) { return new Promise( ( resolve, reject ) => { const error = new DOMException( 'Calculation aborted by the user', 'AbortError' ); // 1 if ( abortSignal.aborted ) { // 2 return reject( error ); } const timeout = setTimeout( ()=> { resolve( 1 ); }, 5000 ); abortSignal.addEventListener( 'abort', () => { clearTimeout( timeout ); reject( error ); } ); } ); }
錯誤被移到頂部(1)。因此,你可以在代碼不同部分中重用它(但是,創建一個錯誤工廠會更優雅,盡管聽起來很愚蠢)。另外出現了一個保護子句,檢查 abortSignal.aborted
(2)的值。如果等于 true
,那么 calculate()
函數將會拒絕帶有適當錯誤的 promise,而無需執行任何其他操作。
關于JavaScript取消異步任務的方法就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。