如何优雅地取消 JavaScript 异步任务
在程序中处理异步任务通常比较麻烦,尤其是那些不支持取消异步任务的编程语言。所幸的是,JavaScript 提供了一种非常方便的机制来取消异步任务。
中断信号
自从 ES2015 引入了 Promise ,开发者有了取消异步任务的需求,随后推出的一些 Web API 也开始支持异步方案,比如 Fetch API。TC39 委员会(就是制定 ECMAScript 标准的组织)最初尝试定义一套通用的解决方案,以便后续作为 ECMAScript 标准。后来讨论不出什么结果来,这个问题也就搁置了。鉴于此,WHATWG (HTML 标准制定组织)另起炉灶,自己搞出一套解决方案,直接在 DOM 标准上引入了 AbortController
。这种做法的坏处显而易见,因为它不是语言层面的 ECMAScript 标准, Node.js 平台也就不支持 AbortController
。
在 DOM 规范里, AbortController 设计得非常通用,事实上你可以用在任何异步 API 中。目前只得到 Fetch API 的官方支持,但你完全可以用在自己的异步代码里。
在开始介绍之前,我们先看下 AbortController 的工作原理
const abortController = new AbortController(); // 1 const abortSignal = abortController.signal; // 2 fetch( 'http://kaysonli.', { signal: abortSignal // 3 } ).catch( ( { message } ) => { // 5 console.log( message ); } ); abortController.abort(); // 4
上面的代码很简单,创建了AbortController的一个实例(1),并将它的 signal 属性赋值给一个变量(2)。然后调用fetch()并传入 signal 参数(3)。取消请求时调用 abortController.abort()(4)。这样就会自动执行fetch() 的 reject ,也就是进入catch()部分(5)。
它的signal属性是核心所在。该属性是 AbortSignal 的实例,它有一个 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 = 'S calculation'; const result = await calculate(); // 2 alert( result ); // 3 target.innerText = 'Calculate'; } ); function calculate() { return new Promise( ( resolve, reject ) => { setTimeout( ()=> { resolve( 1 ); }, 5000 ); } ); } </script>
上面的代码给按钮绑定了一个异步的 click 事件处理器(1),并在里面调用了 calculate() 函数(2)。5 秒后会弹出对话框显示结果(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 = 'S 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)。
如果用户在 5 秒之内点击按钮,就会执行abortController.abort()
函数(5)。这样就会在刚才传给 calculate()的AbortSignal实例上触发 abort 事件(6)。
在 abort 事件处理器里面清除定时器(7),然后用一个适当的异常对象拒绝 Promise(8)。
根据 DOM 规范,这个异常对象必须是一个'AbortError'
类型的DOMException
。
这个异常对象最终传给了catch (9) 和finally (10)。
还要考虑这样一种情况
const abortController = new AbortController(); abortController.abort(); calculate( abortController.signal );
这种情况下 abort 事件不会触发,因为它在signal传给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 异步任务的文章就介绍到这了,更多相关JavaScript 取消异步任务内容请搜索狼蚁SEO以前的文章或继续浏览狼蚁网站SEO优化的相关文章希望大家以后多多支持狼蚁SEO!
编程语言
- 如何快速学会编程 如何快速学会ug编程
- 免费学编程的app 推荐12个免费学编程的好网站
- 电脑怎么编程:电脑怎么编程网咯游戏菜单图标
- 如何写代码新手教学 如何写代码新手教学手机
- 基础编程入门教程视频 基础编程入门教程视频华
- 编程演示:编程演示浦丰投针过程
- 乐高编程加盟 乐高积木编程加盟
- 跟我学plc编程 plc编程自学入门视频教程
- ug编程成航林总 ug编程实战视频
- 孩子学编程的好处和坏处
- 初学者学编程该从哪里开始 新手学编程从哪里入
- 慢走丝编程 慢走丝编程难学吗
- 国内十强少儿编程机构 中国少儿编程机构十强有
- 成人计算机速成培训班 成人计算机速成培训班办
- 孩子学编程网上课程哪家好 儿童学编程比较好的
- 代码编程教学入门软件 代码编程教程