JavaScript取消异步任务的方法

这篇文章将为大家详细讲解有关JavaScript取消异步任务的方法,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

成都创新互联公司长期为上1000家客户提供的网站建设服务,团队从业经验10年,关注不同地域、不同群体,并针对不同对象提供差异化的产品和服务;打造开放共赢平台,与合作伙伴共同营造健康的互联网生态环境。为平武企业提供专业的网站设计、网站制作平武网站改版等技术服务。拥有十余年丰富建站经验和众多成功案例,为您定制开发。

在等待数据返回这段时间,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 );
} );

但有时用户希望能够中止这种代价高昂的操作。没错,他们应该有这样的能力。添加一个能够启动和停止计算的按钮:



在上面的代码中,向按钮(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取消异步任务的方法就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。


当前标题:JavaScript取消异步任务的方法
分享路径:http://ybzwz.com/article/ggpgje.html