如何取消JavaScript中的非同步任務?

青灯夜游
發布: 2020-07-06 10:08:26
轉載
2849 人瀏覽過

如何取消JavaScript中的非同步任務?

有時候執行非同步任務可能是很困難的,尤其是當特定的程式語言不允許取消被錯誤啟動或不再需要的操作時。幸運的是 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
登入後複製

查看上面的程式碼,你會發現在開始時創建了AbortControllerDOM 介面的新實例(1),並將其signal屬性綁定到變數(2)。然後呼叫fetch()並傳遞signal作為其選項之一(3)。要中止取得資源,你只需呼叫abortController.abort()(4)。它將自動拒絕fetch()的 promise,並且控制項將傳遞給catch()區塊(5)。

signal屬性本身非常有趣,它是該節目的主要明星。該屬性是AbortSignalDOM 介面的實例,該實例具有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 ); } );
登入後複製

但有時使用者希望能夠中止這種代價高昂的操作。沒錯,他們應該有這樣的能力。新增一個能夠啟動和停止計算的按鈕:

Calculate" title="" data-original-title="复制">

 
登入後複製

在上面的代码中,向按钮(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中文網其他相關文章!

相關標籤:
來源:segmentfault.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!