javascript基础学习系列三百八十一:期约扩展

本文探讨了ES6中的Promise机制,强调了其在实际应用中的局限性,如缺乏期约取消和进度追踪功能。作者介绍了如何通过CancelToken和扩展Promise实现这些功能,并给出了示例代码。
摘要由CSDN通过智能技术生成

ES6 期约实现是很可靠的,但它也有不足之处。比如,很多第三方期约库实现中具备而 ECMAScript规范却未涉及的两个特性:期约取消和进度追踪。

  1. 期约取消
    我们经常会遇到期约正在处理过程中,程序却不再需要其结果的情形。这时候如果能够取消期约就好了。某些第三方库,比如 Bluebird,就提供了这个特性。实际上,TC39 委员会也曾准备增加这个特性,但相关提案最终被撤回了。结果,ES6 期约被认为是“激进的”:只要期约的逻辑开始执行,就没有办法阻止它执行到完成。
    实际上,可以在现有实现基础上提供一种临时性的封装,以实现取消期约的功能。这可以用到 Kevin Smith 提到的“取消令牌”(cancel token)。生成的令牌实例提供了一个接口,利用这个接口可以取消期约;同时也提供了一个期约的实例,可以用来触发取消后的操作并求值取消状态。
    下面是 CancelToken 类的一个基本实例:
 constructor(cancelFn) { 
 this.promise = new Promise((resolve, reject) => { 
 cancelFn(resolve); 
 }); 
 } 
} 

这个类包装了一个期约,把解决方法暴露给了 cancelFn 参数。这样,外部代码就可以向构造函数中传入一个函数,从而控制什么情况下可以取消期约。这里期约是令牌类的公共成员,因此可以给它添加处理程序以取消期约。
这个类大概可以这样使用:

<button id="cancel">Cancel</button> 
<script> 
class CancelToken { 
 constructor(cancelFn) { 
 this.promise = new Promise((resolve, reject) => { 
 cancelFn(() => { 
 setTimeout(console.log, 0, "delay cancelled"); 
 resolve(); 
 }); 
 }); 
 } 
} 
const startButton = document.querySelector('#start'); 
const cancelButton = document.querySelector('#cancel'); 
function cancellableDelayedResolve(delay) { 
 setTimeout(console.log, 0, "set delay"); 
 return new Promise((resolve, reject) => { 
 const id = setTimeout((() => { 
 setTimeout(console.log, 0, "delayed resolve"); 
 resolve(); 
 }), delay);
  const cancelToken = new CancelToken((cancelCallback) => 
 cancelButton.addEventListener("click", cancelCallback)); 
 cancelToken.promise.then(() => clearTimeout(id)); 
 }); 
} 
startButton.addEventListener("click", () => cancellableDelayedResolve(1000)); 
</script> 

每次单击“Start”按钮都会开始计时,并实例化一个新的 CancelToken 的实例。此时,“Cancel”按钮一旦被点击,就会触发令牌实例中的期约解决。而解决之后,单击“Start”按钮设置的超时也会被取消。
期约进度通知
执行中的期约可能会有不少离散的“阶段”,在最终解决之前必须依次经过。某些情况下,监控期约的执行进度会很有用。ECMAScript 6 期约并不支持进度追踪,但是可以通过扩展来实现。一种实现方式是扩展 Promise 类,为它添加 notify()方法,如下所示:

 constructor(executor) { 
 const notifyHandlers = []; 
 super((resolve, reject) => { 
 return executor(resolve, reject, (status) => { 
 notifyHandlers.map((handler) => handler(status)); 
 }); 
 }); 
 this.notifyHandlers = notifyHandlers; 
 } 
 notify(notifyHandler) { 
 this.notifyHandlers.push(notifyHandler); 
 return this; 
 } 
}

这样,TrackablePromise 就可以在执行函数中使用 notify()函数了。可以像下面这样使用这个
函数来实例化一个期约:

 function countdown(x) { 
 if (x > 0) { 
 notify(`${20 * x}% remaining`); 
 setTimeout(() => countdown(x - 1), 1000); 
 } else { 
 resolve(); 
 } 
 } 
 countdown(5); 
}); 

这个期约会连续5次递归地设置1000毫秒的超时。每个超时回调都会调用notify()并传入状态值。假设通知处理程序简单地这样写:

 function countdown(x) { 
 if (x > 0) { 
 notify(`${20 * x}% remaining`); 
 setTimeout(() => countdown(x - 1), 1000); 
 } else { 
 resolve(); 
 } 
 } 
 countdown(5); 
}); 
p.notify((x) => setTimeout(console.log, 0, 'progress:', x)); 
p.then(() => setTimeout(console.log, 0, 'completed')); 
// (约 1 秒后)80% remaining 
// (约 2 秒后)60% remaining 
// (约 3 秒后)40% remaining 
// (约 4 秒后)20% remaining 
// (约 5 秒后)completed 

notify()函数会返回期约,所以可以连缀调用,连续添加处理程序。多个处理程序会针对收到的每条消息分别执行一遍,如下所示:

p.notify((x) => setTimeout(console.log, 0, 'a:', x)) 
.notify((x) => setTimeout(console.log, 0, 'b:', x)); 
p.then(() => setTimeout(console.log, 0, 'completed')); 
// (约 1 秒后) a: 80% remaining 
// (约 1 秒后) b: 80% remaining 
// (约 2 秒后) a: 60% remaining 
// (约 2 秒后) b: 60% remaining 
// (约 3 秒后) a: 40% remaining 
// (约 3 秒后) b: 40% remaining 
// (约 4 秒后) a: 20% remaining 
// (约 4 秒后) b: 20% remaining 
// (约 5 秒后) completed 

总体来看,这还是一个比较粗糙的实现,但应该可以演示出如何使用通知报告进度了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值