Promise 是一种用于异步编程的 JavaScript 库。它提供了一种简单而强大的方法来处理异步操作,以及确保异步操作的可组合性和错误处理能力。然而,有时候我们需要取消 Promise 中未完成的任务,这时候该怎么办呢?本文将从 Promise 基本用法的角度出发,探讨如何在 Promise 执行过程中取消未完成的任务。
Promise 基本用法回顾
在正式讲解 Promise 取消机制之前,我们有必要先回顾一下 Promise 基本用法。
Promise 状态
Promise 有三个状态:Pending (等待态)、Fulfilled (完成态)和 Rejected (失败态)。
当 Promise 状态为 Pending 时,表示此时异步操作还未完成。
当 Promise 状态为 Fulfilled 时,表示异步操作已经完成,并且成功地返回了结果。
当 Promise 状态为 Rejected 时,表示异步操作已经完成,但是发生了错误或者异常。
Promise 构造函数
我们可以使用 Promise 构造函数来创建一个 Promise 实例。
const promise = new Promise((resolve, reject) => { // 异步操作 // 成功时调用 resolve 函数,传递异步操作结果 // 失败时调用 reject 函数,传递错误信息 });
在 Promise 构造函数中,我们要传入一个参数,这个参数是一个函数,它接受两个参数,resolve 和 reject。resolve 和 reject 函数分别用来将 Promise 的状态设置为 Fulfilled 和 Rejected,并且传递异步操作的结果或者错误信息。当异步操作完成后,我们需要调用 resolve 或者 reject 函数将 Promise 实例的状态改变。
Promise 链式调用
Promise 链式调用是 Promise 的一个重要特性。我们可以在一个 Promise 实例的 resolved 回调函数里再次返回一个 Promise 实例,从而实现多个异步操作的组合。这样,我们就可以避免回调地狱的问题。
// javascriptcn.com 代码示例 const promise = asyncFunc1() .then(result1 => { return asyncFunc2(result1); }) .then(result2 => { return asyncFunc3(result2); }) .then(result3 => { console.log(result3); }) .catch(error => { console.log(error); });
在上面的例子中,我们先调用了 asyncFunc1,当它返回的 Promise 状态为 Fulfilled 时,会自动调用 then 方法中的回调函数。在回调函数中,我们返回了 asyncFunc2(result1),这样 asyncFunc2 会在 asyncFunc1 的结果基础上进行计算,并将计算结果返回给 asyncFunc3。同样地,asyncFunc3 会在 asyncFunc2 的结果基础上进行计算,并将计算结果返回给最后一个 then 方法。如果任何一个异步操作出现错误,catch 方法会被调用。
Promise.all 和 Promise.race
Promise.all 可以用来当我们需要多个异步操作都完成后再执行某个操作的时候。Promise.all 接受一个 Promise 实例组成的数组,它返回的 Promise 状态为 Fulfilled 并传递所有 Promise 实例返回结果的数组,只有当所有 Promise 实例都 fulfilled 时,Promise.all 才 fulfilled。
Promise.all([promise1, promise2, promise3]) .then(results => { console.log(results); }) .catch(error => { console.log(error); });
Promise.race 可以用来当我们需要一系列异步操作中最先完成的一个操作的结果时。Promise.race 接受一个 Promise 实例组成的数组,它返回的 Promise 状态为 Fulfilled 或者 Rejected,并且传递最先完成的 Promise 实例的返回结果或者错误信息。
Promise.race([promise1, promise2, promise3]) .then(result => { console.log(result); }) .catch(error => { console.log(error); });
Promise 取消机制
Promise 取消机制是指当 Promise 实例的异步操作还未完成时,我们可以取消这个异步操作并且防止它继续执行。有时候我们需要取消 Promise 中未完成的任务,例如当用户通过输入框实时搜索数据时,如果用户快速地输入了多次搜索内容,那么前面的搜索操作就可能会被后面的搜索操作所覆盖,从而导致前面的异步操作变成无用功。
关于 Promise 取消机制的讨论
在讨论如何实现 Promise 取消机制之前,有一个问题值得我们思考:为什么 Promise 没有内置的取消机制?
其中一个原因是 Promise 本身的逻辑:Promise 实例一旦创建,它的状态就不能再改变。而这个状态的改变是由 Promise 的异步操作所决定的。也就是说,Promise 取消机制必须通过 Promise 外层的变量、函数等方式去控制 Promise 内的异步操作。而 Promise 内部总不能随便通过外部变量去控制它的异步操作。
另一个原因是 Promise 的可组合性。Promise 的链式调用可以非常方便地将多个异步操作组合起来,如果我们直接对其中某个 Promise 进行取消操作,那么这个 Promise 可能处于链式调用中的任何一个位置,它前面和后面的 Promise 的状态都不确定,这就很难保证整个异步操作的正确性。
因此,如果需要实现 Promise 取消机制,我们需要保证取消操作不会影响异步操作本身的正确性,同时还需保证取取消操作符的实用性,否则取消操作就很难被接受。
手动实现 Promise 取消机制
在现有的 JavaScript 中,并没有内置的 Promise 取消机制。不过,我们可以手动实现一些方法来实现类似的功能。本文提供两种实现方法:基于 cancalToken 的方法和基于 RxJS 的方法。
基于 cancelToken 的方法
为了实现基于 cancelToken 的方法,我们需要创建一个 CancelToken 类。这个类包含一个 cancel 属性,它是一个记录取消状态的布尔值,并且包含一个 cancel 方法,用于将 cancel 属性设置为 true。另外,我们还需要在 Promise 实例中增加一个可选参数 cancelToken,用于传入我们创建的 CancelToken 实例。在异步操作中,我们需要判断当前 cancelToken 的状态,并且进行相应的处理。
// javascriptcn.com 代码示例 class CancelToken { constructor() { this.cancel = false; } cancel() { this.cancel = true; } } function makeCancelable(promise, cancelToken) { let cancelExector = null; const cancelPromise = new Promise((resolve, reject) => { cancelExector = () => { reject(new Error("Operation canceled")); }; }); const promiseExector = Promise.race([promise, cancelPromise]); promiseExector.cancel = cancelExector; if (cancelToken) { cancelToken.cancel = () => { cancelExector(); }; } return promiseExector; }
在上面的代码中,我们首先定义了一个 CancelToken 类,这个类有一个 cancel 属性,它表示当前取消操作的状态。我们还定义了一个 makeCancelable 函数,用于将 Promise 实例转化成可取消 Promise。
makeCancelable 函数接受两个参数:promise 和 cancelToken。其中 promise 是需要转化的 Promise 实例,cancelToken 是自定义的 CancelToken 实例。我们首先创建了一个 cancelPromise,它返回的是一个 Rejected 的 Promise 实例,这个 Promise 实例的值是一个 Error 对象,用于表示当前操作被取消了。然后我们使用 Promise.race 将 cancelPromise 和 promise 组合起来,当任意一个 Promise 状态发生变化时,Promise.race 就会返回它的状态和值。
接着,我们对 promiseExector 对象增加了一个 cancel 属性,这个属性是一个函数,用于取消异步操作,并且会将 cancelPromise 的状态设置为 Rejected。
最后,我们判断传入的 cancelToken 是否存在,如果存在,则将 cancelToken 的 cancel 方法绑定到 cancelExector 并且返回 promiseExector 对象,否则直接返回 promiseExector 对象。
现在,我们可以使用 makeCancelable 函数将 Promise 实例转化为可取消的 Promise 实例,然后在异步操作中添加取消操作的代码。例如,以下代码演示了如何使用鼠标事件进行取消。
// javascriptcn.com 代码示例 const cancelToken = new CancelToken(); const element = document.querySelector("#search"); element.addEventListener("input", async event => { try { const data = await makeCancelable(fetch("/api/data"), cancelToken); if (data !== null) { // do something } } catch (error) { console.log(error); } }); element.addEventListener("click", event => { cancelToken.cancel(); });
在上面的代码中,我们首先创建了一个 CancelToken 实例 cancelToken,然后将它作为参数传递给 makeCancelable。当 input 事件触发时,我们将异步操作的结果赋值给 data。如果我们点击了 element 元素,那么事件回调函数就会调用 cancelToken 的 cancel 方法,从而取消异步操作。
基于 RxJS 的方法
除了基于 CancelToken 的方法,我们还可以使用 RxJS 库来实现 Promise 取消机制。RxJS 是 Reactive Extensions 的 JavaScript 版本,它提供了一些实用的工具函数和操作符,用于处理异步流和事件流。
在 RxJS 中,我们可以使用 Subject 类来创建一个可被取消的异步处理流,它允许我们在任何时候取消正在进行的异步操作。
首先,我们需要创建一个 Subject 实例,它用于发出 cancel 事件。在异步操作中,我们需要监听这个 Subject 实例,当它发出 cancel 事件时,我们需要进行相应的处理。
// javascriptcn.com 代码示例 import { Subject, from } from "rxjs"; import { takeUntil } from "rxjs/operators"; const cancel$ = new Subject(); from(fetch("/api/data")) .pipe(takeUntil(cancel$)) .subscribe( response => { // handle response }, error => { console.log(error); }, () => { console.log("complete"); } ); document.querySelector("#cancel").addEventListener("click", () => { cancel$.next(); });
在上面的代码中,我们首先创建了一个 Subject 实例 cancel$,它用于发出 cancel 事件,表示我们需要取消异步操作。然后我们使用 RxJS 的 from 方法将 fetch 方法返回的 Promise 实例转化成一个 Observable 实例。最后,我们使用 takeUntil 操作符,它接受一个 Observable 实例作为参数,并返回一个新的 Observable 实例,它会在源 Observable 实例调用 complete 或者 cancel$ 实例发出 next 事件时停止发出事件。通过使用 takeUntil 操作符,我们就可以在 cancel$ 实例发出 cancel 事件时停止异步操作。
最后,我们使用事件监听器来在点击时向 cancel$ 实例发送 cancel 事件。
总结
本文详细介绍了 Promise 基本用法和 Promise 取消机制。我们首先回顾了 Promise 的基本概念和使用方法,然后讨论了 Promise 的问题以及取消机制的实现方法。如果您需要实现 Promise 取消机制,可以使用基于 CancelToken 或者 RxJS 的方法。基于 CancelToken 的方法需要定义一个 CancelToken 类和一个自定义的 makeCancelable 函数,可实现任何异步操作的取消。基于 RxJS 的方法可以使用 takeUntil 操作符和 Subject 类来实现异步操作的取消。
来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/65310fcb7d4982a6eb2aa7c7