前段时间试图弄一个能远程控制 aria2 的脚本,并且能和其它平台(比如 Telegram)的交互方式整合起来。
我找到的 aria2 依赖对 Promise 的支持不太完整,部分只支持回调,并且没有对实时获取下载进度等方法做封装,因此得自己造一个轮子。
要实时追踪某个任务的进度,首先得记住任务的 GID,因此我使用了类来管理这些数据。aria2 库在新建完下载任务后会返回此任务的 GID,此时即可将 GID 记录下来。
记录好 GID 之后,便需要解决下载进度回显的问题。我想起 GramJS 的 downloadMedia
方法中有一个 progressCallback
,可以传入自定义的回显函数来实现各种花里胡哨的进度条,遂打算 抄过来 学习一下。
ES6 中将函数作为参数传入另一个函数的大致方式:
async download(uri, progressCallback) {
const gid = await this.aria2.call(...); // 创建任务,获取 gid
return new Promise((resolve, reject) => {
const interval = setInterval(() => {
// 定时获取任务详情
const progressInfo = await this.aria2.call(...);
// 把任务信息通过 progressCallback 函数传回去
progressCallback(...);
}, 3 * 1000);
// 将任务信息保存
this.tasks[gid] = { resolve, interval, path: "..." };
// 一小时后认为超时
setTimeout(() => {
delete this.tasks[gid];
clearInterval(interval);
reject(new Error("Timeout"));
}, 3600 * 1000);
}
}
这里并未用到 resolve
而是和 interval
一起先存进了 this.tasks
。因为 aria2 何时会完成下载是不确定的,要等 aria2 收到 onDownloadComplete
事件我们才被动地知道任务完成了。所以在收到事件以及目标任务的 GID 后,我再去检查 this.tasks
中是否有这个任务,然后把它的 Promise resolve 掉。
this.aria2.on("onDownloadComplete", ([{ gid }]) => {
// 如果存在这个任务
if (this.tasks[gid]) {
this.tasks[gid].resolve(path); // path 为下载好的文件路径,此处仅为示范
clearInterval(this.tasks[gid].interval);
delete this.tasks[gid];
}
});
如此一来,既对 aria2 的下载文件功能做了异步封装,又支持了传入自己的 progress handler。调用的时候只需要一个:
const filePath = await aria2.download("https://example.com/some-file");
优雅,实在是太优雅了.jpg
(exception handling 不在本文讨论范围内)