有時候執行非同步任務可能是很困難的,尤其是當特定的程式語言不允許取消被錯誤啟動或不再需要的操作時。幸運的是 JavaScript 提供了非常方便的功能來中止非同步活動。在本文中,你可以學到如何建立可中止的函數。
中止訊號(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,而无需执行任何其他操作。
这就是创建完全可中止的异步函数的方式。 演示可在这里获得(https://blog.comandeer.pl/ass...)。请享用!
英文原文地址:https://ckeditor.com/blog/Aborting-a-signal-how-to-cancel-an-asynchronous-task-in-JavaScript/
相关教程推荐:JavaScript视频教程
以上是如何取消JavaScript中的非同步任務?的詳細內容。更多資訊請關注PHP中文網其他相關文章!